Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid

Using XML::XPath to change values

by paisani (Acolyte)
on Feb 12, 2018 at 18:07 UTC ( #1209014=perlquestion: print w/replies, xml ) Need Help??
paisani has asked for the wisdom of the Perl Monks concerning the following question:

UPDATE: I broke down and rewrote my code using twig, it's *amazing*. Thanks for the suggestion and a special thanks for those who took the time to provide solutions.

I can't seem to find out how to use XML::Xpath to change the value of priority on the includes in the xml below. I thought this would be simple, but I'm surprised at how stuck I seem to be getting. I'd strongly prefer using XML::XPath and not some other lib.
<includes> <include> <priority>9990</priority> <required>true</required> <location>/efs/dist/tpcommon/itrstemplates/latest/common/templ +ates/equities.xml</location> </include> <include disabled="true"> <priority>10001</priority> <required>true</required> <location>/efs/dist/itrs/tools/latest/common/templates/global_ +authentication.xml</location> </include> </includes>
-- Code snippet below -
my $path = '//includes/include'; my $nodes = $xp->find($path); for my $include ($nodes->get_nodelist) { my $priority = $xp->find('./priority', $include); my $location = $xp->find('./location', $include); print "Location = " . $location->string_value; print ", with Priority = " . $priority->string_value . "\n"; ## I want to change this priority value to +1

Replies are listed 'Best First'.
Re: Using XML::XPath to change values
by haukex (Canon) on Feb 12, 2018 at 21:59 UTC

    The following does what you want, although I don't think it's particularly pretty. I think that XML::Twig (as recommended by Cristoforo), or at least XML::LibXML would be much better. Note that both XML::XPath and XML::Twig use the same underlying parser, XML::Parser, which in turn uses Expat.

    use warnings; use strict; use XML::XPath; my $xml = <<'ENDXML'; <includes> <include> <priority>9990</priority> <required>true</required> <location>/efs/dist/tpcommon/itrstemplates/latest/common/templ +ates/equities.xml</location> </include> <include disabled="true"> <priority>10001</priority> <required>true</required> <location>/efs/dist/itrs/tools/latest/common/templates/global_ +authentication.xml</location> </include> </includes> ENDXML my $xp = XML::XPath->new(xml => $xml); my $nodes = $xp->find('//includes/include'); for my $include ($nodes->get_nodelist) { for my $p ($xp->findnodes('./priority', $include)) { my $priority = $p->string_value; $p->removeChild($_) for $p->getChildNodes; $p->appendChild( XML::XPath::Node::Text->new($priority+1) ); } } my ($root) = $xp->findnodes('/'); print $root->toString, "\n";

    In regards to the last two lines above, I haven't yet found an easy way to print back out the XML document, maybe I'm missing something obvious. Anyway, for comparison, here's an XML::Twig solution:

    use XML::Twig; open my $ofh, '>', \my $outxml or die $!; my $twig = XML::Twig->new( twig_print_outside_roots => $ofh, twig_roots => { '/includes/include/priority' => sub { my ($twig, $elt) = @_; $elt->set_text( $elt->text+1 ); $elt->flush($ofh); }, } ); $twig->parse($xml); close $ofh; print $outxml;

    I've written some things a little longer than they need to be in this code for clarity, the code could be shortened quite a bit, and also the output doesn't need to go to a string, it could be sent to a file directly.

Re: Using XML::XPath to change values
by Cristoforo (Curate) on Feb 12, 2018 at 19:52 UTC
    Hi paisani

    I'm not really acquainted with XML, but this node suggests (in one of the replies), that XML::XPath is better suited to finding but not editing.

    He ended up using XML::Twig.

Re: Using XML::XPath to change values
by Jenda (Abbot) on Feb 12, 2018 at 21:57 UTC

    Do you want to increase all priorities or just some? If all then something like this would be enough:

    use strict; use XML::Rules; my $filter = XML::Rules->new( style => "filter", rules => { priority => sub { 'priority' => $_[1]{_content}+1 }, }); $filter->filter(\*DATA, \*STDOUT);

    If you need to change only some priorities, you can add some tests:

    my $filter = XML::Rules->new( style => "filter", ident => ' ', rules => { _default => 'as is', include => sub { if ($_[1]{location}{_content} =~ m{/itrstemplates/}) { $_[1]{priority}{_content}++; } delete $_[1]{_content}; # remove extra whitespace return include => $_[1] }, } );

    Enoch was right!
    Enjoy the last years of Rome.

Re: Using XML::XPath to change values
by choroba (Bishop) on Feb 13, 2018 at 09:54 UTC
    Using XML::LibXML :
    #! /usr/bin/perl use warnings; use strict; use XML::LibXML; my $doc = 'XML::LibXML'->load_xml(location => shift); for my $include ($doc->findnodes('/includes/include')) { my $priority = $include->find('priority/text()')->[0]; $priority->setData($priority->data + 1); } print $doc;

    or, less verbose XML::XSH2 :

    open file.xml ; for /includes/include set priority (priority + 1) ; save :b ;

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Using XML::XPath to change values
by Anonymous Monk on Feb 13, 2018 at 13:47 UTC
    The two best tools for XML work in Perl are "LibXML2" and "Twig." (To me, "Simple" is too simple.) If you are manipulating the structure and especially when the file is very, very big, "Twig" is often best because its memory-footprint is much lower.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1209014]
Approved by toolic
Front-paged by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (4)
As of 2018-08-16 15:34 GMT
Find Nodes?
    Voting Booth?
    Asked to put a square peg in a round hole, I would:

    Results (168 votes). Check out past polls.