Blog

Kevert karakterkódolás

Publikálva: 2014.07.28 19:00
Kevert karakterkódolás

Találkoztál már az 'invalid char' hibával miközben PHP-s XML parserral dolgoztál? Ha igen, akkor ez a bejegyzés téged is érdekelhet.
Már régóta alapkövetelmény, hogy utf8 karakterkódolást használjak munkáim során. Elég sokat dolgozok XML és JSON adatokkal, így többször is belefutottam már abba a hibába, hogy a simplexml, vagy ajax hívás belehalt az adatok feldolgozásába, miközben az iconv karakterkódolás utf8-ra semmit sem segített.

Figyeljük meg az alábbi szöveget, látszólag semmi kivetnivalót nem találunk benne:
"0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul"

Viszont a háttérben, ahogy a lenti kép is szemlélteti, oda nem illő karakterekkel van megtűzdelve a szöveg:

Invalid char

Hogy kerülhetnek ilyen karakterek a szövegünkbe?

Tapasztalataim szerint legtöbb estben az adminisztrációs felületre bemásolt ismeretlen eredetű szöveg képzi a problémát. Például webshoppoknál gyakori probléma, hogy egy adott termékhez a leírás Ctrl+C/Ctrl+V módon kerül be. Ezáltal elkerülhetetlen, hogy ne emeljünk be a rendszerbe olyan speciális karaktereket, amik akár egy pdf, vagy word dokumentumból is származhatnak. Én ezeket rendszerint kevert karakterkódolású szövegeknek hívom.

A megoldás

Mindenekelőtt a megoldás ne az legyen, hogy visszadobjuk a labdát az ügyfélnek, hogy javítsd a szöveget. Sok esetben én is alig találom meg, mi az a karakter ami gondot okoz.

Program szintjén kell a kevert karakterkódolással foglalkozni. Sajnos a fenti esetekben az iconv nem jó megoldás, még abban az esetben sem, ha használjuk a //TRANSLIT, vagy //IGNORE kapcsolókat. Kivétel nélkül átmegy rajta minden ilyen karakter. Mondhatni ezek is utf8 karakterek, viszont úgynevezett túl hosszú kódolással állnak elő. Így eshet meg, hogy az iconv átengedi, de a simplexml lehal. Mindez angolul: overlong encoding. Erről bővebben a wikin is olvashatsz.

Problémáimra az internet sokszor ad megoldást. Mostanában kezd jó barátom lenni a Stack Overflow. Csak az nincs meg rajta, ami nem is kell. Nincs olyan probléma amit ne feszegetnének több szálon. A sok jó megoldás mellett, rengeteg a false is. És ha az ember nem körültekintő és tapasztalt, akkor félrevezető lehet olykor, hogy rossz megoldást is jónak titulálnak egyes felhasználók.

Az ajánlatok között felfigyeltem egy ígéretesre, ami azt csinálja, hogy preg_replace-szel kicseréli ? jelre az oda nem illő karaktereket. Google-ben ez irányban keresgéltem tovább, két ugyanolyan kódot nem nagyon találtam, de volt hasonlóság köztük, majd találtam egy elég jól kidolgozottat, valószínűsítem, hogy ez lesz az eredeti algoritmus. Én ezt egy függvénybe burkoltam, cleanUTF8 lett a neve.

function cleanUTF8($str){
	$str = preg_replace('/'.
		'[\x00-\x08\x10\x0B\x0C\x0E-\x19\x7F]'.
		'|[\x00-\x7F][\x80-\xBF]+'.
		'|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'.
		'|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'.
		'|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})'.
	'/S', '?', $str);
	
	$str = preg_replace('/'.
		'|\xE0[\x80-\x9F][\x80-\xBF]'.
		'|\xED[\xA0-\xBF][\x80-\xBF]'.
	'/S', '?', $str);
	
	return $str;
}

Szemléltető példa kevert karakterkódolásra

Készítettem segítségként egy kis programot is, amiben látszik, hogy az iconv semmit sem ért. Ha kimásoljuk texteditorba az eredményt, akkor látszik, hogy tartalmaz minden invalid karaktert. Vagy hogy a simplexml lehal, és csak a tisztítás után hajlandó feldolgozni a szövegünket:

<?php
ini_set("display_errors", 1);
header("Content-Type: text/html; charset=utf-8");

function cleanUTF8($str){
	$str = preg_replace('/'.
		'[\x00-\x08\x10\x0B\x0C\x0E-\x19\x7F]'.
		'|[\x00-\x7F][\x80-\xBF]+'.
		'|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'.
		'|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'.
		'|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})'.
	'/S', '?', $str);
	
	$str = preg_replace('/'.
		'|\xE0[\x80-\x9F][\x80-\xBF]'.
		'|\xED[\xA0-\xBF][\x80-\xBF]'.
	'/S', '?', $str);
	
	return $str;
}

$string = '0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul';
echo 'Original: '.$string;
echo '<hr />';

$string = iconv('utf-8', 'utf-8', $string);
echo 'iconv: '.$string;
echo '<hr />';

$string = iconv('utf-8', 'utf-8//TRANSLIT', $string);
echo 'iconv + TRANSLIT: '.$string;
echo '<hr />';

$string = iconv('utf-8', 'utf-8//IGNORE', $string);
echo 'iconv + IGNORE: '.$string;
echo '<hr />';

$xml_1 = simplexml_load_string('<data>'.$string.'</data>');
echo '<pre>'.print_r($xml_1, true).'</pre>';
echo '<hr />';

$string = cleanUTF8($string);
echo 'Clean: '.$string;
echo '<hr />';

$xml_2 = simplexml_load_string('<string>'.$string.'</string>');
echo '<pre>'.print_r($xml_2, true).'</pre>';
?>

A kimenet:

Original: 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul

iconv: 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul

iconv + TRANSLIT: 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul

iconv + IGNORE: 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍłŁ$ßěŘŕ×÷ä¤hozzájárul, és ezen keresztul

Warning: simplexml_load_string(): Entity: line 1: parser error : PCDATA invalid Char value 17 in test.php on line 36 ...

Clean: 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍ?łŁ$ßěŘŕ×÷ä¤?hozzájárul, és ezen keresztu?l

SimpleXMLElement Object
(
    [0] => 0123456789öüóőúéáűíÖÜÓŐÚÉÁŰÍ?łŁ$ßěŘŕ×÷ä¤?hozzájárul, és ezen keresztu?l
)

További szép karakterkódolást!

UPDATE: A fenti megoldás php 5.3 vagy az alatt használható! Egy elegánsabb megoldás itt található, ami működik php 5.4 alatt is.

Kapcsolódó cikkek