perlxpathxml-twig

XML::Twig::XPath: `findnodes` still compains I should use `findnodes` instead of `get_xpath`


I wrote some Perl module that builds an XML::Twig tree. Unfortunately it seems that XML::Twig's XPath implementation is incomplete, so I loaded XML::Twig::XPath.

As the documentation for XML::Twig states that findnodes is the same as get_xpath (among several more), I was expecting that I could process a query like //info_string[text()] that the original XML::Twig did not like.

On the first run XML::Twig::XPath complained that I'll have to use getnodes instead of get_xpath, so I changed that.

Unfortunately even after that change, I still get the same message:

error in xpath expression //info_string[text()] at text()
the expression is a valid XPath statement, and you are using XML::Twig::XPath, but
you are using either 'find_nodes' or 'get_xpath' where the method you likely wanted
to use is 'findnodes', which is the only one that uses the full XPath engine
 at /usr/lib/perl5/vendor_perl/5.18.2/XML/Twig.pm line 3566.

I'm confused.

The statement in question looks like this:

return $self->_XML()->findnodes($XPath);

The _XML() method returns the XML::Twig tree, and findnodes is expected to query that tree.

I had also tried to replace use XML::Twig with use XML::Twig::XPath, but that didn't change the message, too.

Details

As the code to create the data structure and the XML from it is overly complex, I' just going to present the XML output ($self->_XML()->print) and the data structure (via Data::Dumper) here.

The XML to query looks like this:

<MonitoringOutput id="test-id" version="0.1">
  <description>This is a test output</description>
  <exit_code>0</exit_code>
  <status_string>OK</status_string>
  <info_string>(demo): Some interesting story</info_string>
  <perf_string>L1=0.49 L2=4.7s;;;0;7 L3=0.6;@0.2:0.3</perf_string>
  <perf_data count="3">
    <sample label="L3">
      <label>L3</label>
      <value>0.6</value>
      <thresholds>
        <warn end="0.3" inverted="1" start="0.2"/>
      </thresholds>
    </sample>
    <sample label="L1">
      <label>L1</label>
      <value>0.49</value>
    </sample>
    <sample label="L2">
      <label>L2</label>
      <value unit="s">4.7</value>
      <unit>s</unit>
      <range max="7" min="0"/>
    </sample>
    <warn_labels count="0"/>
    <crit_labels count="0"/>
  </perf_data>
</MonitoringOutput>

This is my attempt to make a reproducible minimal example:

#!/usr/bin/perl
use warnings;
use strict;

use XML::Twig;
use XML::Twig::XPath;

my $twig = XML::Twig->new();
$twig->parse('<MonitoringOutput id="test-id" version="0.1">
  <description>This is a test output</description>
  <exit_code>0</exit_code>
  <status_string>OK</status_string>
  <info_string>(demo): Some interesting story</info_string>
  <perf_string>L1=0.49 L2=4.7s;;;0;7 L3=0.6;@0.2:0.3</perf_string>
  <perf_data count="3">
    <sample label="L3">
      <label>L3</label>
      <value>0.6</value>
      <thresholds>
        <warn end="0.3" inverted="1" start="0.2"/>
      </thresholds>
    </sample>
    <sample label="L1">
      <label>L1</label>
      <value>0.49</value>
    </sample>
    <sample label="L2">
      <label>L2</label>
      <value unit="s">4.7</value>
      <unit>s</unit>
      <range max="7" min="0"/>
    </sample>
    <warn_labels count="0"/>
    <crit_labels count="0"/>
  </perf_data>
</MonitoringOutput>');
$twig->findnodes('//info_string[text()]');

When running it under the debugger (on a different machine with somewhat newer perl), I get this output:

error in xpath expression //info_string[text()] at text()
the expression is a valid XPath statement, and you are using XML::Twig::XPath, but
you are using either 'find_nodes' or 'get_xpath' where the method you likely wanted
to use is 'findnodes', which is the only one that uses the full XPath engine
 at /usr/lib/perl5/vendor_perl/5.26.1/XML/Twig.pm line 3685.
 at /usr/lib/perl5/vendor_perl/5.26.1/XML/Twig.pm line 7122.
    XML::Twig::Elt::_croak_and_doublecheck_xpath("//info_string[text()]", "error in xpath expression //info_string[text()] at text()") called at /usr/lib/perl5/vendor_perl/5.26.1/XML/Twig.pm line 7089
    XML::Twig::Elt::_install_xpath("//info_string[text()]") called at /usr/lib/perl5/vendor_perl/5.26.1/XML/Twig.pm line 7131
    XML::Twig::Elt::get_xpath(XML::Twig::Elt=HASH(0x5591017d3da0), "//info_string[text()]") called at /usr/lib/perl5/vendor_perl/5.26.1/XML/Twig.pm line 3685
    XML::Twig::get_xpath(XML::Twig=HASH(0x55910059bb00), "//info_string[text()]") called at /tmp/t.pl line 38

Solution

  • When building the XML tree with full XPath support, it must be built using XML::Twig::XPath->new and elements added must be created using XML::Twig::XPath::Elt->new to make findnodes work.

    As using XPath is kind of optional in my code, I decided to load either XML::Twig or XML::Twig::XPath dynamically, depending whether full XPath support is needed. In addition my code has to use XML::Twig::XPath::Elt instead of XML::Twig::Elt for full XPath support.

    So my code uses these tricks:

    my $xml_twig = $use_xpath ? 'XML::Twig::XPath' : 'XML::Twig';
    my $xml_twig_mod = $xml_twig . '.pm';
    $xml_twig_mod =~ s,::,/,g;
    require $xml_twig_mod;
    my $xml = $xml_twig->new(...);
    my $elt = "${xml_twig}::Elt"->new(...);
    

    See also https://stackoverflow.com/a/5790563/6607497