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 URI

An 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

EnvironmentHow a prefix is boundNote
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 lineOne -N flag per prefix
Browserdocument.evaluate(expr, node, resolver, ...)The resolver is a function from prefix to URI
Python lxmlnamespaces={'p': 'urn:...'}Returns elements, not strings, unless the expression ends in a function
JavaXPath.setNamespaceContext(...)Also usable on a compiled XPathExpression
.NETXmlNamespaceManager passed to SelectNodesPrefixes 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.

XPath in XSLT and in code The XPath data model: nodes, order and values

Last refreshed 2026-09-18.