Building notes, projects, and occasional rants

XML::LibXML braindead, or is it just me?

I spent the last 30 minutes chasing down this bug. The following tests should all pass but the last one doesn't:

use strict;
use warnings;
use Test::More 'no_plan';

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

my $parser = XML::LibXML->new;
ok($parser, 'XML::LibXML parser created');

my $xml = '<x:me xmlns:x="some_namespace" />';
my $xdoc = $parser->parse_string($xml);
ok($xdoc, 'Valid XML parsed');

$xdoc = XML::LibXML::XPathContext->new($xdoc);
ok($xdoc, 'Converted to XPathContext');

my ($node) = $xdoc->findnodes('/me');
ok(!$node, 'Not found because no prefix means NULL namespace');

($node) = $xdoc->findnodes('/x:me');
ok($node, 'Found because using document prefix');

$xdoc->registerNs( new => 'some_namespace' );
($node) = $xdoc->findnodes('/new:me');
ok($node, 'Found because using new specific prefix');

# Notice the change of namespace
my $uri = $xdoc->lookupNs('x');
is($uri, 'some_namespace', 'x prefix is some_namespace');
$xdoc->registerNs( x => 'other_namespace' );
$uri = $xdoc->lookupNs('x');
is($uri, 'some_namespace', 'x prefix is still some_namespace');

($node) = $xdoc->findnodes('/x:me');
ok(!$node, 'Not found because using document prefix was changed to diff namespace');

The basic problem is this: you get a XML document in which a namespace N is tied to a prefix P. I was expecting that if I set in my own parser prefix P to something else, that my local prefix P would take precedence over the document P. Apparently it does not.

Right now, what I did was this: before using XPath to match anything, I always used registerNs method of XML::LibXML::XPathContext to make sure I was getting the right elements. Now, I use this new function instead:

sub _safe_ns_register {
  my ($xpc, $prefix, $ns) = @_;

  while ($xpc->lookupNs($prefix)) {
  $xpc->registerNs($prefix => $ns);

  return $prefix;

This function makes sure that the prefix I choose does not clash with any prefixes on the document. It returns the prefix I must use on my XPath expressions.

It solved my problem, but I still think that my local prefix should take precedence over the document one.