Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Using XPaths with XML::LibXML and XPathContext

by worik (Sexton)
on Jun 02, 2015 at 23:24 UTC ( [id://1128870]=perlquestion: print w/replies, xml ) Need Help??

worik has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to parse some simple XML in XML::LibXML and I have struck a problem. The domain is WebDAV.

A PROPFIND request from a user can send some very simple XML, but in a variety of semantically identical but syntactically different forms.

For a simple example:

<?xml version="1.0" encoding="utf-8" ?> <propfind xmlns="DAV:"> <propname/> </propfind>
<?xml version="1.0" encoding="utf-8" ?> <D:propfind xmlns:D="DAV:"> <D:propname/> </D:propfind>

TO get the 'propfind' node valid XPaths are

//propname
//DAV:propname
/propfind/propname
/DAV:propfind/DAV:propname

surely? But none of them work with XPathContext except in one special case (below) where there is no need for it anyway.

Below are my test code and the results. In it I register 'D' as a prefix for 'DAV:' and so using a XPath with 'D' as prefix works where the XML uses it too. But that is not good enough for where the XML uses 'DAV:' as a default namespace 'D' as a prefix should work too. Surely?

#!/usr/bin/perl -w use strict; use XML::LibXML; sub testfn { my $content = shift or die; my $xpath = shift or die; $|++; my $parser = XML::LibXML->new(); my $dom = $parser->parse_string($content); my @propfind = (); @propfind = $dom->findnodes($xpath); print ref($dom)."::findnodes('$xpath') (NOT XPathContext) ". scalar(@propfind)." nodes\n"; my $xc = XML::LibXML::XPathContext->new($dom); $xc->registerNs('D', 'DAV:'); @propfind = $xc->findnodes($xpath); print ref($xc)."::findnodes('$xpath') ".scalar(@propfind)." nodes\ +n"; } # Example from RFC4918 my $txt1 = '<?xml version="1.0" encoding="utf-8" ?> <D:propfind xmlns:D="DAV:"> <D:propname/> </D:propfind> '; my $txt2 = '<?xml version="1.0" encoding="utf-8" ?> <propfind xmlns="DAV:"> <propname/> </propfind> '; my $xpath1 = '/DAV:propfind/DAV:propname'; my $xpath2 = '/propfind/propname'; my $xpath3 = '/D:propfind/D:propname'; print "\$txt1\n".$txt1."\n"; print "\$txt1 \$xpath1 \n"; eval { &testfn($txt1, $xpath1); }; if($@){ print "Failed \$xpath $xpath1\n"; } print "\$txt1 \$xpath2 \n"; eval{ &testfn($txt1, $xpath2); }; if($@){ print "Failed \$xpath $xpath2\n"; } print "\$txt1 \$xpath3 \n"; eval{ &testfn($txt1, $xpath3); }; if($@){ print "Failed \$xpath $xpath3\n"; } print "\$txt2\n".$txt2."\n"; print "\$txt2 \$xpath1 \n"; eval{ &testfn($txt2, $xpath1); }; if($@){ print "Failed \$xpath $xpath1\n"; } print "\$txt2 \$xpath2 \n"; eval { &testfn($txt2, $xpath2); }; if($@){ print "Failed \$xpath $xpath2\n"; } print "\$txt2 \$xpath3 \n"; eval { &testfn($txt2, $xpath3); }; if($@){ print "Failed \$xpath $xpath3\n"; }

The results:

$txt1 <?xml version="1.0" encoding="utf-8" ?> <D:propfind xmlns:D="DAV:"> <D:propname/> </D:propfind> $txt1 $xpath1 Failed $xpath /DAV:propfind/DAV:propname $txt1 $xpath2 XML::LibXML::Document::findnodes('/propfind/propname') (NOT XPathConte +xt) 0 nodes XML::LibXML::XPathContext::findnodes('/propfind/propname') 0 nodes $txt1 $xpath3 XML::LibXML::Document::findnodes('/D:propfind/D:propname') (NOT XPathC +ontext) 1 nodes XML::LibXML::XPathContext::findnodes('/D:propfind/D:propname') 1 nodes $txt2 <?xml version="1.0" encoding="utf-8" ?> <propfind xmlns="DAV:"> <propname/> </propfind> $txt2 $xpath1 Failed $xpath /DAV:propfind/DAV:propname $txt2 $xpath2 XML::LibXML::Document::findnodes('/propfind/propname') (NOT XPathConte +xt) 0 nodes XML::LibXML::XPathContext::findnodes('/propfind/propname') 0 nodes $txt2 $xpath3 Failed $xpath /D:propfind/D:propname

Replies are listed 'Best First'.
Re: Using XPaths with XML::LibXML and XPathContext
by Anonymous Monk on Jun 03, 2015 at 00:40 UTC
    In it I register 'D' as a prefix for 'DAV:' and so using a XPath with 'D' as prefix works where the XML uses it too.

    No, the expression /D:propfind/D:propname works on both $txt1 and $txt2.

    Your sub testfn does not give you enough information. If $dom->findnodes($xpath) blows up, the second "test case" $xc->findnodes($xpath) never gets executed.

    /DAV:propfind/DAV:propname will not work because you have not registered a prefix "DAV" with the XPathContext.

    /propfind/propname will not work because that expression seeks nodes without a namespace, but the nodes in your document have a namespace.

    $dom->findnodes($xpath) will not always work because it lacks the power that XPathContext provides.

    Please also reference your previous thread xmlns and XML::LibXML, a lot has already been discussed there.

    There is further documentation in XML::LibXML::Node under findnodes, in XML::LibXML::XPathContext, and actually plenty of good information on the net in general: xpath namespace.

Re: Using XPaths with XML::LibXML and XPathContext
by Anonymous Monk on Jun 02, 2015 at 23:37 UTC

      I get it now. (I was getting confused between the namespace DAV: and the namespace DAV, duh!)

      Is it compulsory to register a namespace prefix? I can seem to find no way of using a fully qualified XPath. For a namespace NS calling registerNs(NS, NS) works. Is that the only way?

        I get it now. (I was getting confused between the namespace DAV: and the namespace DAV, duh!) Is it compulsory to register a namespace prefix? I can seem to find no way of using a fully qualified XPath. For a namespace NS calling registerNs(NS, NS) works. Is that the only way?

        If you want to use a prefix in xpath, a prefix that doesn't occur in the xml, you have to register it

        You don't have to use a prefix in xpath, just write longer xpath

        //*[ local-name() != 'propfind' and contains(local-name(), 'prop') and namespace-uri() = 'DAV:' ]

        It all works the same

      I get it now. (I was getting confused between the namespace DAV: and the namespace DAV, duh!)

      Is it compulsory to register a namespace prefix? I can seem to find no way of using a fully qualified XPath. For a namespace NS calling registerNs(NS, NS) works. Is that the only way?

        $xpc->registerNS($prefix, $ns) defines a prefix. (Not the only one, though.) You can't use a prefix you haven't defined. But it's possible to build XPaths that don't use prefixes using namespace-uri() and local-name().

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1128870]
Approved by kevbot
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (7)
As of 2024-04-16 06:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found