Subject: | findvalue() on a node from findnodes() returns surprising value |
First, let's say you've got a file shop.xml, which looks like this:
<?xml version="1.0" encoding="utf-8"?>
<shop>
<grocery>
<fruit name="cherry">
<color> red </color>
</fruit>
<fruit name="banana">
<color> yellow </color>
</fruit>
</grocery>
</shop>
I want to iterate over the fruit, printing out the color of each fruit in turn:
use File::Slurper qw/ read_text /;
use XML::XPath;
my $metadata = read_text($ARGV[0]);
my $xpath = XML::XPath->new( xml => $metadata);
for my $node ($xpath->findnodes('//shop/grocery/fruit')) {
my $name = $node->getAttribute('name');
print STDERR "\nNAME: $name\n";
my $color = $node->findvalue("//color");
print STDERR "COLOUR: $color\n";
}
This doesn't return the result I expect. Instead I get this:
NAME: cherry
COLOUR: red yellow
NAME: banana
COLOUR: red yellow
So it gets the node name right, but it's pulling in the content from all nodes, rather than the selected node.
The code for findvalue() XML::XPath::Node is this:
sub findvalue {
my $node = shift;
my ($path) = @_;
my $xp = XML::XPath->new();
return $xp->findvalue($path, $node);
}
If I change it to this:
sub findvalue
{
my ($node, $path) = @_;
my $xp = XML::XPath->new(xml => XML::XPath::XMLParser::as_string($node));
return $xp->findvalue($path);
}
Then my example produces the expected output:
NAME: cherry
COLOUR: red
NAME: banana
COLOUR: yellow
This is a naive fix though, as I haven't thought through the implications if you're using set_namespace().
This is a simple example distilled out of a problem I've hit with how Net::SAML2 is using XML::XPath.
I'll do some more investigation, but wanted to log this much, in case my understanding of XPath or XML::XPath is wrong.
Cheers,
Neil