AutoCzech aneb automatická detekce kódování
Někdy je potřeba zjistit kódování textu. Například při implementaci trackbacků, při analýze refererů atd. Tuto úlohu se snažilo řešit už několik programátorů, ale výsledek se nikdy dokonalosti zdaleka neblížil. Pokusím se nyní ukázat skutečně 100% funkční řešení.
Jak na to?
Nejprve si musíme vymezit, která kódování nás zajímají. V našich
končinách a ve sféře webů připadá v úvahu UTF-8
,
WINDOWS-1250
a ISO-8859-2
. A ačkoliv se bavíme
o češtině, půjde nám z historicky-společenského hlediska hned
o čtyři abecedy: českou, slovenskou, německou a maďarskou Oproti anglické
mají navíc tyto znaky:
česká: á č ď é ě í ň ó ř š ť ú ů ý ž
slovenská: á ä č ď é í ľ ĺ ň ó ô ŕ š ť ú ý ž
německá: ä ö ü
maďarská: á é í ó ö ő ú ü ű
a samozřejmě ještě velká písmena
Následující řešení je stejně dobře použitelné i pro detekci slovenského textu.
Text v UTF-8
se dá detekovat s největší jistotou. Řetězec
musí přesně odpovídat normě. Naopak pro kódování
WINDOWS-1250
a ISO-8859-2
nic takové neplatí a
detekce bude složitější. Kódování se liší hned ve čtyřech znacích
spadajících do sledovaných abeced. Použít analýzu na základě četnosti?
To je hodně nejisté…
Naštěstí existuje mnohem lepší řešení. Víte, že v kódování
ISO-8859-2
nemohou být použity znaky 7F..9F
?
A zrovna v tomto rozsahu se ve WINDOWS-1250
nachází
š ť ž Š Ž Ť
a české typografické uvozovky. Tím se nám
situace značně zjednodušuje.
Jak tedy budeme postupovat:
- ověříme, zda text odpovídá normě
UTF-8
a obsahuje nějaký „korektní“ znak. Pokud ano, jde oUTF-8
- ověříme, zda text obsahuje znak z rozsahu
7F..9F
. Pokud ano, jde oWINDOWS-1250
- jinak jde o
ISO-8859-2
.
Tento postup jsem si ověřil na mnoha stech tisících náhodně vybraných vzorků a úspěch byl 100 %. Žádná jiná metoda tak úspěšná nebyla.
Implementace
V bodě č. 1 si pomůžeme elegantním trikem. Navíc nesmírně rychlým.
Od PHP verze 4.3.5 preg_match()
v režimu unicode ověřuje
validitu vstupního řetězce. Tedy stačí hledat „nic“ a pokud se najde,
je řetězec korektní UTF-8
.
Dále je třeba ověřit, že text obsahuje i nějaký znak v užívaném
rozsahu. Tím ubráníme mylné identifikací třeba řetězce ĚŽ
jakožto znaku UTF-8
. Rozsah, který je použit v regulárním
výrazu, jsem stanovil pečlivou analýzou možných kolizí.
Bod č. 2 rozšíříme ještě o detekci slovenského znaku
Ľ
, takže rozsah bude 7F..9F,BC
.
Výsledná funkce bude vypadat takto:
// charset detection by dgx
function detect($s)
{
if (preg_match('#[\x80-\x{1FF}\x{2000}-\x{3FFF}]#u', $s))
return 'UTF-8';
if (preg_match('#[\x7F-\x9F\xBC]#', $s))
return 'WINDOWS-1250';
return 'ISO-8859-2';
}
V praxi nám spíš než o vrácení názvu kódování půjde
o překódování textu. Další funkce tedy detekuje kódování a vrací text
v univerzálním UTF-8
.
// convert to UTF-8 by dgx
function autoUTF($s)
{
// detect UTF-8
if (preg_match('#[\x80-\x{1FF}\x{2000}-\x{3FFF}]#u', $s))
return $s;
// detect WINDOWS-1250
if (preg_match('#[\x7F-\x9F\xBC]#', $s))
return iconv('WINDOWS-1250', 'UTF-8', $s);
// assume ISO-8859-2
return iconv('ISO-8859-2', 'UTF-8', $s);
}
Funkce vyžaduje přítomnost iconv. To je nativní součástí PHP5. Pokud
bychom hledali univerzální řešení pro PHP4, doporučil bych nahradit
iconv()
za strtr()
(iconv je o něco rychlejší).
Upravenou funkci si můžete stáhnout:
Pokud vás problematika kódování zajímá více do hloubky, doporučuji výborně zpracované stránky na Wikipedii:
Související:
- Převody mezi kódováním
- Odstranění diakritiky
- detektor kódování Enca (tip poslal Orrorin)
Komentáře
mach #1
Par relevantnich odkazu:
https://web.archive.org/…tection.html
https://web.archive.org/…nition_chart
Vilém Málek #2
S výjimkou znaků š, ť, ž, Š, Ž, Ť a českých typografických uvozovek jsou WINDOWS-1250 a ISO-8859–2 stejné, jak to vyplývá z detekční metody?
David Grudl #3
#2 Viléme Málku, WINDOWS-1250 a ISO-8859–2 se liší cca v 50 znacích. Jednak ve zmíněném rozsahu \x80 – \x9f a pak v dalších patnácti znacích.
Proto je důležité skutečně rozpoznat kódování, nestačí jen zaměnit pár znaků s diakritikou. Nebylo možné rozpoznat mezi slovenským
ľ
až
, neboŤ
a«
, které sdílejí stejné ordinální číslo v těchto kódováních.Lukáš #4
Funkce vyžaduje přítomnost iconv. To je nativní součástí PHP5. Pokud bychom hledali univerzální řešení pro PHP4, doporučil bych nahradit iconv() za strtr()
Možná jsem jen špatně pochopil ty dvě věty, ale iconv je i v php4 (já jej tam normálně používám).
David Grudl #5
#4 Lukáši, v případě PHP4 jde o volitelné rozšíření. Naštěstí na většině rozumných hostingů je k dispozici, ale pravidlem to zdaleka není.
Pachollini #6
BTW: němčina má navíc ještě ß (je i v ISO 8859–1 stejně jako ä, ü a ö).
David Grudl #7
#6 Pachollini, to je samozřejmě pravda, jen se s ním v „českých“ psaných textech nesetkáváme, narozdíl od přehlásek (Müller, ÖMV).
pajada #8
Chtěl bych upozornit, že ani toto řešení není 100%, ale je nejelegantnější, které jsem kdy viděl.
I text napsaný např. v kódování ISO-8859–2 (obsahující nějaké akcentované znaky) může být bohužel korektní řetězec UTF-8 (a jeho interpretace bude jiná než zamýšlená); naštěstí s velmi malou pravděpodobností.
llook #9
#7 Davide Grudle, Scheiße Katze und Schweine Hund!
Pachollini #10
#7 Davide Grudle, To je tedy pravda, jenom ÖMV už mimochodem není ÖMV, ale OMV, protože nikdo nevěděl, jak to psát 🙂
rADo #11
Toto se pro referer použít nedá. Co když na mě někdo odkazuje z čínské/japonské/thajské/ruské/nepálské/indické či dokonce laponské stránky? A nepoužívá UTF-8? 😉
Luboš #12
Uvedený algoritmus vychází ze dvou předpokladů:
Míra splnění prvního předpokladu závisí na kontextu a nebudu ho dále rozebírat.
Druhý předpoklad vychází ze stavu používání Internetu u nás. Příznivců lynxu pro DOS (kódování CP852) již mnoho není, pravděpodobně i většina prohlížečů pro Macy používá kódování UTF8 a ne nativní MacCE. Někdo „zlomyslný“ Vám však může způsobit problémy, neboť obě kódování (CP852 a MacCE) lze nastavit např. ve Firefoxu.
Určitě bych se neodvážil použít Váš algoritmus pro detekci češtiny u textových souborů, neboť souborů s kódováním CP852 se najde ještě mnoho (Macy v síti žádné nemáme).
David Grudl #13
#8 pajado, ani bych nechtěl, aby vznikl dojem, že algoritmus je neprůstřelný. Jde čistě o odhadování. Ale v praxi (při normálních vstupech) by měla být jeho úspěšnost skutečně 100 %. I pro velmi krátké řetězce.
#11 rADo, konkrétně jsem měl na mysli detekci fráze, ze které se člověk dostal na stránky přes vyhledávač. Takže buď má jedno z českých kódování, nebo utf, nebo jakékoliv jiné a pak hledá podle ascii, což algoritmus pokrývá.
#12 Luboši, rozšíření o CP852 by spočívalo v přidání jednoho řádku, detekce podle semigrafických znaků. Jenže větší rozsah podporovaných kódování ⇒ větší chybovost. A protože neznám jediný případ, kdy bych v prostředí internetu musel toto kódování detekovat, vyhnul jsem se mu.
ad zlomyslní: tohle přece není zabezpečovací systém ale prostředek pro zvednutí pohodlí. Lepší náhrada za „automaticky předpokládám utf a občas zobrazím rozsypaný čaj“.
Nicméně produkuje vždy validní UTF-8.
Arcao #14
Tady máš případ, kdy text ve windows-1250 ti to detekuje jako utf-8 :)
echo detect('VĚŽ');
(ulož to jako windows-1250)
Arcao #15
Mimochodem asi by se dala vymyslet i věta, která by byla validní jak v utf-8, tak i ve vindows-1250 (to samé by platilo i pro iso-8859–2) a dávala by česky smysl.
David Grudl #16
#14 Arcao, no, tak přesně s tímhle jsem se bavil při hledání algoritmu 🙂 Dokonce jsem litoval, že „päťka“ se slovensky nepíše „PIÄŤKA“. Ale to je humor jen pro zasvěcené :-p
Petr #17
Jak je to s jistotou i iconv, dost často se mi zdá, že iconv koduje špatně. Často nepozná vstupní kodování, některé znaky vyhodí chybné…
Vilém Málek #18
#3 Davide Grudle, Právě na tohle jsem narážel. Protože pokud by odlišnost spočívala jen v těch několika znacích, stačilo by je najít a přepsat. Ale pokud se liší ve vícero znacích – jak mohu založit identifikaci na jakékoli jejich podmnožině? ;–)
Ebo #19
no.. nevím jak vy, ale já se jdu odkódovat do koupelny, naprogramovat si kartáček a pak se překompilovat do postele..
David Grudl #20
#17 Petře, iconv mi funguje spolehlivě, jen občas blbnou doplňkové funkce (iconv_substr). Každopádně iconv nerozeznává vstupní kódování, to se musí vždy uvést.
#18 Viléme Málku, nejde najít znaky a přepsat je. Pod jedním znakem máme
ľ
až
– které je správně? Je správnězvážať
nebozváľať
? Slovenština zná obojí. Musíme proto detekovat kódování na základě celého textu. V tomto případě rozhodne právě písmenkoť
.David Grudl #21
Aktualizace
#14 Arcao, tak jsem algoritmus vylepšil, a teď už žádná VĚŽ nemá šanci ;)
Vilém Málek #22
#20 Davide Grudle, Asi jsem hodně natvrdlý, ale jak může „ť“ v tomto případě rozhodnout? Jak vůbec mohu v tomto případě určit, v jakém kódování to slovo je?
Jan Renner #23
David Grudl #24
#22 Viléme Málku, protože
ť
se nachází v kódování WINDOWS-1250 na pozici, která je v ISO-8859–2 zakázaná (viz srovnávací tabulka)error414 #25
Já jsem zatím rešil jen je to UTF-8 a není to UTF-8, pomocí chybné zjištění délky řetězce pomocí strlen u UTF-8.
Tady je to ale reseno „az tak moc“
Je-li to možné, piště prosím s diakritikou
jj stačí říct, kdyz za to bude pivko 🙂
Vilém Málek #26
#24 Davide Grudle, Aha, to jsem si neuvědomil. Děkuji za dokopání k pochopení ;–)
ferenczy #27
#20 Davide Grudle,
iconv AFAIK umi detekovat kodovani, viz manual. po pul dni googleni a prolezani manualu je to IMHO nejlepsi cesta. Pouziti je jednoduche, proste se jako vstupni kodovani uvede prazdny retezec, napr.:
iconv('', 'UTF-8//TRANSLIT', $text);
ferenczy #28
#27 ferenczy,
tedy neni to primo v manualu, ale v jednom z komentaru pod iconv. a je to pouze spekulace, nicmene funguje. mozna bude vice info v dokumentaci k iconv (nemyslim PHP rozhrani, ale primo dokumentaci toho softu)
jano #29
funkcia
detect($s)
je veľmi šikovná ale nie vždy aj na 100% funkčná. Problém nastáva pri nesprávnom detekovaní písmen ‚Ľ‘ a ‚ľ ' v znakovej sade WINDOWS-1250.napr. zoberme si slovo 'Stará Ľubovňa‘ kódované vo WINDOWS-1250 funkcia nám vráti ‚ISO-8859–2‘ čo nie je pravda. Problém je v tom, že sa v slove nachádza už zmieňované veľké ‚Ľ‘=>‚\xBC". Tento problém sa dá ešte riešiť náhradou
"\xBC"=>"\x9F" v $s
pred volaním`// detect WINDOWS-1250
if (preg_match(‘#[\x7F-\x9F]#', $s).`
V kódovaní ‚ISO-8859–2‘ sa „\xBC“ v našich jazykoch nepoužíva takže je to OK. Ale čo v prípade keď máme v slove použité malé ľ napr. ‚Rozdeľovanie‘?
David Grudl #30
#29 jano,
Ľ
by se skutečně do rozlišovací schopnosti zahrnout mohlo, ale maléľ
bohužel už ne – článek a kódy jsem upravil.Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.