Jak funguje XML

Co se děje, když program načte XML soubor: jak parser text rozloží, jak z něj vznikne strom, proč i jediná chyba zastaví zpracování, jaká pravidla platí pro názvy, konce řádků a bílé znaky a jaký je rozdíl mezi dobře formovaným a validním dokumentem.

Autor: Aktualizováno

Parser: od textu ke struktuře

XML soubor je pro počítač nejdřív jen posloupnost znaků. Program, který z ní vytáhne strukturu, se jmenuje XML parser (česky se říká i analyzátor nebo „parsovač“). Parser čte text zleva doprava a rozpoznává v něm otevírací a uzavírací značky, atributy, text, komentáře a další prvky.

Hotové parsery jsou součástí všech běžných platforem: v prohlížeči je to DOMParser, v Javě JAXP, v .NET System.Xml, v Pythonu modul xml.etree nebo knihovna lxml, v PHP rozšíření DOM a SimpleXML. Mnoho z nich stojí na knihovně libxml2, kterou používá i nástroj xmllint. Vlastní parser psát nemusíš a neměl bys — XML má řadu okrajových pravidel, na která se snadno zapomene.

Dokument je strom

Výsledkem parsování je strom uzlů. Kořenový element je kmen, vnořené elementy větve a text s atributy listy. Tenhle dokument:

<objednavka cislo="2026-117">
  <zakaznik>Jana Nováková</zakaznik>
  <polozka mnozstvi="2">Hrnek</polozka>
</objednavka>

parser převede na strom:

Dokument
└─ element objednavka
   ├─ atribut cislo = "2026-117"
   ├─ element zakaznik
   │  └─ text "Jana Nováková"
   └─ element polozka
      ├─ atribut mnozstvi = "2"
      └─ text "Hrnek"

Nad stromem pak pracují další technologie: XPath v něm vyhledává uzly, XSLT ho přetváří do jiné podoby a XSD kontroluje, jestli má správný tvar. Pořadí elementů je součástí dat a parser ho vždy zachová; pořadí atributů naopak význam nemá.

Dobře formovaný dokument (well-formed)

Specifikace XML je v jedné věci nekompromisní: pokud dokument poruší pravidla zápisu, jde o fatální chybu a parser nesmí pokračovat v běžném zpracování. Na rozdíl od HTML, kde prohlížeč chyby tiše opraví, XML parser nic nedomýšlí. To však neznamená, že všechna parserová API vrátí totožný model: mohou se lišit v načítání externí DTD, zpracování výchozích atributů nebo uchování komentářů. Pravidla pro validující a nevalidující procesory popisuje XML 1.0, oddíl 5.

Dokument, který pravidla splňuje, se nazývá dobře formovaný. Musí mimo jiné platit:

  • existuje právě jeden kořenový element,
  • každá otevírací značka má uzavírací (nebo je zapsaná jako prázdná <br/>),
  • značky se nekříží a velikost písmen v otevírací i uzavírací značce se shoduje,
  • hodnoty atributů jsou v uvozovkách a stejný atribut se v jednom elementu neopakuje,
  • znaky < a & se v textu zapisují jako entity.

Tenhle dokument obsahuje tři chyby:

Chybně
<objednavka>
  <polozka>Hrnek</Polozka>
  <cena mena=CZK>199</cena>
  <poznamka>Barva: černá & bílá</poznamka>
</objednavka>

Uzavírací značka </Polozka> má velké P, hodnota CZK není v uvozovkách a znak & není zapsaný jako entita. Opravená verze:

Správně
<objednavka>
  <polozka>Hrnek</polozka>
  <cena mena="CZK">199</cena>
  <poznamka>Barva: černá &amp; bílá</poznamka>
</objednavka>

Jak smí vypadat názvy elementů a atributů

Specifikace XML je k názvům velkorysejší, než se čeká. Název musí začínat písmenem, podtržítkem nebo dvojtečkou, dál smí obsahovat i číslice, pomlčku a tečku. „Písmeno“ přitom znamená jakékoli písmeno z Unicode, takže <položka> je stejně platná jako <polozka>. Dvojtečku ale nech jmenným prostorům — odděluje prefix od lokálního jména.

NázevPlatný?Proč
<cena_s_DPH>anopísmena, číslice za prvním znakem a podtržítko jsou v pořádku
<položka>anopísmena s diakritikou specifikace povoluje
<datum-vystaveni>anopomlčka a tečka smí být kdekoli kromě začátku
<2polozka>nenázev nesmí začínat číslicí
<-polozka>neani pomlčkou nebo tečkou
<cena s DPH>nemezera název ukončí a „s“ parser čte jako atribut bez hodnoty (hláška Specification mandates value for attribute)
<xmlData>raději nenázvy začínající na xml (v jakékoli velikosti písmen) jsou vyhrazené pro specifikaci

Diakritika v názvech je sice platná, ale v praxi ji většina formátů nepoužívá: některé starší nástroje a knihovny si s ní neporadí a jinde komplikuje psaní XPath výrazů. Pro nový formát zvol názvy bez diakritiky a drž jeden styl (datumVystaveni, nebo datum-vystaveni, ne obojí).

Instrukce pro zpracování

Kromě elementů, textu a komentářů může dokument obsahovat instrukce pro zpracování (processing instructions). Zapisují se mezi <? a ?> a nesou pokyn pro konkrétní aplikaci. První slovo je cíl, tedy komu je instrukce určená; zbytek je libovolný text, kterému rozumí jen ta aplikace.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="faktura.css"?>
<faktura>
  <?tisk strana="nova"?>
  <cislo>FV2026-042</cislo>
</faktura>

Nejznámější je <?xml-stylesheet?>, který prohlížeči říká, jakým stylem má dokument zobrazit. Instrukce <?tisk?> v ukázce je vymyšlená: parser ji jen předá dál a aplikace, která cíl tisk nezná, ji ignoruje. Cíl přesně xml v libovolné kombinaci velkých a malých písmen je vyhrazený. Předpona xml je pro další standardy rezervovaná; vlastní instrukci pojmenuj jinak.

Deklarace není instrukce: úvodní <?xml version="1.0"?> vypadá stejně, ale je to deklarace XML. Smí být jen úplně na začátku souboru — ani mezera nebo prázdný řádek před ní nejsou povolené. Hláška XML declaration allowed only at the start of the document znamená právě tohle.

Časté chybové hlášky a co znamenají

Parser obvykle vypíše číslo řádku a sloupce, kde chybu objevil. Skutečná příčina ale často leží o kus dřív — například chybějící uzavírací značka se projeví až na konci nadřazeného elementu. Většina nástrojů (xmllint, PHP, Python lxml, starší Chrome) používá knihovnu libxml2. Chrome od verze 151 čte XML vlastním parserem napsaným v Rustu a hlášky formuluje jinak:

Hláška libxml2Hláška Chrome 151+Co znamená
Opening and ending tag mismatchUnexpected closing taguzavírací značka neodpovídá otevírací — překlep, jiná velikost písmen nebo křížení značek
Extra content at the end of the documentUnexpected token: <za kořenovým elementem je další obsah, typicky dva kořenové elementy za sebou
Premature end of data in tagUnexpected end of stream: still inside the root elementsoubor skončil dřív, než se uzavřely všechny značky (často useknutý soubor)
EntityRef: expecting ';'Unexpected token inside entityv textu je samotný znak & — musí se zapsat jako &amp;
StartTag: invalid element nameUnexpected token inside qualified nameza znakem < není platný název elementu, např. neescapované < v textu
AttValue: " or ' expectedUnexpected tokenhodnota atributu není v uvozovkách
Document is emptyUnexpected end of stream: no root element foundsoubor je prázdný nebo obsahuje jen bílé znaky

Firefox používá jiný parser a hlášky formuluje jinak, například mismatched tag nebo junk after document element; příčiny jsou ale stejné.

Znění hlášek v Javě, PHP, Pythonu, .NET a prohlížečích, ukázky chybného a opraveného dokumentu a opravy v kódu najdeš v přehledu chybových hlášek XML parserů.

Validní dokument: dobře formovaný a navíc podle schématu

Dobře formovaný dokument má správný zápis, ale může obsahovat nesmysly — třeba objednávku bez zákazníka nebo cenu „levně“. Proto se k formátům píše schéma, které popisuje, jaké elementy a atributy smí dokument obsahovat, v jakém pořadí a s jakými hodnotami.

Dokument, který schématu odpovídá, je validní. Každý validní dokument je zároveň dobře formovaný, opačně to neplatí. Specifikace XML přitom slovo „validní“ používá jen pro shodu s DTD; u XSD nebo RELAX NG se přesněji mluví o validitě vůči schématu:

PojemCo kontrolujePodle čeho
dobře formovaný (well-formed)syntaxi: značky, kořen, uvozovky, entityXML 1.0
validní (valid)strukturu podle deklarací v DOCTYPEDTD
validní vůči schématu (schema-valid)strukturu, datové typy a omezení hodnotXSD, RELAX NG, Schematron

Nejčastější jazyky schémat:

  • DTD — nejstarší, součást samotné specifikace XML; umí strukturu, ale ne datové typy.
  • XML Schema (XSD) — dnes nejrozšířenější; datové typy, omezení hodnot, podpora jmenných prostorů.
  • RELAX NG a Schematron — alternativy pro specifické potřeby (RELAX NG je stručnější, Schematron kontroluje pravidla zapsaná pomocí XPath).

Pozor na terminologii: „validátor XML“ v běžné řeči často znamená jen kontrolu dobré formovanosti. Skutečná validace vyžaduje schéma.

Kódování znaků a entity

Parser musí vědět, jak z bajtů v souboru poskládat znaky. Informaci mu dává XML deklarace <?xml version="1.0" encoding="UTF-8"?>, případně značka BOM na začátku souboru. Bez deklarace a bez BOM se předpokládá UTF-8. Každý parser musí umět UTF-8 i UTF-16; pro češtinu je dnes jedinou rozumnou volbou UTF-8. Starší systémy ještě používají windows-1250 nebo ISO-8859-2 — deklarace pak musí odpovídat skutečnému kódování souboru, jinak se místo diakritiky objeví nesmyslné znaky nebo parser skončí chybou.

Pět znaků se v textu zapisuje pomocí předdefinovaných entit: &lt; (<), &gt; (>), &amp; (&), &quot; (") a &apos; ('). Povinné jsou hlavně první a třetí; uvozovky jen uvnitř hodnoty atributu ohraničené stejným typem uvozovek. Libovolný znak lze zapsat i číselným odkazem, např. &#169; nebo &#xA9; pro ©.

Pozor: HTML entity jako &nbsp; nebo &copy; v XML neexistují, dokud je nedefinuje DTD. Místo nich použij přímo znak nebo číselný odkaz (&#160; pro nezlomitelnou mezeru).

Číselný odkaz se zapisuje desítkově (&#233;) nebo šestnáctkově (&#xE9;), obojí je é. Odkazovat jde jen na znaky, které XML 1.0 vůbec připouští: tabulátor, konec řádku, návrat vozíku a běžné znaky od mezery výš. Řídicí znaky jako &#0; nebo &#27; jsou chyba i jako číselný odkaz. Když je potřebuješ přenést (třeba binární data), zakóduj je do Base64.

Co znamená standalone="yes"

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

Třetí údaj deklarace říká, jestli dokument závisí na externích deklaracích — typicky na externím DTD, které doplňuje výchozí hodnoty atributů nebo definuje entity. Hodnota yes slibuje, že nic takového není a dokument se přečte stejně i bez externích souborů. Bez externího DTD na údaji nezáleží a klidně ho vynech; výchozí hodnota je no.

Konce řádků a bílé znaky

Soubory z Windows končí řádky dvojicí znaků CR LF, z Linuxu a macOS jen LF. Parser rozdíly srovná ještě před čtením: každé CR LF i samostatné CR převede na LF. Aplikace tak vždy dostane stejný text bez ohledu na to, kde soubor vznikl. Znak CR se do dat dostane jen jako číselný odkaz &#13;.

V hodnotách atributů jde parser dál: každý tabulátor a konec řádku nahradí mezerou. Víceřádkový text proto do atributu nepatří — patří do elementu.

<!-- v souboru -->
<poznamka text="první řádek
druhý řádek	a tabulátor"/>

<!-- hodnota atributu, jak ji předá parser -->
první řádek druhý řádek a tabulátor

Pokud atribut v DTD nebo schématu není deklarovaný jako obyčejný text, parser navíc ořízne mezery na začátku a na konci hodnoty a vícenásobné mezery sloučí do jedné.

Bílé znaky v obsahu elementů parser naopak nemění — odsazení mezi značkami dostane aplikace jako textové uzly. Jestli je zahodí, rozhoduje ona. Atribut xml:space="preserve" jí může naznačit, že v daném elementu na mezerách záleží (například u ukázky kódu). Atribut patří do vyhrazeného jmenného prostoru xml, který se nedeklaruje, viz kapitola Jmenné prostory. Proto také formátovač mění jen odsazení mezi značkami a text uvnitř elementů nechává být.

DOM, nebo streamování (SAX, StAX)?

Programové rozhraní k parseru bývá dvojího druhu. DOM načte celý dokument do stromu v paměti a nabídne ho k libovolnému procházení a úpravám. SAX (a jeho „pull“ varianta StAX) dokument nestaví; posílá programu události typu „začal element“, „text“, „skončil element“ a program si sám drží, co potřebuje.

VlastnostDOMSAX / StAX (streamování)
Jak čte dokumentcelý najednou do stromu v pamětipostupně, událost po události
Paměťroste s velikostí dokumentutéměř konstantní
Přístup k datůmlibovolný, lze se vracetjen dopředu, jedním průchodem
Úpravy dokumentusnadné, strom lze změnit a uložitnení určeno k úpravám
Kdy použítkonfigurace, menší dokumenty, XPath/XSLTvelké exporty, logy, datové proudy

DOM je pohodlnější a stačí na většinu dokumentů do desítek megabajtů. Pro gigabajtové exporty, kde by strom v paměti zabral mnohonásobek velikosti souboru, se vyplatí streamování.

Bajty, BOM, deklarace a HTTP

Parser nad bajty může podle prvních bajtů rozlišit například UTF-8 a UTF-16 a potom přečíst deklaraci. XML 1.0 vyžaduje podporu obou; příloha F ukazuje vzory autodetekce. UTF-16 v XML má pravidla pro BOM, proto nezaměňuj obecný UTF-16 se zvlášť označeným UTF-16LE či UTF-16BE.

Při přenosu jako application/xml se uplatňuje také RFC 7303: BOM je autoritativní, bez BOM použije příjemce uvedený charset; bez obojího platí pravidla XML. Odesílatel musí zajistit, aby skutečné kódování souboru odpovídalo deklaraci i HTTP hlavičce. Při převodu do jiného kódování uprav deklaraci i HTTP hlavičku.

Pokud předáš parseru už dekódovaný řetězec, část rozhodování proběhla dřív. Změna encoding v textu neopraví bajty špatně převedené na znaky. Pro diagnostiku uchovej původní soubor, prohlédni první bajty a porovnej je s HTTP Content-Type a deklarací.

CDATA a zakázané ukončení

Text bez escapování značek
<text><![CDATA[a < b && c > d]]></text>

CDATA je alternativní zápis textového obsahu, nikoli jiný datový typ. Nelze ji vnořovat a uvnitř nesmí být posloupnost ]]>, která ji ukončí. Pokud musí být v textu právě tato posloupnost, rozděl sekce nebo použij běžný text s escapováním.

Text a]]>b ve dvou sekcích
<text><![CDATA[a]]]]><![CDATA[>b]]></text>

Uvnitř CDATA se nerozvíjejí entity ani číselné odkazy; doslovné &lt; zůstane textem těchto znaků. Pravidla povolených znaků a normalizace konců řádků platí i zde.

Instrukce pro zpracování a zápis XML

Deklarace, instrukce, komentář a element jsou různé konstrukce
<?xml version="1.0" encoding="UTF-8"?>
<?aplikace rezim="nahled"?>
<zprava><!-- poznámka --><text>A &amp; B</text></zprava>

Instrukce má cíl a data pro konkrétní aplikaci. aplikace zde neurčuje standardní chování a data uvnitř PI nejsou běžné XML atributy. XML parser zpravidla zpřístupní instrukci podle svého API; interpretaci musí implementovat příjemce. Cíl xml v libovolné kombinaci velikosti písmen je rezervovaný. XML deklarace je zvláštní konstrukce, nikoli obyčejná PI.

Po parsování text zprávy obsahuje A & B. Serializátor jej může zapsat znovu s escapováním, jiným odsazením a odlišným zápisem prázdných elementů. Strom může uchovat komentáře a PI, ale mapování do objektů je často vypouští. Pokud záleží na původním zápisu, uchovej bajty; pro porovnání či podpis stanov přesný model a kanonizační algoritmus.