Home > Blockchain >  Why doesn't my XPath expression find a newly added node in an XML document?
Why doesn't my XPath expression find a newly added node in an XML document?

Time:10-14

I try to add a new node to an XML (svg) document, but when I try to query it afterwards with an XPath expression, it does not find the new node.

use strict;
use warnings;
use XML::LibXML;
use XML::LibXML::XPathContext;

my $parser = XML::LibXML->new();
my $svg = $parser->load_xml(string => <<'SVG');
<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<g inkscape:groupmode="layer" id="old-id" inkscape:label="old-label">...</g>
</svg>
SVG

my $layer = XML::LibXML::Element->new('g');     # same result with 'svg:g'
$layer->setAttribute('inkscape:groupmode', 'layer');
$layer->setAttribute('id', 'new-id');
$layer->setAttribute('inkscape:label', 'new-label');
$svg->documentElement()->appendChild($layer);

print "Dump:\n$svg\n";

print "Xpath:\n";
my $xpc = XML::LibXML::XPathContext->new($svg);
$xpc->registerNs('svg', 'http://www.w3.org/2000/svg');
my $xpath = '//svg:g';
foreach my $node ($xpc->findnodes($xpath)) {
    print $node->getAttribute('id'), ": ", $node->getAttribute('inkscape:label'), "\n";
}

This prints:

Dump:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<g inkscape:groupmode="layer" id="old-id" inkscape:label="old-label">initially</g>
<g inkscape:groupmode="layer" inkscape:label="new-label" id="new-id"/></svg>

Xpath:
old-id: old-label

The new node shows up when I dump the whole xml document, but the XPath expression only reports the old node.

Why is that and how do I get the XPath expression to also find the newly added node?

CodePudding user response:

While your print statement is outputting XML that, if reparsed, would give you the correct result, your in-memory DOM doesn't have awareness of the namespace of the new element. To tell the new element about the namespaces, we can use SetNamespace and SetAttributeNS per the docs

use strict;
use warnings;
use XML::LibXML;
use XML::LibXML::XPathContext;

my $parser = XML::LibXML->new();
my $svg = $parser->load_xml(string => <<'SVG');
<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<g inkscape:groupmode="layer" id="old-id" inkscape:label="old-label">...</g>
</svg>
SVG

my $layer = XML::LibXML::Element->new('g');     # same result with 'svg:g'
$layer->setNamespace('http://www.w3.org/2000/svg');
$layer->setNamespace('http://www.inkscape.org/namespaces/inkscape', 'inkscape', 0);
$layer->setAttributeNS('http://www.inkscape.org/namespaces/inkscape', 'groupmode', 'layer');
$layer->setAttribute('id', 'new-id');
$layer->setAttributeNS('http://www.inkscape.org/namespaces/inkscape', 'label', 'new-label');
$svg->documentElement()->appendChild($layer);

print "Dump:\n$svg\n";

print "Xpath:\n";
my $xpc = XML::LibXML::XPathContext->new($svg);
$xpc->registerNs('svg', 'http://www.w3.org/2000/svg');
my $xpath = '//svg:g';
foreach my $node ($xpc->findnodes($xpath)) {
    print $node->getAttribute('id'), ": ", $node->getAttribute('inkscape:label'), "\n";
}

Output

Dump:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<g inkscape:groupmode="layer" id="old-id" inkscape:label="old-label">...</g>
<g inkscape:groupmode="layer" id="new-id" inkscape:label="new-label"/></svg>

Xpath:
old-id: old-label
new-id: new-label
  • Related