xmlperlxpathxml-twig

Xpath is not working with XML::Twig::XPath::Elt


I have following XML.

<Path>
  <To>
    <My>
      <Node key="11">
        <ChildA>1A</ChildA>
        <ChildB key="Key1">1B</ChildB>
        <ChildC>
            <ChildD>ValD</ChildD>
        </ChildC>
      </Node>
      <Node key="22">
        <ChildA>2A</ChildA>
        <ChildB key="Key2">2B</ChildB>
        <ChildC>
            <ChildD>ValD</ChildD>
        </ChildC>
      </Node>
    </My>
  </To>
</Path>

And I am trying to parse this xml using XML::Twig::XPath. Following is my code to parse it and extract element from it using XPath syntax.

use 5.010;
use strict;
use warnings FATAL => 'all';
use XML::Twig::XPath;  #Issue with this shit as this thing was up to data but XML::XPath was not installed. So I did cpanm XML::XPath
use XML::Twig;
use Data::Dumper;
my @xml;

$xml[0] = q|<Path>
  <To>
    <My>
      <Node key="11">
        <ChildA>1A</ChildA>
        <ChildB key="Key1">1B</ChildB>
        <ChildC>
            <ChildD>ValD</ChildD>
        </ChildC>
      </Node>
      <Node key="22">
        <ChildA>2A</ChildA>
        <ChildB key="Key2">2B</ChildB>
        <ChildC>
            <ChildD>ValD</ChildD>
        </ChildC>
      </Node>
    </My>
  </To>
</Path>|;

my $twig = XML::Twig::XPath->new(pretty_print => 'indented')->parse($xml[0]);

#Finding the whole Node, using xpath. This shit also works if you have Twig, not XPath.
my @constraint = $twig->findnodes('/Path/To/My/Node[@key="22"]');
say $constraint[0]->sprint;
say ref($constraint[0]);

my $child_key = $constraint[0]->find('//ChildB/@key');
say $child_key;

$constraint[0] rightfully prints the XML Node.

And I expect $child_key to be Key2 but it turns out to be Key1Key2

What I could be possibly doing wrong? If I do full XPath search it is giving me corrct output.


Solution

  • The //ChildB/@key xpath expression means that it starts at the root element and looks in any element for an element ChildB and the attribute @key.

    It's confusing that your twig $constraint[0] is not completely taken out of the tree. It's like a view on some part of the full document tree, so the // actually looks in the full tree. It starts at root of the document, not at the <Node key="22"> element.

    You need to tell your find to start the current element by using the ..

    #                                     V
    my $child_key = $constraint[0]->find('.//ChildB/@key');
    

    Now your output is only

    Key2