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

vytrženo z kontextu

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

Karma body: 40. Líbil se vám článek?

Komentáře » přidat

  1. [1] mach: nový
    Posláno 24. 6. 2006 v 9.00 | Odpovědět
    Na komentář reagoval [23] Jan Renner
  2. [2] Vilém Málek: 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
  3. avatar [3] David Grudl: 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. [4] Lukáš: 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
  5. avatar [5] David Grudl: 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. [6] Pachollini: 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
    Na komentář reagoval [23] Jan Renner
  7. avatar [7] David Grudl: 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
    Na komentář reagoval [10] Pachollini
  8. avatar [8] pajada: 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
  9. avatar [9] llook: nový

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

    Posláno 24. 6. 2006 v 11.40 | Odpovědět
  10. [10] Pachollini: 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
  11. avatar [11] rADo: 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. [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
  13. avatar [13] David Grudl: 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. [14] Arcao: 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
    Na komentář reagoval [21] David Grudl
  15. avatar [15] Arcao: 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
  16. avatar [16] David Grudl: 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. [17] Petr: 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. [18] Vilém Málek: 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. [19] Ebo: 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
  20. avatar [20] David Grudl: 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
    Na komentář reagoval [27] ferenczy
  21. avatar [21] David Grudl: 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. [22] Vilém Málek: 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. [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
  24. avatar [24] David Grudl: 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
  25. avatar [25] error414: 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. [26] Vilém Málek: 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
  27. avatar [27] ferenczy: 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
  28. avatar [28] ferenczy: 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. [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
  30. avatar [30] David Grudl: 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á.

La Trine © 2004, 2008 David Grudl – o webu
provozuje Pachollini.

Jakékoliv užití obsahu, včetně převzetí článků nebo jejich částí, je bez předchozího písemného svolení autora zakázáno.

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