Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Translate to English… Ins Deutsche übersetzen…

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:

  1. ověříme, zda text odpovídá normě UTF-8 a obsahuje nějaký „korektní“ znak. Pokud ano, jde o UTF-8
  2. ověříme, zda text obsahuje znak z rozsahu 7F..9F. Pokud ano, jde o WINDOWS-1250
  3. 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:

Download AutoCzech

Pokud vás problematika kódování zajímá více do hloubky, doporučuji výborně zpracované stránky na Wikipedii:


Související:

napsáno 24. 6. 2006 | karma 24. líbil se vám článek? | shlédnuto 10450x | nahoru

Komentáře RSS 2.0 komentářů » přidat

#1 mach nový

Posláno 24. 6. 2006 v 9.00 | Odpovědět
Na komentář reagoval [23] Jan Renner

#2 Vilém Málek http://interval.cz nový

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?

Posláno 24. 6. 2006 v 9.28 | Odpovědět
Na komentář reagoval [3] David Grudl
avatar

#3 David Grudl http://davidgrudl.com nový

#2 Vilém Málek: 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.

Posláno 24. 6. 2006 v 9.39 | Odpovědět
Na komentář reagoval [18] Vilém Málek

#4 Lukáš http://vsevjednom.cz nový

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).

Posláno 24. 6. 2006 v 10.13 | Odpovědět
Na komentář reagoval [5] David Grudl
avatar

#5 David Grudl http://davidgrudl.com nový

#4 Lukáš: 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í.

Posláno 24. 6. 2006 v 10.29 | Odpovědět
Na komentář reagoval [23] Jan Renner

#6 Pachollini http://seky.nahory.net/ nový

BTW: němčina má navíc ještě ß (je i v ISO 8859–1 stejně jako ä, ü a ö).

Posláno 24. 6. 2006 v 10.51 | Odpovědět
Na komentář reagoval [7] David Grudl [23] Jan Renner
avatar

#7 David Grudl http://davidgrudl.com nový

#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).

Posláno 24. 6. 2006 v 10.57 | Odpovědět
Na komentář reagoval [9] llook [10] Pachollini
avatar

#8 pajada pajada@gmail.com nový

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í.

Posláno 24. 6. 2006 v 11.13 | Odpovědět
Na komentář reagoval [13] David Grudl
avatar

#9 llook http://llook.wz.cz/weblog/ nový

#7 David Grudl: Scheiße Katze und Schweine Hund!

Posláno 24. 6. 2006 v 11.40 | Odpovědět

#10 Pachollini http://seky.nahory.net/ nový

#7 David Grudl: To je tedy pravda, jenom ÖMV už mimochodem není ÖMV, ale OMV, protože nikdo nevěděl, jak to psát :-)

Posláno 24. 6. 2006 v 11.58 | Odpovědět
avatar

#11 rADo http://radekhulan.cz/ nový

Toto se pro referer použít nedá. Co když na mě někdo odkazuje z čínské/japon­ské/thajské/rus­ké/nepálské/in­dické či dokonce laponské stránky? A nepoužívá UTF-8? ;-)

Posláno 24. 6. 2006 ve 12.39 | Odpovědět
Na komentář reagoval [13] David Grudl

#12 Luboš nový

Uvedený algoritmus vychází ze dvou předpokladů:

  • text je v češtině,
  • text je v jednom z kódování UTF-8, Windows-1250 a ISO-8859–2.

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).

Posláno 24. 6. 2006 ve 12.51 | Odpovědět
Na komentář reagoval [13] David Grudl
avatar

#13 David Grudl http://davidgrudl.com nový

#8 pajada: 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š: 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.

Posláno 24. 6. 2006 ve 13.35 | Odpovědět

#14 Arcao http://arcao.com nový

Tady máš případ, kdy text ve windows-1250 ti to detekuje jako utf-8 :)
echo detect('VĚŽ');

(ulož to jako windows-1250)

Posláno 24. 6. 2006 ve 14.01 | Odpovědět
Na komentář reagoval [16] David Grudl [21] David Grudl
avatar

#15 Arcao http://arcao.com nový

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.

Posláno 24. 6. 2006 ve 14.08 | Odpovědět
avatar

#16 David Grudl http://davidgrudl.com nový

#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

Posláno 24. 6. 2006 v 15.07 | Odpovědět

#17 Petr http://tapety.na-mobily.cz nový

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é…

Posláno 24. 6. 2006 v 15.32 | Odpovědět
Na komentář reagoval [20] David Grudl

#18 Vilém Málek http://interval.cz nový

#3 David Grudl: 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ě? ;–)

Posláno 25. 6. 2006 v 0.21 | Odpovědět
Na komentář reagoval [20] David Grudl

#19 Ebo http://ebolinx.no-ip.org nový

no.. nevím jak vy, ale já se jdu odkódovat do koupelny, naprogramovat si kartáček a pak se překompilovat do postele..

Posláno 25. 6. 2006 v 0.45 | Odpovědět
avatar

#20 David Grudl http://davidgrudl.com nový

#17 Petr: 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ém Málek: nejde najít znaky a přepsat je. Pod jedním znakem máme ľ a ž – které je správně? Je správně zvážať nebo zváľ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 ť.

Posláno 25. 6. 2006 v 7.04 | Odpovědět
Na komentář reagoval [22] Vilém Málek [27] ferenczy
avatar

#21 David Grudl http://davidgrudl.com nový

Aktualizace

#14 Arcao: tak jsem algoritmus vylepšil, a teď už žádná VĚŽ nemá šanci ;)

Posláno 25. 6. 2006 v 7.46 | Odpovědět

#22 Vilém Málek http://interval.cz nový

#20 David Grudl: 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?

Posláno 25. 6. 2006 ve 14.35 | Odpovědět
Na komentář reagoval [24] David Grudl

#23 Jan Renner nový

function reImage($matches) {
  $content = $matches[6711];
  $align = $matches[6715];
  $href = $matches[6716];
}

Posláno 25. 6. 2006 ve 22.21 | Odpovědět
avatar

#24 David Grudl http://davidgrudl.com nový

#22 Vilém Málek: 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)

Posláno 26. 6. 2006 v 8.15 | Odpovědět
Na komentář reagoval [26] Vilém Málek
avatar

#25 error414 http://www.error414.com/ nový

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 :-)

Posláno 26. 6. 2006 v 8.26 | Odpovědět

#26 Vilém Málek http://interval.cz nový

#24 David Grudl: Aha, to jsem si neuvědomil. Děkuji za dokopání k pochopení ;–)

Posláno 26. 6. 2006 ve 12.59 | Odpovědět
avatar

#27 ferenczy http://ferenczy.coex.cz nový

#20 David Grudl:

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);

Posláno 25. 8. 2006 ve 12.17 | Odpovědět
Na komentář reagoval [28] ferenczy
avatar

#28 ferenczy http://ferenczy.coex.cz nový

#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)

Posláno 25. 8. 2006 ve 12.19 | Odpovědět

#29 jano nový

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‘?

Posláno 15. 2. 2008 ve 14.05 | Odpovědět
Na komentář reagoval [30] David Grudl
avatar

#30 David Grudl http://davidgrudl.com nový

#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.

Posláno 15. 2. 2008 ve 14.47 | Odpovědět

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře ani hlasovat

Výtah na začátek článku na první komentář

Názory čtenářů v diskusích nejsou názory provozovatele webu, a ten za jejich obsah neodpovídá.

phpFashion © 2004, 2010 David Grudlo webu

Pokud není uvedeno jinak, podléhá obsah těchto stránek licenci Creative Commons BY-NC-ND Creative Commons License BY-NC-ND

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.