Search code examples
xmlperlxpathlibxml2xml-libxml

Unregister namespaces in scope of context node


I am using XML::LibXML. In creating an XPath context, I need to be able to specify exactly which namespaces are available. However, all of the namespaces in the scope of the context node are automatically registered with the XPathContext object. I need to unregister those, but I get an error when I try to unregister a namespace which is in the scope of the context node:

use XML::LibXML;
use XML::LibXML::XPathContext;

my $xml = <<'__EOI__';
<?xml version="1.0"?>
<myDoc id="myDocId">
    <body id="bodyId">
    <baz:par xmlns:baz="www.baz.com"
             xmlns:bar="www.bar.com">
        <bar:id>xyz123</bar:id>
    </baz:par>
    </body>
</myDoc>
__EOI__

my $parser = XML::LibXML->new();
my $doc = $parser->parse_string($xml);

my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerNs('baz', 'www.baz.com');

my $par = ${ $xpc->findnodes('//baz:par', $doc) }[0];

my $xpc2 = XML::LibXML::XPathContext->new($par);
$xpc2->unregisterNs('bar');

The above croaks XPathContext: cannot unregister namespace. Inspecting the source, I see that the error is printed from line 7618 of LibXML.xs. It is printed when the function xmlXPathRegisterNs returns -1. The only documentation for this function I can find is on xmlsoft.org. This documentation specifies that a -1 return value means that there was an error, but doesn't specify under what conditions an error occurs. I cannot for the life of me find the source for that method.

It may very well be that the XPath specification disallows this particular operation, but I am unable to determine that, either.

Can anyone tell me a) if there is a way to unregister namespaces in the scope of the context node using XML::LibXML::XPathContext or b) where there is documentation that this is not allowed in XPath?

EDIT

Joel showed me that unregistering a namespace only throws the given error if you haven't manually registered the namespace. However, unregistering still doesn't work right:

$xpc2->registerNs('bar', 'nothing'); #otherwise unregistering throws an error
$xpc2->unregisterNs('bar');
my @nodes = $xpc2->find('bar:id');
print scalar @nodes; #I want '0', but this prints '1'

Solution

  • This is a limitation of the Perl bindings. XML::LibXML always registers all namespaces in scope of the context node. All you can do is to rebind an existing prefix to another namespace like you did in the edit section of your question. If you remove the call to unregisterNS, it should do what you want:

    $xpc2->registerNs('bar', 'nothing'); # Rebind prefix
    my @nodes = $xpc2->findnodes('bar:id', $par);
    print scalar(@nodes), "\n"; # Prints 0