tags:

views:

2333

answers:

2

I want to parse XML files using xPaths. After getting a node I may need to perform xPath searches on their parent nodes. My current code using XML::XPath is:

my $xp = XML::XPath->new(filename => $XMLPath);
# get all foo or foos node with a name
my $Foo = $xp->find('//foo[name] | //foos[name]');
if (!$Foo->isa('XML::XPath::NodeSet') || $Foo->size() == 0) {
    # no foo found
    return undef;
} else {
    # go over each and get its bar node
    foreach my $context ($Foo->get_nodelist) {
     my $FooName = $context->find('name')->string_value;
     $xp = XML::XPath->new( context => $context );
     my $Bar = $xp->getNodeText('bar');
     if ($Bar) {
      print "Got $FooName with $Bar\n";
     } else {
      # move up the tree to get data from parent
      my $parent = $context->getParentNode;
      print $parent->getNodeType,"\n\n";
     }
    }
}

My goal is to get a hash of foo elements names and their bar child nodes values, if a foo does not have a bar node it should get the one from its parent foo or foos node.

For this XML:

<root>
    <foos>
     <bar>GlobalBar</bar>
     <foo>
      <name>number1</name>
      <bar>bar1</bar>
     </foo>
     <foo>
      <name>number2</name>
     </foo>
    </foos>
</root>

I would expect:

number1->bar1 
number2->GlobalBar

When using the above code I get an error when trying to get parent node:

Can't call method "getNodeType" on an undefined value

Any help will be much appreciated!

+4  A: 

You will see that error when you try to call a method on undef. The most common reason for calling a method on undef is failure to check to see if the constructor method was successful. Change

$xp = XML::XPath->new( context => $context );

to be

$xp = XML::XPath->new( context => $context )
    or die "could not create object with args ( context => '$context' )";
Chas. Owens
Thanks for that answer but this is not the issue. I know that I get the error message for trying to call a method of undef, the question is what am I doing wrong - why can't i get the parent node? by the way, the constructor is OK, i used your code and got no message.
Dror
Offhand, I would say your problem is that you are destroying the original object by assigning to $xp a second time.
Chas. Owens
I am new to Perl, Can you provide a sample of the correct way to handle the task at hand?
Dror
+4  A: 

As Chas mentioned, you should not create a second XML::XPath object (the docs mention this too). You can either pass pass the context as the second parameter of the find* methods, or simply call the methods on the context node, as you in fact did to get $FooName.

You also have a few method calls that don't do what you think (getNodeType doesn't return the element name, but a number representing the node type).

Overall the updated code below seems to give you what you want:

#!/usr/bin/perl

use strict;
use warnings;

use XML::XPath;

my $xp = XML::XPath->new(filename => "$0.xml");
# get all foo or foos node with a name
my $Foo = $xp->find('//foo[name] | //foos[name]');
if (!$Foo->isa('XML::XPath::NodeSet') || $Foo->size() == 0) {
    # no foo found
    return undef;
} else {
    # go over each and get its bar node
    foreach my $context ($Foo->get_nodelist) {
        my $FooName = $context->find('name')->string_value;
        my $Bar = $xp->findvalue('bar', $context); # or $context->findvalue('bar');
        if ($Bar) {
                print "Got $FooName with $Bar\n";
        } else {
                # move up the tree to get data from parent
                my $parent = $context->getParentNode;
                print $parent->getName,"\n\n";
        }
    }
}

Finally, a word of warning: XML::XPath is not well maintained, and you would probably be better off using XML::LibXML instead. The code would be very similar.

mirod
Thank you - it works fine now. I'll look into XML:LibXML as you advised.
Dror