Add new nodes/elements to XML::LibXML object
Asked Answered
P

3

5

Here is a basic XML document example

<book_reviewers>
    <results>
        <reviewer>
            <name>Anne</name>
            <profession>Catfish wrangler</profession>
        </reviewer>
        <reviewer>
            <name>Bob</name>
            <profession>Beer taster</profession>
        </reviewer>
        <reviewer>
            <name>Charlie</name>
            <profession>Gardener</profession>
        </reviewer>
    </results>
</book_reviewers>

And I want to add this:

<reviewer>
    <name>Joan</name>
    <profession>Jett</profession>
 </reviewer>

I have tried a combinations of many solutions, here is one that, at least, doesn't throw an error, but, it also doesn't work.

#!/usr/bin/perl
use XML::LibXML;
use strict;

my $filename = "cr.xml";

my $parser = XML::LibXML->new();
my $critic_details = $parser->parse_file("$filename") or die;
my $new_reviewer = $critic_details->documentElement;
my $reviewer_name = $critic_details->documentElement;
my $reviewer_prof = $critic_details->documentElement;
my $newnode = $critic_details->documentElement;

 for my $reviewers($critic_details->findnodes("book_reviewers/results/reviewers")){
     $new_reviewer = $reviewers->createElement("reviewer");
    $reviewer_name = $new_reviewer->addChild("name");
    $reviewer_name->appendText("Joan");
    $reviewer_prof = $new_reviewer->addChild("profession");
    $reviewer_prof->appendText("Jett");
    $newnode = $reviewers->addSibling($new_reviewer); #also tried addChild

}
print $critic_details->toString;

The output that i get is:

<?xml version="1.0"?>
<book_reviewers>
        <results>
                <reviewer>
                        <name>Anne</name>
                        <profession>Catfish wrangler</profession>
                </reviewer>
                <reviewer>
                        <name>Bob</name>
                        <profession>Beer taster</profession>
                </reviewer>
                <reviewer>
                        <name>Charlie</name>
                        <profession>Gardener</profession>
                </reviewer>
        </results>
</book_reviewers>

Which is just the original data

Any help greatly appreciated - I am very new to both Perl and XML Cheers

Preindicate answered 18/5, 2014 at 9:29 Comment(0)
P
5

There are lots of ways to do this

This way creates the nodes separately and puts it together. It isn't production ready as it assumes there is a section

#!/usr/bin/perl
use XML::LibXML;
use strict;

my $filename = "cr.xml";

my $parser = XML::LibXML->new();
my $critic_details = $parser->parse_file("$filename") or die;

my $reviewer  = $critic_details->findnodes("book_reviewers/results")->[0];

my $node = XML::LibXML::Element->new("reviewer");

my $p = XML::LibXML::Element->new("profession");
my $pn = XML::LibXML::Text->new("Jett");
$p->addChild($pn);

my $n = XML::LibXML::Element->new("name");
my $nn = XML::LibXML::Text->new("Joan");
$n->addChild($nn);

$node->addChild($p);
$node->addChild($n);

$reviewer->addChild($node);

print $critic_details->toString;

which gives this

<?xml version="1.0"?>
<book_reviewers>
    <results>
        <reviewer>
            <name>Anne</name>
            <profession>Catfish wrangler</profession>
        </reviewer>
        <reviewer>
            <name>Bob</name>
            <profession>Beer taster</profession>
        </reviewer>
        <reviewer>
            <name>Charlie</name>
            <profession>Gardener</profession>
        </reviewer>
    <reviewer><profession>Jett</profession><name>Joan</name></reviewer></results>
</book_reviewers>
Portiaportico answered 18/5, 2014 at 10:57 Comment(2)
Is there a way to do it so that it is indented the same as existing data? CheersPreindicate
@KerynDrake, for indenting, take a look at XML::LibXML::PrettyPrint.Want
C
4

Just to help you out with some more neat things of XML::LibXML and get you to a bit more easy way to understand how XML works, I posted here another answer. Maybe you wil find some useful clues in it as well:

use strict;
use warnings;

use utf8;

use XML::LibXML;

my $filename = "cr.xml";

my $parser = XML::LibXML->new();
my $critic_details = $parser->parse_file("$filename") or die;

# find ALL the <book_reviewers><results> nodes
my @results = $critic_details->findnodes("book_reviewers/results");
die "no result node in xml-file" unless @results;

my ($name, $profession) = ("Joan", "Jett");

#
# Here gets the work done, put this in a loop for more entries
#

# add a new <reviewer> node to the LAST <results>
my $reviewer_node_child;
my $reviewer_node = $results[-1]->addNewChild(undef, "reviewer");

# create a child node
$reviewer_node_child =  $reviewer_node->addNewChild(undef, "name");
$reviewer_node_child->appendTextNode($name);

# create a child node
$reviewer_node_child =  $reviewer_node->addNewChild(undef, "profession");
$reviewer_node_child->appendTextNode($profession);

#
# Done the heavy power lifting
#

use XML::LibXML::PrettyPrint;
my $pretty = XML::LibXML::PrettyPrint->new(
  indent_string => ' ' x4,
  element       => {
    compact       => [qw| name profession | ],
    }
  );
$pretty->pretty_print($critic_details);

print $critic_details->toString;

__END__

The undef in the addNewChild methods are needed here. They are used to set the XML-NameSpace, which you will not need here, but in huge XML-documents is really the way to go.

Also, I added XML::LibXML::PrettyPrint just to show you how to get back to the nice pretty looking syntax. Forgive me for adding the use… add the end of the script.

which produces the this result:

<?xml version="1.0"?>
<book_reviewers>
    <results>
        <reviewer>
            <name>Anne</name>
            <profession>Catfish wrangler</profession>
        </reviewer>
        <reviewer>
            <name>Bob</name>
            <profession>Beer taster</profession>
        </reviewer>
        <reviewer>
            <name>Charlie</name>
            <profession>Gardener</profession>
        </reviewer>
        <reviewer>
            <name>Joan</name>
            <profession>Jett</profession>
        </reviewer>
    </results>
</book_reviewers>

Enjoy XML and Perl, a very powerful but daunting combination of tools!

Crooked answered 18/5, 2014 at 12:36 Comment(6)
Thankyou - I really appreciate it. And yes very daunting, but i think that i am getting there, thanks to people like yourself, KeepCalmAndCarryOn and tobyink. There is so much info but it often doesnt make too much sense until it is put in context - thanks once againPreindicate
If i had a second file with the same stucture but different data is there a function that would grab it all, i.e. parse both files, then add all reviewers from the second one and write them to to the first one or do you have to do it in a loop, manually grabbing each one - I would look on CPAN but the site has been down for hours ; CheersPreindicate
you can have more than one XML::LibXML::Document instances ($critic_details and $additional_details). Then, @additionals = $additional_details->findnodes("book_reviewers/results/reviewer") will give you an array of all the additional <reviewer>-nodes. --- then you can iterate over them and make that loop I was already suggesting.Crooked
however, if you have exact the same structures, you can use cloneNode to do the deep-copying - leaving the additional_xml intact: $results[-1]->addChild($_->cloneNode(1)) foreach ($additional_details->findnodes("book_reviewers/results/reviewer")) --- a bit compact notation, but that is to show that TIMTOWTDI especially if you only have one statement in a foreach loop.Crooked
Thankyou once again VAnHoesel - as we say here in Australia "Your blood is worth bottling!!"Preindicate
@KerynDrake: you might like to add some kuddo's for all the correct answers gave :-) — thanks!Crooked
V
1

It's possible to use XML::LibXML to essentially import the nodes from one XML Document into another. This is useful since you won't have to build the new data explicitly using objects, but instead can just use standard XML Notation.

The below demonstrates this. This could easily be expanded to important more than one new reviewer by setting a root node in the new document and then iterating over the found nodes instead of just selecting the first.

#!/usr/bin/perl

use strict;
use warnings;

use XML::LibXML;

my $dom = XML::LibXML->load_xml(IO => \*DATA);

my $newnode = XML::LibXML->load_xml(string => <<'END_STRING')->findnodes('//*')->[0];
<reviewer>
    <name>Joan</name>
    <profession>Jett</profession>
</reviewer>
END_STRING

$dom->findnodes("book_reviewers/results")->[0]->addChild($newnode);

print $dom->toString;

__DATA__
<book_reviewers>
    <results>
        <reviewer>
            <name>Anne</name>
            <profession>Catfish wrangler</profession>
        </reviewer>
        <reviewer>
            <name>Bob</name>
            <profession>Beer taster</profession>
        </reviewer>
        <reviewer>
            <name>Charlie</name>
            <profession>Gardener</profession>
        </reviewer>
    </results>
</book_reviewers>

Outputs:

<?xml version="1.0"?>
<book_reviewers>
    <results>
        <reviewer>
            <name>Anne</name>
            <profession>Catfish wrangler</profession>
        </reviewer>
        <reviewer>
            <name>Bob</name>
            <profession>Beer taster</profession>
        </reviewer>
        <reviewer>
            <name>Charlie</name>
            <profession>Gardener</profession>
        </reviewer>
    <reviewer>
    <name>Joan</name>
    <profession>Jett</profession>
</reviewer></results>
</book_reviewers>
Vicenta answered 19/5, 2014 at 3:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.