Proč diakritika v XML dělá problémy
Písmena bez diakritiky mají ve všech běžných kódováních stejné bajty jako v ASCII, proto se anglický text rozbije jen výjimečně. České znaky ale každé kódování ukládá jinak:
| Znak | UTF-8 | Windows-1250 | ISO-8859-2 | CP852 | Kamenických | KOI-8 CS2 |
|---|---|---|---|---|---|---|
| ř | C5 99 | F8 | F8 | FD | A9 | D2 |
| š | C5 A1 | 9A | B9 | E7 | A8 | D3 |
| ž | C5 BE | 9E | BE | A7 | 91 | DA |
| ů | C5 AF | F9 | F9 | 85 | 96 | CA |
Parser z bajtu F8 nepozná, jestli jde o ř z Windows-1250, nebo o neplatný bajt
UTF-8. Kódování mu musí říct deklarace, BOM nebo protokol, kterým soubor přišel (třeba
hlavička HTTP). Bez nich podle XML 1.0 platí UTF-8.
UTF-8 jako výchozí volba
UTF-8 obsáhne všechny znaky Unicode, takže v jednom souboru můžeš mít češtinu, polštinu, azbuku i emoji. Navíc ho podle specifikace musí umět přečíst každý XML parser, zatímco ostatní kódování parser podporovat nemusí. Například .NET bez registrace zvláštního poskytovatele nezná Windows-1250 a validátor na XML.cz ho neumí vůbec. Přehled podpory je na stránce Špatné kódování.
<?xml version="1.0" encoding="UTF-8"?>
<zprava>Příliš žluťoučký kůň úpěl ďábelské ódy</zprava> U UTF-8 a UTF-16 (s BOM) se deklarace smí vynechat. Uvádět ji je přesto dobrý zvyk, protože člověk i nástroj pak kódování vidí hned na prvním řádku.
Starší česká kódování
| Kódování | Název v IANA | Kde se používalo | Stav dnes |
|---|---|---|---|
| UTF-8 | UTF-8 | Unicode; web, Linux, macOS, většina formátů | výchozí volba |
| Windows-1250 (CP1250) | windows-1250 | Windows ve střední Evropě | stále v exportech starších programů |
| ISO 8859-2 (Latin 2) | ISO-8859-2 | Unix, e-mail a web v 90. letech | na ústupu |
| PC Latin 2 (CP852) | IBM852 | DOS a OS/2 | historické |
| Kamenických (KEYBCS2) | není registrováno | DOS v Československu zhruba 1985–1995 | historické |
| KOI-8 CS2 (ČSN 36 9103) | není registrováno | starší terminály, volba v editoru T602 | historické |
- Windows-1250 obsahuje všechny tisknutelné znaky ISO 8859-2, ale některé na jiných místech. V češtině se liší š, ž, ť a jejich velké tvary. Windows-1250 navíc využívá oblast 80–9F, kterou ISO 8859-2 vyhrazuje pro řídicí znaky. Není to tedy nadmnožina ISO 8859-2, i když to tak často vypadá.
- CP852 (PC Latin 2) má stejnou sadu písmen jako ISO 8859-2, ale úplně jiné rozložení. Kvůli tomu, že se mu říkalo „Latin 2“, se dodnes plete s ISO 8859-2.
- Kamenických vzniklo úpravou kódové stránky 437 a pojmenované je podle bratří Kamenických, autorů ovladače klávesnice KEYBCS2. Oblíbené bylo hlavně proto, že zachovalo znaky pro kreslení rámečků v DOSu. Někdy se mu říká CP895, ale IBM ani Microsoft takovou stránku pro češtinu nedefinovaly.
- KOI-8 CS2 definovala norma ČSN 36 9103. Nejčastější písmena
s diakritikou leží o 80 (šestnáctkově) výš než odpovídající velké písmeno bez diakritiky,
takže po vymazání osmého bitu zůstane text aspoň trochu čitelný: z „Příliš žluťoučký kůň“ vznikne
PRIliS ZluTouCkY kJN.
Kamenických a KOI-8 CS2 v registru IANA nejsou. XML 1.0 pro neregistrovaná kódování
doporučuje názvy s prefixem x-, takový název ale parsery neznají. Prakticky
se s těmito kódováními setkáš jen ve starých datech, která je potřeba převést.
Deklarace a BOM
Pokud soubor není v UTF-8 ani UTF-16, musí to podle XML 1.0 říct deklarace, jinak je to
fatální chyba. V deklaraci použij název z registru IANA. Na velikosti písmen nezáleží,
windows-1250 a WINDOWS-1250 znamenají totéž:
<?xml version="1.0" encoding="windows-1250"?>
<zprava>Příliš žluťoučký kůň úpěl ďábelské ódy</zprava>
BOM je značka pořadí bajtů na začátku souboru. U UTF-16 je povinná, u UTF-8 dovolená
(bajty EF BB BF). BOM a deklarace si nesmějí odporovat. Když UTF-8 s BOM
deklaruje windows-1250, libxml2 2.9.13 se řídil deklarací a vypsal rozsypaný text, zatímco
Python skončil chybou. Podrobně o autodetekci, BOM a hlavičce HTTP píše kapitola
Jak funguje XML.
Co se stane, když kódování nesedí
Text „Příliš žluťoučký kůň úpěl ďábelské ódy“ uložený ve Windows-1250 bez deklarace parser
čte jako UTF-8. Na prvním písmenu ř narazí na bajt F8, který v UTF-8 nesmí
stát nikde, a skončí:
$ xmllint --noout bez-deklarace.xml # bajty Windows-1250, bez encoding
bez-deklarace.xml:2: parser error : Input is not proper UTF-8, indicate encoding !
Bytes: 0xF8 0xED 0x6C 0x69
# Python 3.14 (expat)
xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 2, column 9
Tahle chyba je aspoň vidět. Horší je, když soubor deklaruje kódování, které je bajtově
platné, jen jiné. Soubor ve Windows-1250 s deklarací ISO-8859-2 parser
přijme bez námitek, protože v ISO-8859-2 je platný každý bajt. Z š, ž a ť se ale stanou
neviditelné řídicí znaky U+009A, U+009E a U+009D:
<?xml version="1.0" encoding="ISO-8859-2"?> <!-- bajty jsou ve Windows-1250 -->
<zprava>Příliš žluťoučký kůň</zprava>
# xmllint i Python dokument přijmou, text ale obsahuje řídicí znaky:
'Příli\x9a \x9elu\x9doučký kůň' Specifikace takový soubor označuje za fatální chybu, protože deklarace neodpovídá skutečnému kódování. Parser ji ale nemá jak poznat. Proto po každém převodu text zkontroluj očima nebo testem. Podle vzhledu poškozeného textu se dá odhadnout, co se stalo:
| Jak to vypadá | Skutečné kódování | Čteno jako |
|---|---|---|
| PĹ™Ăliš ĹľluĹĄouÄŤkĂ˝ | UTF-8 | Windows-1250 |
| PÅÃliÅ¡ žluÅ¥ouÄký | UTF-8 | ISO-8859-1 (Latin 1) |
| Příli luoučký (š, ž, ť zmizí) | Windows-1250 | ISO-8859-2 |
| P°ÝliÜ ×luŁouŔkř | Windows-1250 | CP852 |
| Pýˇliç §luśouźkě | CP852 | Windows-1250 |
| P��li� �lu�ou�k� | Windows-1250 | UTF-8 s náhradou chybných bajtů |
Stejná chyba nemusí v různých parserech dopadnout stejně. UTF-8 s deklarací windows-1250
vypsal xmllint jako rozsypaný text, ale Python skončil chybou na písmenu ň. Jeho druhý
bajt 88 totiž ve Windows-1250 není přiřazený žádnému znaku.
Převod do UTF-8
Než soubor převedeš, zjisti, v čem doopravdy je. Příkaz file jen hádá a
Windows-1250 od ISO-8859-2 nerozliší. Spolehlivější je podívat se na bajty a porovnat je
s tabulkou výše:
# Zjisti, co v souboru skutečně je
file vstup.xml
xxd vstup.xml | head
# Windows-1250, ISO-8859-2 nebo CP852 → UTF-8
iconv -f WINDOWS-1250 -t UTF-8 vstup.xml > vystup.xml
iconv -f CP852 -t UTF-8 vstup.xml > vystup.xml
# Kamenických a KOI-8 CS2 iconv nezná; umí je cstocs z balíku Cstools (CPAN)
cstocs kam utf8 < vstup.xml > vystup.xml
cstocs koi8 utf8 < vstup.xml > vystup.xml
# Po převodu oprav deklaraci a ověř výsledek parserem
xmllint --noout vystup.xml -
iconvpřevede bajty, ale deklaraci v souboru nepřepíše. Po převodu změňencoding="windows-1250"naUTF-8, nebo deklaraci smaž. - V editoru jde totéž ručně. Ve VS Code otevři soubor přes Reopen with Encoding ve správném kódování a ulož ho přes Save with Encoding jako UTF-8.
- Výsledek ověř parserem a zkontroluj písmena š, ž a ť. Právě u nich se Windows-1250 a ISO-8859-2 liší.
Jak načíst soubor ve Windows-1250 přímo v Javě, Pythonu, PHP nebo C# bez převodu na disku, ukazuje stránka Špatné kódování. Rozdíl mezi znakem, entitou a číselným odkazem vysvětluje kapitola Jak funguje XML.