Registered namespace prefixes are ignored when evaluating an XPath expression if node name contains multiple colons (behavior change introduced by a873191c)
Given the following example XML file:
<root xmlns:A="https://example.com">
<A:B:C>text</A:B:C>
</root>
Before commit a873191c (which introduced xmlParseQNameHashed
), it was possible to select the element node <A:B:C>
with the following XPath expression (provided that A
was registered as a namespace prefix first):
/root[1]/A:*[local-name() = "B:C"]
After commit a873191c, the XPath expression above no longer works as expected (i.e. it returns an empty nodeset).
Reproduction Code
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <string>
#include <iostream>
xmlChar* make_xml_char(const std::string& str) {
return xmlStrdup(reinterpret_cast<const xmlChar*>(str.c_str()));
}
void print_xpath_num_nodes(xmlXPathObjectPtr obj) {
std::cout << "Number of nodes returned: " << std::to_string(obj->nodesetval->nodeNr) << std::endl;
}
void execute_xpath_expr(const std::string& expr, xmlXPathContextPtr xpath_ctxt) {
std::cout << "Executing XPath: " << expr << std::endl;
xmlChar* xpath_expr = make_xml_char(expr);
xmlXPathObjectPtr obj = xmlXPathEval(xpath_expr, xpath_ctxt);
print_xpath_num_nodes(obj);
xmlXPathFreeObject(obj);
xmlFree(xpath_expr);
}
int main(int argc, char *argv[]) {
const std::string xml = "<root xmlns:A=\"https://example.com\"><A:B:C>text</A:B:C></root>";
// Parse XML
xmlParserCtxt* parser_ctxt = xmlNewParserCtxt();
xmlDoc* doc = xmlCtxtReadMemory(parser_ctxt, xml.data(), static_cast<int>(xml.size()),
NULL, NULL, XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
// Register Namespaces
xmlChar* prefix = make_xml_char("A");
xmlChar* uri = make_xml_char("https://example.com");
xmlXPathContext* xpath_ctxt = xmlXPathNewContext(doc);
xmlXPathRegisterNs(xpath_ctxt, prefix, uri);
// Evaluate XPath expression (zero nodes returned after a873191c)
execute_xpath_expr("/root[1]/A:*[local-name() = \"B:C\"]", xpath_ctxt);
// Free resources
xmlFree(prefix);
xmlFree(uri);
xmlFreeParserCtxt(parser_ctxt);
xmlXPathFreeContext(xpath_ctxt);
xmlFreeDoc(doc);
}
Behavior Comparison
Included below is a comparison of the behavior of the reproduction code included above, before and after a873191c.
Before a873191c:
Executing XPath: /root[1]/A:*[local-name() = "B:C"]
Number of nodes: 1
After a873191c:
Executing XPath: /root[1]/A:*[local-name() = "B:C"]
Number of nodes returned: 0
Workaround
As a workaround, you can select the node <A:B:C>
via the following XPath, instead:
/root[1]/*[local-name() = "A:B:C"]
Notes
- When stepping through the reproduction code included above with a debugger, I noticed that the value of the
ns
property of thexmlNode
that gets created under the hood isNULL
. Before a873191c, thens
property was not set toA
, as expected. - It's not immediately clear whether this should be considered a bug or whether it is an intended behavior change since having multiple colons in an element name seems like an edge case.