Invalid byte 1 of 1-byte UTF-8 sequence: špatné kódování XML

Soubor je uložený v jiném kódování, než uvádí deklarace — typicky ve Windows-1250, i když první řádek tvrdí UTF-8 (nebo deklarace chybí a platí výchozí UTF-8). Parser narazí na první znak s diakritikou a skončí. Soubor převeď do UTF-8, nebo mu při načítání řekni, jaké kódování skutečně má.

Autor: Aktualizováno

Znění hlášky v jednotlivých parserech

Java hlásí Invalid byte 1 of 1-byte UTF-8 sequence (nebo byte 2 of 2-byte, byte 2 of 3-byte …), libxml2 podle verze Invalid bytes in character encoding nebo Input is not proper UTF-8, indicate encoding !, Python obecné not well-formed (invalid token) a .NET Invalid character in the given encoding:

Parser Hláška Kdy
libxml2 2.13 a novější PHP (DOM, SimpleXML), xmllint, Python lxml, validátor na XML.cz Invalid bytes in character encoding Soubor ve Windows-1250 s deklarací UTF-8, Znak, který v UTF-8 vypadá jako začátek sekvence
libxml2 2.9 starší xmllint, PHP a lxml Input is not proper UTF-8, indicate encoding ! Soubor ve Windows-1250 s deklarací UTF-8, Znak, který v UTF-8 vypadá jako začátek sekvence
Java (Xerces) DocumentBuilder, SAXParser, JAXB, Spring, Maven Invalid byte 1 of 1-byte UTF-8 sequence. Soubor ve Windows-1250 s deklarací UTF-8
Invalid byte 2 of 2-byte UTF-8 sequence. Znak, který v UTF-8 vypadá jako začátek sekvence
Python (expat) xml.etree.ElementTree, minidom not well-formed (invalid token): line 2, column 12 Soubor ve Windows-1250 s deklarací UTF-8
not well-formed (invalid token): line 2, column 7 Znak, který v UTF-8 vypadá jako začátek sekvence
.NET XmlDocument, XDocument, XmlReader Invalid character in the given encoding. Line 2, position 13. Soubor ve Windows-1250 s deklarací UTF-8
Invalid character in the given encoding. Line 2, position 8. Znak, který v UTF-8 vypadá jako začátek sekvence

Tabulka se týká parserů čtoucích původní bajty. Pokud soubor nejdřív přečteš jako text s nahrazováním chybných bajtů znakem � a teprve potom ho předáš DOMParseru, původní chybu kódování už tímto parserem nezjistíš.

Proč to vzniká

Ve Windows-1250 zabírá každý znak jeden bajt, v UTF-8 mají znaky s diakritikou dva nebo tři bajty a musí dodržet přesný vzor. Bajt F8 (ř ve Windows-1250) v UTF-8 nesmí stát nikde, proto Java hlásí hned první bajt. Bajt C8 (Č) sice smí zahájit dvoubajtovou sekvenci, ale následující 65 (e) do ní nepatří — Java pak hlásí Invalid byte 2 of 2-byte UTF-8 sequence.

ZnakWindows-1250UTF-8
řF8C5 99
íEDC3 AD
ČC8C4 8C
áE1C3 A1

Soubory ve Windows-1250 dodnes vytvářejí starší účetní a skladové programy, exporty z Excelu nebo ruční úpravy v editoru, který soubor uloží „v kódování systému“. Opačný případ — UTF-8 s deklarací windows-1250 — chybu nehlásí, jen místo „Jiří“ uvidíš „JiřÓ.

Ukázky

Soubor ve Windows-1250 s deklarací UTF-8

Chybně: uloženo ve Windows-1250
<?xml version="1.0" encoding="UTF-8"?>
<zakaznik>Jiří Černý</zakaznik>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<zakaznik>Jiří Černý</zakaznik>

Obě verze vypadají v editoru stejně, liší se bajty. Opravená je uložená v UTF-8. Ukázka se nedá otevřít ve validátoru, protože do textového pole se vždy vloží už převedený text.

Znak, který v UTF-8 vypadá jako začátek sekvence

Chybně: uloženo ve Windows-1250
<?xml version="1.0" encoding="UTF-8"?>
<mesto>Česká Lípa</mesto>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<mesto>Česká Lípa</mesto>

Jak kódování zjistit

  • Příkaz file vstup.xml (Linux, macOS) vypíše odhad kódování. Soubor ve Windows-1250 obvykle označí jako ISO-8859 text, protože obě sady rozlišit neumí.
  • Podívej se na bajty kolem pozice z hlášky (xxd vstup.xml nebo hexdump v prohlížeči). Jednotlivé bajty E8, F8, 9A, 9E mezi písmeny ukazují na Windows-1250, dvojice začínající C3, C4 nebo C5 na UTF-8.
  • VS Code ukazuje kódování vpravo dole ve stavovém řádku. Přes Reopen with Encoding soubor otevřeš jako Windows-1250 a přes Save with Encoding uložíš jako UTF-8.

Oprava: převést do UTF-8, nebo načíst se správným kódováním

Nejlepší oprava je převést soubor do UTF-8, které přečte každý parser. Pokud deklarace už uvádí encoding="UTF-8", po převodu začne odpovídat skutečným bajtům. Pokud uvádí Windows-1250, změň ji na UTF-8: samotný iconv text deklarace nepřepisuje.

Příkazová řádka (Linux, macOS, Git Bash)
iconv -f WINDOWS-1250 -t UTF-8 vstup.xml > vystup.xml

Když soubor převést nemůžeš (přichází pravidelně od dodavatele), dekóduj ho v programu sám. Rozlišuj bajty a už dekódované znaky. Java Reader předává parseru znaky a má přednost před encoding v deklaraci. PHP string ale obsahuje bajty: ukázka je převádí do UTF-8 a předpokládá, že deklarace uvádí UTF-8 nebo chybí. Pokud uvádí jiné kódování, uprav ji také.

PHP
$raw = file_get_contents('vstup.xml');
$xml = simplexml_load_string(iconv('WINDOWS-1250', 'UTF-8', $raw));
// mb_convert_encoding() Windows-1250 nezná (ValueError), proto iconv
Python
# Soubor ve Windows-1250 s chybnou deklarací UTF-8: dekóduj ho sám
with open("vstup.xml", encoding="cp1250") as f:
    root = ET.fromstring(f.read())
Java
// Reader s daným kódováním má přednost před deklarací v souboru
try (Reader reader = new InputStreamReader(Files.newInputStream(path), "windows-1250")) {
    Document doc = builder.parse(new InputSource(reader));
}
C#
// .NET (Core, 5 a novější) zná bez registrace jen Unicode, ASCII a Latin-1;
// ostatní kódové stránky zpřístupní CodePagesEncodingProvider
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

using var reader = new StreamReader("vstup.xml", Encoding.GetEncoding(1250));
var doc = XDocument.Load(reader);

Třetí možnost je opravit deklaraci na encoding="windows-1250". Ne každý parser ale Windows-1250 umí:

Parserencoding="windows-1250"
Java (Xerces)funguje
Python (expat)funguje, převod zajistí Python
PHP, xmllint (libxml2 s iconv)funguje
.NET (ověřeno na .NET 10)System does not support 'windows-1250' encoding — dokud se nezaregistruje CodePagesEncodingProvider
Validátor na XML.cz (libxml2 ve WebAssembly)Unsupported encoding: windows-1250 — soubor převeď do UTF-8

Související