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

using XML::Twig, not sure that is the most appropriate module, but I need to eventually print a modified XML version of the input. 'myobj' has two instances, but with unique attribute of "uniqueName". Ultimately, I'm trying to match on a myObj instance with a specific uniqueName text value, and modify one of the attrib? text values for that instance. Cant get the parser to filter on a particular uniqueName. And the path and parent reported when it does filter on one of the attribA values seems to be missing the myObj and "myattributes" nodes.

demo.xml:
<myconfig> <myobj> <uniqueName> objA </uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> <myobj> <uniqueName> objB </uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> </myconfig>

t.pl:

use strict; use warnings; use English; use XML::Twig; my $xmlFile = "demo.xml"; my $t = XML::Twig->new( keep_atts_order => 1, pretty_print => 'indented_a', twig_roots => { # following gives "unrecognized expression in handler" err +or #q(myconfig/myobj[uniqueName="objA"]/myattributes/attribB) + => \&getfoo, q(myconfig/myobj/myattributes/attribB) => \&getfoo, } ); $t->parsefile( $xmlFile ); $t->print; exit 0; sub getfoo { my ($t, $e) = @_; print "text=", $e->text(), "' tag='", $e->tag(), "' path='", $e->path(), "' parentTag='", $e->parent->tag(), "'\n";
Dan

Replies are listed 'Best First'.
Re: XML::Twig correct parent, xpath filter
by choroba (Cardinal) on Oct 30, 2015 at 15:09 UTC
    When you use twig_roots, the matching nodes don't see their parents. Try using twig_handlers instead.
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      thanks. that solved the problem with the missing parents. I still have the problem with how to select on a specific node. I'm still getting two nodes recognized. I did correct one typo with the object name, and I no longer get a parsing error, but I still cant figure out how to select the specific myobj that I'm looking for. modified example (got rid of spaces in demo.xml uniqueName node, and added the '@' in front of uniqueName in the handler filter.

      use strict; use warnings; use English; use XML::Twig; my $xmlFile = "demo.xml"; my $t = XML::Twig->new( keep_atts_order => 1, pretty_print => 'indented_a', twig_handlers => { q(myconfig/myobj[@uniqueName=objA]/myattributes/attribB) = +> \&getfoo, } ); $t->parsefile( $xmlFile ); $t->print; exit 0; sub getfoo { my ($t, $e) = @_; print "text=", $e->text(), "' tag='", $e->tag(), "' path='", $e->path(), "' parentTag='", $e->parent->tag(), "'\n"; }

      output:

      text= fooB ' tag='attribB' path='/myconfig/myobj/myattributes/attribB' + parentTag='myattributes' text= fooB ' tag='attribB' path='/myconfig/myobj/myattributes/attribB' + parentTag='myattributes' <myconfig> <myobj> <uniqueName>objA</uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> <myobj> <uniqueName>objB</uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> </myconfig>
        @ is for attributes. Remove the whole predicate, Twig doesn't understand it anyway. Just extract the uniqueName element, compare it to the wanted id, and change the element:
        #!/usr/bin/perl use warnings; use strict; use XML::Twig; my $xmlFile = 'demo.xml'; my $t = XML::Twig->new( keep_atts_order => 1, pretty_print => 'indented_a', twig_handlers => { 'myconfig/myobj/myattributes/attribB' => \ +&getfoo, }, ); $t->parsefile($xmlFile); $t->print; sub getfoo { my ($t, $e) = @_; my $id = $e->parent->parent->first_child_text('uniqueName'); if ('objB' eq $id) { $e->set_text(' NEW '); } }

        For comparison, the same task in XML::XSH2:

        open demo.xml ; set /myconfig/myobj[uniqueName='objB']/myattributes/attribB 'NEW' ; save :b ;
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: XML::Twig correct parent, xpath filter
by Preceptor (Deacon) on Oct 30, 2015 at 16:16 UTC

    Can I just say how marvellous it is to see someone with use English; and then going on to use single letter variable names?

    But for the sake of being more helpful - to match the _content_ of an element, you probably want the "string()" function in your XPath:

    use strict; use warnings; use English; use XML::Twig; my $xmlFile = "demo.xml"; my $t = XML::Twig->new( pretty_print => 'indented_a', keep_atts_order => 1, twig_handlers => { q(myconfig/myobj[string(uniqueName)=" objA "]/myattributes/ +attribB) => \&getfoo, } ); $t->parse ( \*DATA ); $t->print; exit 0; sub getfoo { my ($t, $e) = @_; print "text=", $e->text(), "' tag='", $e->tag(), "' path='", $e->path(), "' parentTag='", $e->parent->tag(), "'\n"; } __DATA__ <myconfig> <myobj> <uniqueName> objA </uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> <myobj> <uniqueName> objB </uniqueName> <myattributes> <attribA> fooA </attribA> <attribB> fooB </attribB> </myattributes> </myobj> </myconfig>

    Although actually I'd probably be suggesting not using twig_handlers or roots unless you've a need to keep memory footprint down, and just use "get_xpath" and "children" etc. to process it after parsing

    foreach my $elt ( $t -> get_xpath( '//myconfig/myobj/uniqueName[string +()=" objA "]/../myattributes/attribB' ) ) { $elt -> print; }

    Or just iterate all the "myobjs" and extract differentiating elements.

      that is exactly what I was looking for. much easier than trying to decode and look at all the elements. the real XML is far more complicated and bigger. Sorry about the single letter variables. was just trying to keep the example short. real version is better :-) Thanks to both of you.