Perl XML::LibXML $node->findnodes($xpath) finds nodes it shouldn't Perl XML::LibXML $node->findnodes($xpath) finds nodes it shouldn't xml xml

Perl XML::LibXML $node->findnodes($xpath) finds nodes it shouldn't


From the documentation of XPath 1.0

//para selects all the para descendants of the document root

(emphasis my own). So your call

$node->findnodes( '//Identifier' )

is ignoring the context node $node and searching for all Identifier elements anywhere in the document

To get all Identifier descendants of the context node you must add a dot, like this

$node->findnodes('.//Identifier');

but since $node is always a Response element and Identifier is a direct child of Response you can just write

$node->findnodes('Identifier');



You seem to have got yourself a little tied up writing this. I know you have cut the code down as an example, but do you really need the separate package? Much can be done with judicious application of XPath.

The most obvious change is that you don't need to loop through all children - you can simply pick out the ones you're interested in.

This refactored code may be worth reading

use strict;use warnings;use XML::LibXML;my $parser = XML::LibXML->new;my $doc    = $parser->parse_fh(*DATA);for my $item ( $doc->findnodes('//Item') ) {    print "\n";    my ($id) = $item->findvalue('Id');    printf "Item Id: %s\n", $item->findvalue('Id');    my @messages = $item->findnodes('Message');    for my $message (@messages) {        my ($response) = $message->findnodes('Response');        printf "Response Identifier: %s\n", $response->findvalue('Identifier');    }}__DATA__<Envelope>  <Body>    <Reply>      <List>        <Item>          <Id>8b9a</Id>          <Message>            <Response>              <Identifier>55D</Identifier>            </Response>          </Message>        </Item>        <Item>          <Id>5350</Id>          <Message>            <Response>              <Identifier>56D</Identifier>            </Response>          </Message>        </Item>      </List>    </Reply>  </Body></Envelope>

output

Item Id: 8b9aResponse Identifier: 55DItem Id: 5350Response Identifier: 56D


I have no comment on the quality of the code, but having learned to use XML::DOM before I used XML::LibXML I have a tendancy to use some of the DOM syntax. I have been trying to beat this habit out of me :).
The reason I mention this is because I see you have used the equivalent of ->item(0) to get the first position from a nodelist as you would in DOM.
XML::LibXML supports use of ->item() but from cpan I can see that xpath creates nodelists starting at 1 not 0 like DOM. I am pretty sure that if you leave your code as is and look for the 1st array position not the 0th, you will get the result you want.
What is not clear is why ->item(0) gives you the last result as it seems to do from my testing (is it perhaps offset from an array value so that you are in fact returned the -1th array value)