XML namespaces: what xmlns does and why identical tags can differ
Two XML documents can both contain a <title> element and mean completely different things: one a book title, one an HTML page title. Namespaces exist to tell them apart. A namespace attaches a unique identifier to an element or attribute so that names from different vocabularies can coexist in one document without colliding.
What xmlns actually declares
The xmlns attribute binds a prefix (or no prefix) to a namespace URI. The URI is just an identifier, a globally unique string. It does not have to point at anything and the parser never fetches it. Its only job is to be unique.
<book xmlns:bk="https://example.com/book">
<bk:title>Dune</bk:title>
</book>
Here bk is bound to the URI https://example.com/book. The element <bk:title> has the local name title and belongs to that namespace. The combination of namespace URI plus local name is called the expanded name, and it is what the parser actually compares. The prefix itself is throwaway, which is the single most important fact about namespaces.
Prefixes are not the identity
Because the identity is the URI, not the prefix, two documents using different prefixes for the same URI describe the same elements:
<bk:title xmlns:bk="https://example.com/book"/>
<x:title xmlns:x="https://example.com/book"/>
These are identical to a namespace aware parser. Both expand to {https://example.com/book}title. Conversely, the same prefix bound to different URIs in different places refers to different elements. Never compare prefixes to decide if two elements are "the same". Compare expanded names. This is where hand written XPath and string matching go wrong: they match on the prefix text, which the author could have chosen freely.
Default namespaces
A declaration with no prefix, xmlns="...", sets the default namespace. Every unprefixed element inside that scope belongs to it:
<catalog xmlns="https://example.com/book">
<title>Dune</title> <!-- belongs to the book namespace -->
</catalog>
Here <title> is in the book namespace even though it has no prefix, because the default applies. Two subtle rules follow:
- The default namespace applies to elements only, never attributes. An unprefixed attribute is always in no namespace, regardless of any default declaration in scope. This surprises almost everyone.
- Namespaces are scoped to the subtree where they are declared. A default declared on a child overrides the one from the parent for that subtree.
Why identical looking tags differ
Put the two ideas together and you get the headline confusion. These two <title> tags look the same but are different elements:
<doc>
<book xmlns="https://example.com/book"><title>Dune</title></book>
<page xmlns="https://example.com/html"><title>Home</title></page>
</doc>
The first title is {.../book}title, the second is {.../html}title. A query for one must not match the other. The text on screen is identical; the expanded names are not.
The table to keep in mind
| Written as | Expanded name | Namespace comes from |
|---|---|---|
<bk:title> with xmlns:bk="U" | {U}title | The bk prefix binding |
<title> under xmlns="U" | {U}title | The default namespace |
<title> with no default in scope | title, no namespace | Nothing |
attribute id="x" under xmlns="U" | id, no namespace | Defaults never apply to attributes |
The practical advice
When you query namespaced XML with XPath, bind your own prefix to the target URI in your query engine and use that; do not rely on the prefixes in the document. When you compare or validate, think in expanded names. And when a selector mysteriously matches nothing, the cause is almost always a default namespace you forgot the document declares, so your unprefixed query is looking in no namespace while the elements are all in one.
To see an XML document's structure and nesting clearly before you reason about its namespaces, run it through the XML Formatter, which indents it locally in your browser.