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