Décomposer une chaîne UTF-8
Posté : 02 févr. 2006, 02:04
Voilà mon histoire: j'écris depuis quelques temps différentes fonctions qui doivent s'appliquer à des chaînes UTF-8. Ces fonctions sont variées (changer la casse, décomposer une chaîne en mots, transcription , etc...) mais toutes ont en commun la nécessité de décomposer une chaîne en caractères, et plus particulièrement de capturer les caractères multi-bytes.
C'est pourquoi je suis à la recherche de l'algorithme le plus optimisé, ce qui m'amène à poster ici. Ci-dessous vous trouverez un exemple vide pour illustrer mon propos, l'idée est de rajouter le traitement des données aux alentours de "Do stuff". Si vous trouvez comment optimiser d'avantage ce code, je vous en serait éternellement reconnaissant, sinon j'espère qu'il vous servira au moins à quelque chose un de ces jours (avant que PHP 6.0 ne soit adopté, après on s'en fiche).
À noter que dans ce que je recherche, les caractères ASCII n'ont besoin d'aucun traitement particulier, c'est pour cela que je les "saute" rapidement avec strspn(), seuls les caractères multi-byte m'intéressent. Autre détail qui a son importance, $str ne contient pas nécessairement de l'UTF-8 bien formé car il provient de l'utilisateur. Voilà, si vous parvenez à faire plus rapide que ça, vous êtes un champion
PS: je suis ouvert à toute discussion, pour expliquer le fonctionnement ou explorer de nouvelles pistes
C'est pourquoi je suis à la recherche de l'algorithme le plus optimisé, ce qui m'amène à poster ici. Ci-dessous vous trouverez un exemple vide pour illustrer mon propos, l'idée est de rajouter le traitement des données aux alentours de "Do stuff". Si vous trouvez comment optimiser d'avantage ce code, je vous en serait éternellement reconnaissant, sinon j'espère qu'il vous servira au moins à quelque chose un de ces jours (avant que PHP 6.0 ne soit adopté, après on s'en fiche).
À noter que dans ce que je recherche, les caractères ASCII n'ont besoin d'aucun traitement particulier, c'est pour cela que je les "saute" rapidement avec strspn(), seuls les caractères multi-byte m'intéressent. Autre détail qui a son importance, $str ne contient pas nécessairement de l'UTF-8 bien formé car il provient de l'utilisateur. Voilà, si vous parvenez à faire plus rapide que ça, vous êtes un champion
PS: je suis ouvert à toute discussion, pour expliquer le fonctionnement ou explorer de nouvelles pistes
$str = "Un fran\xC3\xA7ais \xC3\xA0 M\xC3\xA2con a \xC3\xA9t\xC3\xA9 mis aux arr\xC3\xAAts";
echo 'Phrase originale: "', $str, "\"\n";
utf8_stuff($str);
function utf8_stuff($str)
{
$ascii_range =
"\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D"
. "\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31"
. "\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45"
. "\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46"
. "\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F"
. "\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40"
. "\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F"
. "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
$pos = strspn($str, $ascii_range);
$len = strlen($str);
if ($pos == $len)
{
return;
}
$trailing_bytes_range =
"\xA9\xA0\xA8\x80\xAA\x99\xA7\xBB\xAB\x89\x94\x82\xB4\xA2\xAE\x83"
. "\xB0\xB9\xB8\x93\xAF\xBC\xB3\x81\xA4\xB2\x9C\xA1\xB5\xBE\xBD\xBA"
. "\x98\xAD\xB1\x84\x95\xA6\xB6\x88\x8D\x90\xB7\xBF\x92\x85\xA5\x97"
. "\x8C\x86\xA3\x8E\x9F\x8F\x87\x91\x9D\xAC\x9E\x8B\x96\x9B\x8A\x9A";
$utf_len_mask = array(
/**
* Leading byte masks
*/
"\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4,
/**
* Trailing byte masks
*/
"\x80" => 0, "\x90" => 0, "\xA0" => 0, "\xB0" => 0
);
do
{
$utf_len =& $utf_len_mask[$str[$pos] & "\xF0"];
if (isset($utf_len))
{
if ($utf_len)
{
/**
* Leading byte
*/
$utf_char = substr($str, $pos, $utf_len);
$_pos = 1;
do
{
/**
* Check that each byte after the first is a trailing byte
*/
if (($utf_char[$_pos] & "\xC0") != "\x80")
{
/**
* Ill-formed char, skip the mangled bytes and go on
*/
$pos += $_pos;
continue 2;
}
}
while (++$_pos < $utf_len);
/**
* Do stuff here
*/
echo "UTF char: ", $utf_char, "\n";
$pos += $utf_len;
}
else
{
/**
* Misplaced trailing byte, skip it and all subsequent trailing bytes
*/
$pos += strspn($str, $trailing_bytes_range, ++$pos);
}
}
else
{
/**
* ASCII char
*/
$pos += strspn($str, $ascii_range, ++$pos);
}
}
while ($pos < $len);
}