Na navigaci | Klávesové zkratky

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

Komentáře

  1. mach #1

    před 10 lety
  2. Vilém Málek http://interval.cz #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?

    před 10 lety | reagoval [3] David Grudl
  3. David Grudl http://davidgrudl.com #3

    avatar

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

    před 10 lety | reagoval [18] Vilém Málek
  4. Lukáš http://vsevjednom.cz #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).

    před 10 lety | reagoval [5] David Grudl
  5. David Grudl http://davidgrudl.com #5

    avatar

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

    před 10 lety
  6. Pachollini http://seky.nahory.net/ #6

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

    před 10 lety | reagoval [7] David Grudl
  7. David Grudl http://davidgrudl.com #7

    avatar

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

    před 10 lety | reagoval [9] llook [10] Pachollini
  8. pajada #8

    avatar

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

    před 10 lety | reagoval [13] David Grudl
  9. llook http://llook.wz.cz/weblog/ #9

    avatar

    #7 Davide Grudle, Scheiße Katze und Schweine Hund!

    před 10 lety
  10. Pachollini http://seky.nahory.net/ #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 :-)

    před 10 lety
  11. rADo http://radekhulan.cz/ #11

    avatar

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

    před 10 lety | reagoval [13] David Grudl
  12. Luboš #12

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

    před 10 lety | reagoval [13] David Grudl
  13. David Grudl http://davidgrudl.com #13

    avatar

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

    před 10 lety
  14. Arcao http://arcao.com #14

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

    (ulož to jako windows-1250)

    před 10 lety | reagoval [16] David Grudl [21] David Grudl
  15. Arcao http://arcao.com #15

    avatar

    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.

    před 10 lety
  16. David Grudl http://davidgrudl.com #16

    avatar

    #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

    před 10 lety
  17. Petr http://tapety.na-mobily.cz #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é…

    před 10 lety | reagoval [20] David Grudl
  18. Vilém Málek http://interval.cz #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ě? ;–)

    před 10 lety | reagoval [20] David Grudl
  19. Ebo http://ebolinx.no-ip.org #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..

    před 10 lety
  20. David Grudl http://davidgrudl.com #20

    avatar

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

    před 10 lety | reagoval [22] Vilém Málek [27] ferenczy
  21. David Grudl http://davidgrudl.com #21

    avatar

    Aktualizace

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

    před 10 lety
  22. Vilém Málek http://interval.cz #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?

    před 10 lety | reagoval [24] David Grudl
  23. Jan Renner #23

    function reImage($matches) {
      $content = $matches[6711];
      $align = $matches[6715];
      $href = $matches[6716];
    }
    před 10 lety
  24. David Grudl http://davidgrudl.com #24

    avatar

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

    před 10 lety | reagoval [26] Vilém Málek
  25. error414 http://www.error414.com/ #25

    avatar

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

    před 10 lety
  26. Vilém Málek http://interval.cz #26

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

    před 10 lety
  27. ferenczy http://ferenczy.coex.cz #27

    avatar

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

    před 10 lety | reagoval [28] ferenczy
  28. ferenczy http://ferenczy.coex.cz #28

    avatar

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

    před 10 lety
  29. 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‘?

    před 9 lety | reagoval [30] David Grudl
  30. David Grudl http://davidgrudl.com #30

    avatar

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

    před 9 lety

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