michael99 has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to replace some nodes value in XML using the XML:LibXML file parser from user input.

Example XML

<header> <id x_id="1"> <a></a> <b></b> <c>NA</c> </id> </header>

Code:

use strict; use warnings; use XML::LibXML; print "Please specify node c content\n"; my $node_c = <STDIN>; chomp $node_c; my $template = "xx.xml"; my $parser = XML::LibXML->new(); my $doc = $parser->parse_file($template); my($object0) = $doc->findnodes("/header/id/c/text()"); my $text0 = XML::LibXML::Text->new($node_c); $object0->replaceNode($text0);

Currently my code is working, but it somehow removed and from the xml when I tried to replace Node b content if both nodes are empty. If there is some value in node a or b, it works fine.

Replies are listed 'Best First'.
Re: XML replacenode
by choroba (Cardinal) on Apr 15, 2020 at 07:49 UTC
    The code as posted doesn't work. Where does $node_c come from?

    If you want to fill a node regardless of whether it contains any text, don't use its text contents - as there can be none. Clean the node instead and add the new text:

    #! /usr/bin/perl use strict; use feature qw{ say }; use warnings; use XML::LibXML; my $xml = '<header> <id x_id="1"> <a></a> <b></b> <c>NA</c> </id> </header>'; print "Please specify node c content: "; chomp( my $new_text = <STDIN> ); my $dom = 'XML::LibXML'->load_xml(string => $xml); for my $node($dom->findnodes('(/header/id/b | /header/id/c)')) { $node->removeChildNodes; $node->appendText($new_text); } print $dom;

    Or, try XML::XSH2:

    open file.xml ; for (/header/id/b|/header/id/c) set text() "YOUR TEXT HERE" ; save :b ;

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Hi, it was a typo in my original post. I tried your code and it shows XML::LibXML::Document=SCALAR(0x6e7fd0)

        I edited it to load file from my cwd. It seems still interrupting empty node: Code:
        #! /usr/bin/perl use strict; use feature qw{ say }; use warnings; use XML::LibXML; print "Please specify node c content: "; chomp( my $new_text = <STDIN> ); my $file = 'xx.xml'; my $dom = 'XML::LibXML'->load_xml(location => $file); for my $node($dom->findnodes('(/header/id/c)')) { $node->removeChildNodes; $node->appendText($new_text); } #print $dom; open (my $Output, "> $file") or die "Could not write $file"; print $Output $dom->toString(); close ($Output) or die "Could not close generated $file ";
        Output:
        <?xml version="1.0"?> <header> <id x_id="1"> <a/> <b/> <c>as</c> </id> </header>
        Expecting output:
        <?xml version="1.0"?> <header> <id x_id="1"> <a></a> <b></c> <c>something</c> </id> </header>
Re: XML replacenode
by marto (Cardinal) on Apr 15, 2020 at 07:55 UTC

    As posted your script returns:

    Global symbol "$node_c" requires explicit package name (did you forget + to declare "my $node_c"?) at derp.pl line 13. Execution of derp.pl aborted due to compilation errors.

    Where possible please copy/paste rather than retype code. I've never used XML::LibXML (at least not that I can remember at this time of day), but believe your example should look something like this:

    use strict; use warnings; use XML::LibXML; print "Please specify node c content\n"; my $node_c = <STDIN>; chomp $node_c; my $template = "xx.xml"; my $parser = XML::LibXML->new(); my $doc = $parser->parse_file($template); my($object0) = $doc->findnodes("/header/id/c/text()"); $object0->setData( $node_c ); print $doc;

    Output:

    Please specify node c content derp <?xml version="1.0"?> <header> <id x_id="1"> <a/> <b/> <c>derp</c> </id> </header>

    Update: Correct use of XML::LibXML here

    Or using Mojo::DOM:

    #!/usr/bin/perl use strict; use warnings; use feature 'say'; use Mojo::DOM; my $xml = '<header> <id x_id="1"> <a></a> <b></b> <c>NA</c> </id> </header>'; my $dom = Mojo::DOM->new->xml(1)->parse( $xml ); say 'Current value of c: ' . $dom->at('id > c')->text; say 'Please specify node c content: '; chomp( my $node_c = <STDIN> ); $dom->at('id > c')->content( $node_c ); say $dom->content;

    Output:

    Current value of c: NA Please specify node c content: derp <header> <id x_id="1"> <a /> <b /> <c>derp</c> </id> </header>

      Hi, I have not try on your code but from the output you posted, it seems remove and from the original file. Is there anyway to retain empty node a and b?

        That is valid XML for a node with no content or attributes.

        #!/usr/bin/perl use strict; use warnings; use feature 'say'; use Mojo::DOM; my $xml = '<header> <id x_id="1"> <a></a> <b></b> <c>NA</c> </id> </header>'; my $dom = Mojo::DOM->new->xml(1)->parse( $xml ); say 'Current value of c: ' . $dom->at('id > c')->text; say 'Please specify node c content: '; chomp( my $node_c = <STDIN> ); $dom->at('id > c')->content( $node_c ); say $dom->content; say 'add content to nodes a & b'; $dom->at('id > b')->content( 'Foo' ); $dom->at('id > a')->content( 'Bar' ); say $dom->content;

        Output:

        Current value of c: NA Please specify node c content: derp <header> <id x_id="1"> <a /> <b /> <c>derp</c> </id> </header> add content to nodes a & b <header> <id x_id="1"> <a>Bar</a> <b>Foo</b> <c>derp</c> </id> </header>