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