Bonjour à tous,

Nouveau venu (mais lecteur assidu), je souhaite partager un truc de développeur concernant UTF-8 qui m'aura bien pris la tête et qui peut être utile à nombre d'entre vous.

J'utilise UTF-8 dans mon application PHP et j'ai besoin de retraiter différents types de chaînes de caractères, parfois complexes. Grosso-modo, tout allait bien jusqu'à ce que, lors de mes tests, je commence à tester des chaînes tordues du style :
é&à"#-|<i>7è</i>\r\n'ù^e<br>$"£*" </textarea>blabla, etc...

Tout va bien, sauf... pour le caractère "à" qui devient "?"...
Bref, après plusieurs affichages sous différents encodages, utf8_decode, et autres détection automatique, j'en arrive au constat que ce n'est pas un problème de manipulation d'UTF-8 mais un problème au niveau du traitement de ma chaîne.

Il existe déjà un certain nombre de posts sur ce sujet, certains concernant des bugs dans des CMS reconnus, mais peu contenant une explication plausible et une solution valable.
Dont celui-ci : En utf-8 le caractère "à " devient "?"

Après plusieurs heures de recherche pour ce même problème (eh oui), je suis enfin tombé sur la bonne page: PHP character encoding ? sign instead of à

Il semblerait donc que l'encodage UTF-8 de "à" se fait sur 2 octets (comme pour les autres caractères accentués), mais que le second octet n'est autre qu'un espace blanc (non-breaking space), ce qui est un cas particulier. Je cite :

a écrit :
Here is my take on your problem. The letter à is encoded in two bytes in utf8. The first byte is xC3, which is à in latin-1, the second byte is... non breaking space! (The other accented letters, such as è are encoded by à followed by an other accented letter in latin-1, and they are not affected).

Therefore, my guess is that you have a script, somewhere, that removes, or replaces, the non-breaking space in latin-1, i.e., character xA0. The resulting lonely byte xC3 cannot be displayed properly, so the general placeholder ? is displayed instead. (just load your page in latin-1, you will see that I am right).


Pour le coup, j'ai fait ce que suggère l'auteur (Olivier Verdier, révérence) :

a écrit :
Find that script that removes non-breaking spaces, and you'll be fine.


et donc modifié mon traitement "problématique" :

$str = preg_replace('`[[:blank:]]{1,}`', ' ', trim($str));


en :

$str = str_replace('[[&agrave;]]', 'à', preg_replace('`[[:blank:]]{1,}`', ' ', str_replace('à', '[[&agrave;]]', trim($str))));


Voilà, je ne sais pas si c'est très clair. Par contre, j'aurais bien aimé tomber rapidement sur cette explication chez Alsacréations, d'où ma contribution...
Modifié par adp (29 May 2013 - 17:19)
Bonsoir,

Ton problème doit probablement pouvoir aussi se résoudre plus simplement en utilisant le mode UTF-8 des expressions régulières.

IL existe en effet un mode UTF-8 très peu connu et très peu utilisé, qu'on peut activer avec l'option u ! J'en ai compris l'utilité rapidement quand j'ai passé un de mes sites en UTF-8...

Une autre solution possible à ton problème serait donc :

$str = preg_replace('`\s+`u', ' ', $str); // note que ton blank s'écrit aussi \s, et que {1,} s'écrit plus simplement +
$str = preg_replace('%\A\s+%u', '', preg_replace('%\s+\Z%u', '', $str)); // équivalent UTF-8-aware de trim


A noter que la fonction trim est à ne pas utiliser en contexte UTF-8, justement parce qu'elle ne le supporte pas; c'est aussi le cas de plusieurs autres fonctions de base telles que strtolower/strtoupper, etc.

Je me suis aussi déjà fait surprendre avec les fonctions de la famille printf/sprintf/fprintf qui produisent parfois des sorties inattendues; également à éviter en contexte UTF-8.

Bref, le maître mot est méfiance !

Voir les fonctions mb_* qui sont spécifiquement prévues pour traiter les chaînes UTF-8.
Modifié par QuentinC (29 May 2013 - 21:29)
Merci Quentin pour ces explications complémentaires, je ne connaissais pas l'option "u", de même que j'ignorais qu'il pouvait exister ce genre de problème selon le type de fonction utilisée. C'est vrai qu'on a souvent tendance, une fois une solution trouvée à notre problème, à ne pas chercher plus loin...

Juste une remarque cependant avec ta solution: "\s" ne remplace pas vraiment "[[:blank:]]", car cela remplace les retours chariots par un espace, ce que ne fait pas la classe [[:blank:]] (voir les classes pré-définies).

Donc finalement, après avoir remixé le tout et choisi ma "convention d'écriture", j'opte pour cette solution :

$str = preg_replace(array('`^\s+`u', '`[[:blank:]]+`u', '`\s+$`u'), array('', ' ', ''), $str);
a écrit :
Juste une remarque cependant avec ta solution: "\s" ne remplace pas vraiment "[[:blank:]]", car cela remplace les retours chariots par un espace, ce que ne fait pas la classe [[:blank:]] (voir les classes pré-définies).

Ah, en effet, au temps pour moi, j'ai raté cette petite subtilité. Je n'utilise jamais les classes prédéfinies genre [[:truc:]], donc ce que tu proposais me paraissait un peu suspect.