Namespaces and the default namespace trap
Why //book matches nothing in a document that looks perfectly fine, and how to register prefixes in every library you are likely to use.
The trap
<catalog xmlns="urn:example:catalog" xmlns:dc="http://purl.org/dc/elements/1.1/">
<book><dc:title>Pragmatic XPath</dc:title></book>
</catalog>xmllint --xpath 'count(//book)' catalog.xml
# XPath error: no result / returns nothing useful
xmllint --xpath 'count(//*[local-name()="book"])' catalog.xml
# 1 -- this works, because it ignores the namespace URIAn unprefixed name in XPath matches a node in no namespace. When the document declares a default namespace, every unprefixed element is in that namespace, so the name can never match. Attributes are the opposite: an unprefixed attribute is always in no namespace, even when a default element namespace is declared.
The second query works because local-name() throws the namespace away, but that is a blunt instrument. The correct fix is to bind a prefix yourself and use it in the expression.
Binding a prefix in each environment
| Environment | How a prefix is bound | Note |
|---|---|---|
| xmllint | --xpath cannot bind; use --shell with setns, or rewrite with local-name() | The shell mode is the practical option |
| xmlstarlet | -N p=urn:example:catalog on the command line | One -N flag per prefix |
| Browser | document.evaluate(expr, node, resolver, ...) | The resolver is a function from prefix to URI |
| Python lxml | namespaces={'p': 'urn:...'} | Returns elements, not strings, unless the expression ends in a function |
| Java | XPath.setNamespaceContext(...) | Also usable on a compiled XPathExpression |
| .NET | XmlNamespaceManager passed to SelectNodes | Prefixes are arbitrary; only the URI matters |
from lxml import etree
ns = {"c": "urn:example:catalog", "d": "http://purl.org/dc/elements/1.1/"}
doc = etree.parse("catalog.xml")
print(doc.xpath("//c:book/d:title/text()", namespaces=ns)) # ['Pragmatic XPath']// The prefix in the expression is resolved by your resolver, not by the document.
const resolver = (prefix) => (prefix === "c" ? "urn:example:catalog" : null);
const doc = new DOMParser().parseFromString(xmlString, "application/xml");
const it = doc.evaluate("//c:book/d:title/text()", doc, resolver,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0; i < it.snapshotLength; i++) console.log(it.snapshotItem(i).nodeValue);💡
The prefix letters are yours to choose and carry no meaning — only the URI matters. That is why a stylesheet can use
dc: while the source document used dcterms: and still select the same nodes, and why renaming the prefix in one file is always safe.FAQ
Can I change the default namespace to make my paths work?
No. XPath 1.0 has no default namespace for unprefixed names, and XPath 2.0 only honours one through a host-language default element namespace setting. Bind a prefix instead.
Why do attributes never need the namespace of their element?
Because namespaces belong to names, not to positions. An attribute with no prefix is in no namespace regardless of the enclosing element, so match it without a prefix.
Related
XPath in XSLT and in code The XPath data model: nodes, order and values
Last refreshed 2026-09-18.