Dimanche 19 mai 2013 A- / A+

Unicode, pourquoi, comment

L’Unicode en résumé

L’Unicode peut être considéré comme une révolution, ou au moins une évolution, des différentes normes d’encodage. Ce dernier permet d’écrire dans plus de 650 langues du monde et avec plus d’un million de caractères et symboles en tous genres, et même de prendre en charge les opérateurs et symboles mathématiques.

La très grande majorité des encodages ne permet d’écrire que dans deux ou trois langues différentes, et ce pour une raison très simple : plus la quantité de caractères supportés est importante et plus la quantité de bits que prendra chaque caractère est importante. Si vous ne saisissez pas, imaginez la même chose avec les adresses IP ; l’arrivée à saturation de l’IPv4 et donc l’usage de l’IPv6 qui permet beaucoup plus d’adresses mais dont les adresses sont en conséquence plus longues.

L’Unicode a trouvé une solution à ce problème : le multi-octet. Ce procédé permet de concurrencer des normes obsolètes telles que l’ASCII (un octet par caractère) en terme de taille sur les mêmes caractères, et permet en même temps d’écrire en chinois, en hébreu ou en russe avec des caractères pesant jusqu’à 6 octets, en toute cohabitation avec ceux n’en pesant qu’un seul.

Le multi-octet, des avantages mais aussi des inconvénients

La majorité des normes d’encodage ont la particularité d’occuper une quantité prédéfinie de bits par caractères, de sorte que l’on peut traiter des chaînes de caractères plus rapidement car on se base sur une quantité de bits identique pour tous les caractères d’une même chaîne.

Pour exemple, et en simplifiant à l’extrême, disons que nous voulons supprimer le 8ième caractère d’une chaîne de caractères, il suffit alors de multiplier par 7 le nombre de bits qu’occupe chaque caractère en se référant à l’encodage en cours et de supprimer les X bits suivants (où X correspond à nouveau à la quantité de bits que l’encodage en cours consacre par caractère).

L’Unicode n’assigne pas de quantité prédéfinie de bits par caractère, on l’appelle « multi-octet », il peut disposer de caractères de différentes tailles. L’avantage est que l’occupation d’espace par chaque caractère est donc plus optimisée que les encodages à deux octets par caractères ou plus. Ainsi, chaque caractère peut occuper entre 1 et 6 octets en Unicode UTF-8.

L’inconvénient est, en se basant sur le même exemple très simplifié que précédemment, qu’il faudra désormais plus de temps pour connaître la position du 8ième caractère de notre chaîne ainsi que le nombre de bits à supprimer. Il faut « lire » caractère par caractère pour connaître la quantité de bits que ces derniers occupent, jusqu’au caractère à supprimer, vérifier combien de bits ce dernier occupe et enfin supprimer les bits en question.

Ce traitement est effectivement un peu plus long sur une chaîne de caractères multi-octets que sur une chaîne à caractères mono-octets, et l’appellation « format auto-synchronisé » qu’on attribut parfois à l’UTF-8 ne change rien à cette latence.

Cet inconvénient sur le temps de traitement est mineur et est très largement compensé par les avantages certains de l’Unicode. De plus, la version 6 de PHP amène le support complet de l’Unicode et des chaînes de caractères multi-octets, de cette façon PHP impose aux migrateurs un temps de traitement plus long des chaînes de caractères. C’est la raison principale pour laquelle PHP 6 peut s’avérer moins performant que son prédécesseur, mais je pense au contraire que l’apport natif de l’Unicode est l’une des plus grandes et des plus belles performances de PHP 6.

Tutoriel pour faire fonctionner vos applications Web en Unicode

Pré-requis :

  • Si vous êtes sur une plateforme Windows : Windows 2000 ou ultérieur.
  • Apache 1.3 ou ultérieur sur plateforme Unix ; Apache 2.0 ou ultérieur sur plateforme Windows NT.
  • PHP 5.0 avec mbstring ou ultérieur (à partir de PHP 6.0 vous n’avez plus besoin d’utiliser mbstring).
  • MySQL 4.1 ou ultérieur.
  • Si vous utilisez un gestionnaire de bases de données MySQL, il faut que ce dernier vous permette de définir les interclassements pour les connexions SQL, les bases et les tables (phpMyAdmin par exemple).

À partir de là vous êtes paré pour vous lancer. Vous verrez qu’après tout c’est relativement simple, il suffit de suivre correctement les trois étapes primordiales :

  1. Encoder vos documents
  2. Paramétrer PHP et Apache
  3. Définir l’encodage de MySQL

Encoder un document en Unicode UTF-8

Via votre éditeur de texte modifiez l’encodage de votre document pour « Unicode UTF-8 », si vous en avez l’option choisissez le formulaire de normalisation Unicode C (C - Décomposition canonique suivie d'une composition canonique). N’incluez pas de signature Unicode (BOM).

Petite parenthèse sur la BOM, ou Byte Order Mark, cette dernière permet d’indiquer l’orientation des octets (big-endian ou little-endian) en tête du document. Seulement voilà, la BOM est très peu reconnue et pour cause si vous ne la supprimez pas PHP vous retournera des erreurs du type « Des en-têtes ont déjà été envoyés… » lorsque vous essayerez d’envoyer le moindre en-tête. Il est donc plus pertinent d’utiliser un marquage explicite dans vos en-têtes, par exemple « charset = UTF-16LE », où LE signifie Little Endian.

Question : « Je n’ai pas ces options dans mon éditeur de texte, que faire ? »
Réponse : Votre éditeur de texte ne supporte que partiellement l’encodage Unicode, vous n’aurez probablement pas d’autres solutions que d’utiliser un autre éditeur de texte le supportant nativement et dans sa totalité. Des éditeurs tel que le Bloc-notes ou Notepad++ ne supportent malheureusement pas correctement l’Unicode UTF-8, vous devrez donc faire appel à un logiciel tel qu’Adobe Dreamweaver afin d’effectuer cette tâche. Voici une liste non-exhaustive de logiciels vous permettant d’encoder vos documents en Unicode :

  • Adobe Dreamweaver — Logiciel propriétaire et payant disponible pour Windows et Mac OS X, il permet de créer et de convertir des documents en Unicode UTF-8 sans BOM et avec le formulaire de normalisation C.
  • SciTE — Logiciel libre et gratuit disponible pour Windows et Linux, il permet de créer des documents en Unicode UTF-8 sans BOM et avec le formulaire de normalisation C.
  • Emacs — Logiciel libre et gratuit disponible pour Windows, Linux et Mac OS X, il permet de créer des documents en Unicode UTF-8 sans BOM et avec le formulaire de normalisation C.
  • UniRed — Logiciel libre et gratuit disponible pour Windows, il permet de créer des documents en Unicode UTF-8 sans BOM avec le formulaire de normalisation C.

Notez que le choix du formulaire de normalisation n’est pas toujours disponible dans les logiciels présentés ci-dessus. Cependant étant donné que le formulaire de normalisation C (Décomposition canonique suivie d'une composition canonique) est utilisé par défaut nous considérerons ces derniers envisageables.

Note : « encoder » ne signifie pas « convertir », pour convertir un document existant il vous faut un logiciel approprié qui soit capable de faire les liaisons entre chaque encodage et les correspondances entre les caractères équivalents de l’encodage source à l’encodage destinataire. Dans le monde des éditeurs de texte Adobe Dreamweaver fait partie des logiciels aux grandes capacités de conversion sans perte.

Une fois votre document correctement encodé, il n’est pas inutile de préciser le nouvel encodage s’il s’agit d’une page HTML dans la balise META :

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

Si vos documents XHTML possèdent un prologue XML, indiquez là aussi l’encodage :

<?xml version="1.0" encoding="UTF-8" ?>

Pour les fichiers CSS externes, précisez au tout début du document : @charset "UTF-8";
Ainsi qu’au moment de leur inclusion dans le code HTML :

<link rel="stylesheet" type="text/css" media="all" charset="UTF-8" href="/Dossier /Style.css" />

Si vous envoyez le « Content-type » dans les en-têtes via PHP, précisez là-aussi le « charset ».

header('Content-type: text/html; charset=UTF-8');

Faites de même pour vos documents JavaScript, CSS, XML, etc. pour qu’ils soient tous encodés en Unicode UTF-8 sans BOM et avec le formulaire de normalisation C.

Notez que les navigateurs choisissent l’encodage à utiliser selon des priorités :

  1. Utilisateur — Si l’utilisateur choisi via son navigateur un encodage spécifique à utiliser c’est ce dernier qui sera utilisé et aucun autre
  2. En-tête — En-tête envoyé par Apache. Si vous spécifiez un en-tête via PHP header() c’est ce dernier qui aura la priorité sur celui d’Apache
  3. Balise META — Si aucun en-tête n’a été envoyé par le serveur c’est l’encodage spécifié dans la balise META qui sera utilisé (ou dans le prologue XML si présent)

Paramétrer PHP et Apache

Tous les paramètres que nous avons besoin de modifier dans PHP sont modifiables via PHP_INI_ALL ou PHP_INI_PERDIR, ce qui signifie que nous pouvons directement les modifier depuis un fichier .htaccess, exactement ce dont nous avons besoin ! Profitons du même fichier .htaccess pour paramétrer Apache et ainsi faire d’une pierre deux coups.

Créez un fichier nommé .htaccess à la racine de votre site et insérez-y le code ci-dessous :

# Apache › En-têtes Unicode
AddDefaultCharset UTF-8

# PHP › mbstring
php_value mbstring.internal_encoding UTF-8
php_value mbstring.http_output UTF-8
php_value mbstring.http_input UTF-8
php_value mbstring.func_overload 7
php_flag mbstring.encoding_translation On
php_value mbstring.language Neutral
php_flag mbstring.strict_detection On
php_value mbstring.substitute_character UTF-8

# PHP › iconv
php_value iconv.internal_encoding UTF-8
php_value iconv.output_encoding UTF-8
php_value iconv.input_encoding UTF-8

Bien entendu, le fichier .htaccess lui-même doit être encodé en Unicode, cela va de soit :)

Notez que le paramétrage mbstring.func_overload permet de remplacer la totalité des fonctions de PHP ne supportant pas les chaînes multi-octets* (Unicode) par leur équivalent mbstring. Par exemple mb_substr() remplacera substr().

* C'est-à-dire les fonctions supportant les chaînes binaires, et non celles traitant les caractères en tant que tel, mais en tant qu’une quantité prédéfinie de bits. Elles sont nombreuses et en voici la liste, mbstring les remplace toutes par leur équivalent, ce qui rend ces dernières incompatibles avec les chaînes binaires :

	[mail] => mb_send_mail
	[strlen] => mb_strlen
	[strpos] => mb_strpos
	[strrpos] => mb_strrpos
	[stripos] => mb_stripos
	[strripos] => mb_strripos
	[strstr] => mb_strstr
	[strrchr] => mb_strrchr
	[stristr] => mb_stristr
	[substr] => mb_substr
	[strtolower] => mb_strtolower
	[strtoupper] => mb_strtoupper
	[substr_count] => mb_substr_count
	[ereg] => mb_ereg
	[eregi] => mb_eregi
	[ereg_replace] => mb_ereg_replace
	[eregi_replace] => mb_eregi_replace
	[split] => mb_split

Il se peut donc qu’il y ait des problèmes avec les chaînes binaires en utilisant mbstring.func_overload. Vous avez deux solutions, ne pas utiliser mbstring.func_overload et ainsi utiliser constamment les fonctions équivalentes mb_xxx, et lors de traitements binaires utiliser les fonctions habituelles. La deuxième solution consiste à créer une fonction qui désactive temporairement mbstring.func_overload, traite votre chaîne binaire, et réactive mbstring.func_overload. Pour plus d’information voyez ce commentaire (anglais).

Pour aller plus loin, spécifiez UTF-8 comme encodage pour les fonctions qui le supportent. Par exemple pour la fonction htmlspecialchars($varaible) utilisez plutôt htmlspecialchars($variable,,"UTF-8"); en vous référant au manuel PHP pour connaître les fonctions compatibles avec ce genre de spécification.

Note : plus haut nous avons utilisé un fichier .htaccess pour paramétrer PHP et Apache, pour ce dernier il n’y a en général aucun problème et c’est ainsi qu’il faut procéder. Cependant pour PHP il est possible — selon votre hébergement et selon le mode de fonctionnement de PHP (CGI, CLI) et selon la configuration d’Apache (AllowOverride dans httpd.conf (ou apache2.conf sur Linux)) — que vous ne puissiez pas passer par un fichier .htaccess, dans ce cas modifiez directement le fichier php.ini, ou si vous êtes en hébergement mutualisé via un fichier php.ini local comme le permet certains hébergeurs tel que 1&1 (anglais) ou Site5 (anglais).

Définir l’encodage pour MySQL

C’est probablement la partie la plus facile. Dans un premier temps rendez-vous donc sur votre panneau phpMyAdmin, ou en l’occurrence sur l’interface de votre gestionnaire de base de données.

Attention ici il faut utiliser la graphie « utf8 » (sans tiret) qui est propre à MySQL et non « utf-8 » (avec tiret).

  • Selon votre version de phpMyAdmin vous serez en mesure ou non de modifier l’encodage de l’interface de ce dernier, si vous êtes en mesure alors spécifiez UTF-8 Unicode (utf8_unicode_ci), sinon c’est probablement la valeur par défaut et elle est inchangeable.
  • Modifiez l’interclassement pour la connexion MySQL : utf8_unicode_ci
  • Définissez le jeu de caractères (interclassement) de toutes vos tables et tous vos champs de sorte qu’il soit utf8_unicode_ci.
Note : Si vos champs n’ont pas d’interclassement spécifié il suffit de modifier celui de la table, si cette dernière n’en a pas il suffit de modifier celui de la base, les tables et champs prenant l’interclassement de la base par héritage, sauf indication contraire. Note : Nous déconseillons l’utilisation de l’interclassement utf8_general_ci car ce dernier est incohérent lors de comparaisons. Il est vanté d’être plus rapide par MySQL mais porte à confusion puisqu’il vous trompera lors de comparaisons ou de recherches dans vos champs. Pour plus d’information voyez la documentation MySQL à ce sujet (anglais). Notez aussi que utf8_general_ci est insensible à la casse lors de comparaisons tandis que utf8_unicode_ci est sensible à la casse.

Pour terminer, après chaque connexion MySQL (mais avant vos requêtes) définissez à nouveau l’encodage à utiliser pour la connexion courante :

mysql_query("SET NAMES 'utf8'");

Quelques exemples pratiques

Créer une base de données en spécifiant l’encodage :

CREATE DATABASE nom_de_la_base 
CHARACTER SET utf8 
COLLATE utf8_bin;

Créer une table en spécifiant l’encodage :

CREATE TABLE nom_de_la_table (
    texte VARCHAR(30) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
) CHARACTER SET utf8;

Identifier rapidement la source d’un problème d’encodage sur une page

  • Des caractères « Ã » apparaissent sur la page : les données sont encodées en Unicode mais le navigateur les interprète comme des données encodées en ISO.
    • Changez l’encodage de la page en Unicode via votre navigateur, si l’affichage est devenu correct alors la théorie se confirme et il s’agit bien d’un mauvais en-tête envoyé par Apache ou par PHP via header().
  • Des caractères « � » apparaissent sur la page : les données sont encodées en ISO mais le navigateur les interprète comme des données encodées en Unicode.
    • Changez l’encodage de la page en ISO via votre navigateur, si l’affichage est devenu correct alors la théorie se confirme et il s’agit bien d’un mauvais encodage des caractères. Ceci peut provenir de plusieurs niveaux ; base de données, traitement PHP, encodage des fichiers bruts.

      Si vous avez suivi et appliqué ce tutoriel sur un site déjà existant sachez qu’il faut convertir les données. Que ce soit un fichier brut ou bien les données d’une base de données, il ne suffit pas de changer l’encodage, il faut convertir toutes les données en Unicode.

      Les meilleurs éditeurs de texte se chargent de ré-encoder automatiquement les données d’un fichier lorsque vous modifiez l’encodage, tel qu’Adobe Dreamweaver par exemple. Pour une base de données c’est un peu plus compliqué. Il existe plusieurs possibilités pour convertir les données d’une base de données déjà existante ; l’une d’elles consiste à utiliser iconv que nous ne détaillerons pas ici. Une autre possibilité plus facile à mettre en œuvre pour certains consiste à exporter la base de données dans un fichier que vous convertirez ensuite avec un éditeur de texte en Unicode avant de le réimporter dans MySQL. Cette dernière solution est moins sujette à des erreurs d’encodage.

Un petit point sur les éditeurs de texte

Un éditeur de texte basique peut être considéré comme suffisant à partir du moment où vous débutez une nouvelle application Web, vous n’aurez qu’à spécifier l’encodage à utiliser et l’éditeur de texte se chargera d’enregistrer vos données dans cet encodage.

Si votre désir est de convertir un document déjà existant, un éditeur de texte basique ne suffit plus. La raison est simple, pour être capable de convertir des données d’un encodage à un autre, un éditeur de texte doit être capable de faire les liaisons entre les encodages et reconnaître les équivalents de chaque caractère de l’encodage source vers l’encodage destinataire.

Il n’est pas rare lors de conversions d’encodages de perdre des caractères si des outils peu appropriés sont utilisés. Pour exemple, la conversion de caractères tels que « œ » (e-dans-l’o) ou encore « Ç » (c-cédille majuscule) se résultera par des caractères inadéquats ou une perte pure et simple des caractères une fois convertis. L’utilisation d’outils plus avancés évite bon nombre de ces problèmes (pour le citer une dernière fois, Adobe Dreamweaver fait partie de ces outils).

Conclusion

Même si cela peut sembler un peu ardu de faire fonctionner vos applications pleinement en Unicode UTF-8, les avantages que représente cette évolution sont infinis. En optant pour l’Unicode vous mettez un terme à tous les problèmes d’encodage et pourrez traiter tous les caractères du monde, et ce quelle que soit la plate-forme, quel que soit le logiciel et quelle que soit la langue.

L’Unicode est l’aboutissement ultime de tous les moyens de communication aujourd’hui et le restera dans le futur. L’encodage Unicode UTF-8 a un potentiel de plus de deux milliards de caractères (2^31-1) dont seule une infime partie n’est actuellement utilisée (un peu plus d’un million).

Documentation supplémentaire sur l’Unicode UTF-8 :

Une erreur s’est glissée dans cet article ? Faites en part à l’auteur à dev@yok.fr.
Document publié sous licence Creative Commons