XPath nic nevrací: výchozí jmenný prostor

Dotaz //entry vrátí prázdný výsledek, i když element entry v dokumentu vidíš. Dokument má výchozí jmenný prostor (xmlns="…") a jméno bez prefixu v XPath 1.0 znamená element bez jmenného prostoru. Prefix musíš navázat na URI v kódu a použít ho v dotazu.

Autor: Aktualizováno

Co se děje

Atom feed, sitemap, XHTML, SOAP nebo ISDOC deklarují na kořeni výchozí jmenný prostor. Všechny elementy bez prefixu v něm pak leží, i když to v textu dokumentu není vidět:

feed.xml — elementy feed, entry, title i link jsou v http://www.w3.org/2005/Atom
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Novinky XML.cz</title>
  <entry>
    <title>JSON v PostgreSQL</title>
    <link href="https://xml.cz/programovani/postgresql/"/>
  </entry>
  <entry>
    <title>Billion laughs</title>
    <link href="https://xml.cz/xxe/"/>
  </entry>
</feed>

XPath ale nečte deklarace ze vstupního dokumentu. Podle XPath 1.0 má jméno bez prefixu ve výrazu prázdný jmenný prostor. Krok entry tedy hledá element s lokálním jménem entry a bez namespace. Takový v dokumentu není, a proto je výsledek prázdný. Nejde o chybu parseru ani syntaxe dotazu, žádná hláška se neobjeví.

Chybně a správně

Chybně — vrátí prázdný výsledek
//entry/title
Správně — prefix navázaný v kódu na URI
//atom:entry/atom:title      (prefix atom → http://www.w3.org/2005/Atom)

Prefix ve výrazu nemusí odpovídat prefixu v dokumentu, rozhoduje jen URI. Dokument žádný prefix nemá, a přesto ho dotaz potřebuje. Atribut href zůstává bez prefixu (atom:link/@href), protože výchozí jmenný prostor se na atributy nevztahuje.

Registrace prefixu v jednotlivých jazycích

Všechny ukázky pracují se souborem feed.xml výše a vypíšou „JSON v PostgreSQL“ a „Billion laughs“.

JavaScript v prohlížeči

Chrome, document.evaluate
const doc = new DOMParser().parseFromString(xml, "application/xml");
const resolver = prefix => ({ atom: "http://www.w3.org/2005/Atom" })[prefix] ?? null;

const r = doc.evaluate("//atom:entry/atom:title", doc, resolver,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0; i < r.snapshotLength; i++) console.log(r.snapshotItem(i).textContent);

doc.evaluate("//atom:entry", doc, null, XPathResult.ANY_TYPE, null);
// NamespaceError: … '//atom:entry' contains unresolvable namespaces.

C# / .NET

.NET 10
var doc = new XmlDocument();
doc.Load("feed.xml");
doc.SelectNodes("//entry/title")!.Count;                 // 0

var nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("atom", "http://www.w3.org/2005/Atom");
foreach (XmlNode t in doc.SelectNodes("//atom:entry/atom:title", nsm)!)
    Console.WriteLine(t.InnerText);

// LINQ to XML bez XPath: jméno = XNamespace + lokální jméno
XNamespace a = "http://www.w3.org/2005/Atom";
var hrefs = XDocument.Load("feed.xml")
    .Descendants(a + "entry").Elements(a + "link").Attributes("href");

Java

JDK 25, JAXP
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);           // výchozí je false
Document doc = dbf.newDocumentBuilder().parse(new File("feed.xml"));

XPath xp = XPathFactory.newInstance().newXPath();
xp.setNamespaceContext(new NamespaceContext() {
  public String getNamespaceURI(String prefix) {
    return "atom".equals(prefix) ? "http://www.w3.org/2005/Atom" : XMLConstants.NULL_NS_URI;
  }
  public String getPrefix(String uri) { return null; }
  public Iterator<String> getPrefixes(String uri) { return null; }
});
NodeList tituly = (NodeList) xp.evaluate("//atom:entry/atom:title", doc, XPathConstants.NODESET);

Výchozí DocumentBuilderFactory není namespace-aware. Pak //entry/title dva elementy najde, ale prefixový dotaz fungovat nebude. Kód, který „funguje bez prefixu“, se rozbije, jakmile někdo parser správně nastaví. Namespace-aware parser proto zapínej vždy.

Python

Python 3.14, xml.etree.ElementTree
import xml.etree.ElementTree as ET   # cizí vstup: defusedxml.ElementTree, stejné API

strom = ET.parse("feed.xml")
strom.findall(".//entry/title")                                   # []

ns = {"atom": "http://www.w3.org/2005/Atom"}
for t in strom.findall(".//atom:entry/atom:title", ns):
    print(t.text)

# Python 3.8+: klíč "" nastaví výchozí namespace pro jména bez prefixu
strom.findall(".//entry/title", {"": "http://www.w3.org/2005/Atom"})

# lxml (plné XPath 1.0): root.xpath("//atom:entry/atom:title", namespaces=ns)

PHP

PHP 8.4, DOM a SimpleXML
$doc = new DOMDocument();
$doc->load('feed.xml');
$xp = new DOMXPath($doc);
$xp->query('//entry/title')->length;                       // 0

$xp->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
foreach ($xp->query('//atom:entry/atom:title') as $t) echo $t->textContent, "\n";

$sx = simplexml_load_file('feed.xml');
$sx->registerXPathNamespace('a', 'http://www.w3.org/2005/Atom');
foreach ($sx->xpath('//a:entry/a:link/@href') as $href) echo $href, "\n";

PostgreSQL

PostgreSQL 18
SELECT xpath('//a:entry/a:title/text()', doc,
             ARRAY[ARRAY['a', 'http://www.w3.org/2005/Atom']])
FROM feed;

Více o XML v databázi popisuje kapitola JSON a XML v PostgreSQL.

Bez registrace prefixu

XPath 2.0 a novější mají zápisy, které prefix nepotřebují. Všechny tři vrátí oba titulky:

XPath 2.0+: libovolný namespace
//*:entry/*:title
XPath 3.0+: URI přímo ve jménu (EQName)
//Q{http://www.w3.org/2005/Atom}entry/Q{http://www.w3.org/2005/Atom}title
XPath 1.0 i novější: jen lokální jméno
//*[local-name()='entry']/*[local-name()='title']

*:entry a local-name() namespace ignorují. Vyberou i cizí element stejného jména, třeba entry z rozšíření feedu. Hodí se pro diagnostiku a rychlý dotaz, do produkčního kódu patří prefix nebo Q{URI}. Podmínka [local-name()='entry' and namespace-uri()='http://www.w3.org/2005/Atom'] je přesná i v XPath 1.0, jen dlouhá.

Procesory XPath 2.0+ mohou mít nastavený výchozí elementový namespace, se kterým //entry najde elementy i bez prefixu. Je to nastavení procesoru, ne vlastnost dokumentu. Tester na XML.cz ho v režimu XPath 3.1 přebírá z kořene dokumentu, takže tam //entry/title výsledek vrátí. V režimu XPath 1.0 (prohlížeč) nevrátí nic.

Atribut bez prefixu
//*:entry/*:link/@href

Jak chybu poznat

  • Podívej se na kořen dokumentu. Atribut xmlns="…" bez dvojtečky znamená výchozí jmenný prostor.
  • Vyzkoušej //*[local-name()='entry']. Pokud najde výsledky a //entry ne, jde o tento problém.
  • Vypiš namespace-uri() nalezeného elementu a porovnej ho znak po znaku s URI v kódu. Liší se i koncové lomítko, http a https nebo velikost písmen.
  • Pokud prefixový dotaz hlásí nevyřešený prefix (NamespaceError, XPST0081, Namespace prefix … is not defined), registrace v kódu chybí nebo se nepředala do volání.
Prostředí//entry/title bez prefixuŘešení
Prohlížeč, document.evaluate (XPath 1.0)nicresolver prefixů (funkce) jako 3. argument
.NET, XmlDocument.SelectNodesnicXmlNamespaceManager; v LINQ to XML XNamespace
Java JAXP, setNamespaceAware(true)nicXPath.setNamespaceContext
Java JAXP, výchozí factorynajde (parser namespace ignoruje)zapni setNamespaceAware(true), jinak se chování změní později
Python ElementTreenicslovník namespaces; klíč "" od Pythonu 3.8
PHP DOMXPath / SimpleXMLnicregisterNamespace / registerXPathNamespace
PostgreSQL xpath()nicpole dvojic prefix–URI jako 3. argument
XPath 2.0+ (Saxon, BaseX…)podle nastavení výchozího elementového namespaceprefix, *:entry nebo Q{URI}entry

Neodstraňuj jmenný prostor z dokumentu

Častá rada „smaž xmlns a dotaz začne fungovat“ mění data. Elementy pak mají jiné rozšířené jméno, dokument přestane odpovídat schématu a mohou splynout elementy z různých slovníků. Kdy je to přesto nutné, popisuje návod odstranění jmenného prostoru.