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:
<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:
<objednavka>
<polozka>Hrnek</polozka>
<cena mena="CZK">199</cena>
<poznamka>Barva: černá & 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ázev | Platný? | Proč |
|---|---|---|
| <cena_s_DPH> | ano | písmena, číslice za prvním znakem a podtržítko jsou v pořádku |
| <položka> | ano | písmena s diakritikou specifikace povoluje |
| <datum-vystaveni> | ano | pomlčka a tečka smí být kdekoli kromě začátku |
| <2polozka> | ne | název nesmí začínat číslicí |
| <-polozka> | ne | ani pomlčkou nebo tečkou |
| <cena s DPH> | ne | mezera název ukončí a „s“ parser čte jako atribut bez hodnoty (hláška Specification mandates value for attribute) |
| <xmlData> | raději ne | ná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 libxml2 | Hláška Chrome 151+ | Co znamená |
|---|---|---|
| Opening and ending tag mismatch | Unexpected closing tag | uzaví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 document | Unexpected token: < | za kořenovým elementem je další obsah, typicky dva kořenové elementy za sebou |
| Premature end of data in tag | Unexpected end of stream: still inside the root element | soubor skončil dřív, než se uzavřely všechny značky (často useknutý soubor) |
| EntityRef: expecting ';' | Unexpected token inside entity | v textu je samotný znak & — musí se zapsat jako & |
| StartTag: invalid element name | Unexpected token inside qualified name | za znakem < není platný název elementu, např. neescapované < v textu |
| AttValue: " or ' expected | Unexpected token | hodnota atributu není v uvozovkách |
| Document is empty | Unexpected end of stream: no root element found | soubor 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:
| Pojem | Co kontroluje | Podle čeho |
|---|---|---|
| dobře formovaný (well-formed) | syntaxi: značky, kořen, uvozovky, entity | XML 1.0 |
| validní (valid) | strukturu podle deklarací v DOCTYPE | DTD |
| validní vůči schématu (schema-valid) | strukturu, datové typy a omezení hodnot | XSD, 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: < (<),
> (>), & (&), " (") a
' ('). 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ř.
© nebo © pro ©.
Pozor: HTML entity jako nebo © v XML
neexistují, dokud je nedefinuje DTD. Místo nich použij přímo znak
nebo číselný odkaz (  pro nezlomitelnou mezeru).
Číselný odkaz se zapisuje desítkově (é) nebo šestnáctkově
(é), 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
� nebo  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 .
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.
| Vlastnost | DOM | SAX / StAX (streamování) |
|---|---|---|
| Jak čte dokument | celý najednou do stromu v paměti | postupně, událost po události |
| Paměť | roste s velikostí dokumentu | téměř konstantní |
| Přístup k datům | libovolný, lze se vracet | jen dopředu, jedním průchodem |
| Úpravy dokumentu | snadné, strom lze změnit a uložit | není určeno k úpravám |
| Kdy použít | konfigurace, menší dokumenty, XPath/XSLT | velké 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><![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><![CDATA[a]]]]><![CDATA[>b]]></text> Uvnitř CDATA se nerozvíjejí entity ani číselné odkazy; doslovné < 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
<?xml version="1.0" encoding="UTF-8"?>
<?aplikace rezim="nahled"?>
<zprava><!-- poznámka --><text>A & 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.