Dimanche 20 Juillet 2008 à 08h17::AutresJeux de caractères et encodage des documents
Si je ne devais donner qu'un conseil à un développeur, avec toute l'humilité que je me dois d'avoir, ce serait de prendre en considération dès la conception d'une application le jeu de caractères approprié à la localisation du produit, et de mettre place tous les moyens possibles pour s'y tenir.
Grossièrement, un jeu de caractères (charset en anglais) est une table de codage associant la représentation d'un caractère sous une forme compréhensible par un ordinateur, qui lui ne comprend que des suites binaires [01].
La première table utilisée fut l'ASCII. Codée sur huit bits, elle ne permettait la translation que de 128 caractères, oubliant par là même les langages utilisant des signes diacritiques. Cette limite due rapidement être résolue et apparurent alors de nouvelles tables de code suivant le langage utilisé.
Si un développeur, quel que soit le langage de programmation pratiqué, n'est pas sensibilisé au codage des documents, il risque d'être rapidement confronté à des situations tordues, à même d'aboutir à un imbroglio abscons particulièrement difficile à rétablir.
Maintes fois je fus confronté à des problèmes de conversion de caractères, les différentes couches applicatives ne « parlant pas la même langue ».
L'une de ces situations critique remonte à quelques années, à une époque où je n'étais pas vraiment au courant des bonnes pratiques de codage et où le jeu de caractère avait été omis lors de la création de la base de données.
Malgré de nombreuses tentatives, jamais je n'ai été en mesure de reconnaître le codage d'origine. Nous passions en ce temps là pour la connexion à la base de données par le composant BDE (Borland Database Engine, une sorte de couche ODBC propriétaire abandonnée depuis 2001). L'absence de définition de jeu de caractères aboutissait régulièrement à de grossières erreurs d'affichage, voire des « plantages » lors des mises à jour de la base, lorsque le programme chargé de cette opération entrait en conflit à la lecture de certains caractères insérés via un portail web.
La majorité de ces erreurs découlait d'un copier-coller à partir d'un document Word dans une zone de texte du portail. Voici donc le problème : Word utilise de l'UTF (8, 16, ou 32, je n'en sais rien), le portail était décrit comme utilisant de l'ISO-8859-1 (la norme à l'époque). Pour les communications avec la base via BDE, c'est le flou total, et enfin la base de données (Interbase) n'avait aucune notion de collation...
Repartir sur de solides fondements fut un travail épique (et d'équipe) particulièrement long et parsemé d'embûches, dont nous venons à peine de sortir (en supprimant au passage l'utilisation du composant TBatchMove pour le traitement de la base de données).
Autre exemple, la récente installation de l'éditeur « PHP Zend Studio For Eclipse » a littéralement traduit - sans demander l'avis de qui que soit - mes documents XML au format UTF-8, bien que que ceux-ci avaient été créés au format Windows-1252. Comble de l'absurdité, ces documents avaient été créés avec l'ancienne version de Zend Studio (version 5). Seule solution, définir un par un le codage des documents dans l'éditeur et balayer le contenu pour remplacer les caractères incorrectement traduits lors de la conversion du fichier.
Enfin, très récemment, j'ai dû importer une base de données MYSQL 4 vers MySQL 5. Même si le codage des tables avait été précisé, il se trouve que le ficher SQL de création et insertion des valeurs utilisait l'UTF-8. L'import s'est donc mal déroulé, et tous les caractères accentués furent perdus. Heureusement, l'utilisation du petit (mais puissant) utilitaire notepad2 autorise rapidement la conversion du jeu de caractère. Après avoir traduit le format de fichier UTF-8 vers ANSI, un nouvel import et le problème fut résolu.
Bref, sans rentrer dans des considérations complexes dont je ne maîtrise pas tous les rouages, ce dont je suis sûr, c'est qu'il faut indiquer explicitement le jeu de caractère utilisé dans toutes les « briques » d'une application.
Il serait par exemple incohérent d'utiliser un jeu de caractère ISO-8859-1 dans vos pages HTML tandis que la base de données emploierait de l'UTF-8. Le risque de retrouver de nombreux « pâtés » ou la substitution d'un caractère par un autre serait grand.
En outre, un des problèmes courant avec l'utilisation de l'objet XML Http Request (XHR) est la transformation de certains caractères ISO-8859-1, comme « é », par un ensemble insolite, « é », provenant d'un transtypage au format UTF-8. A l'origine, un bogue de Firefox présent dans les versions 1.5 oblige la traduction de la réponse serveur au format UTF-8, l'objet XHR ne tenant pas compte de l'entête "content-type" renvoyé par le serveur. En PHP, il faut donc utiliser la fonction utf8_encode() avant de transmettre une réponse au client.
Apparemment, il existe une solution pour pallier à ce problème en utilisant la méthode "overrideMimeType" :
xhr.overrideMimeType("text/html; charset=ISO-8859-1");
Mais je n'ai pas encore eu le temps de tester son efficacité.
[EDIT 22/07/08] : les tests sur cette technique ne sont pas concluants. Malgré la surcharge du jeu de caractère, Firefox (v3 comprise) et IE 7 attendent toujours un résultat encodé en UTF-8.
Pour éviter tous ces problèmes, il faut se forcer à être cohérent sur l'ensemble de l'application et spécifier la « langue » utilisée sur tous les éléments.
Prenons l'exemple du charset ISO-8859-1, simplement parce qu'il s'agit de celui employé dans mes applications. Une note au passage, il s'agit là d'une grossière erreur car ce dernier ne reconnaît, entre autres, ni le caractère oeillet (« oe ») ni le symbole euro. En lieu et place, il faudrait utiliser son extension ISO-8859-15.
Au niveau du code HTML, déclarez le jeu de caractères dans la balise META appropriée :
<meta http-equiv="Content-type" content="text/html;charset=iso-8859-1"/>
Si cette dernière n'est pas précisée, le navigateur web ne sera pas en mesure de détecter le jeu de caractère utilisé dans le document, pouvant amener à présenter du texte partiellement illisible.
Au niveau de PHP, avant toute sortie écran, indiquez le via la fonction d'envoi des entêtes :
header('Content-type: text/html; charset:iso-8859-1');
Attention : ne fixez pas le jeu de caractère directement dans le fichier de configuration php.ini (directive "default_charset"). Après quelques tests, il semble impossible de surcharger cette valeur avec la fonction "header()" par la suite.
Au niveau de votre base de données, lors de la création de la base :
CREATE DATABASE `mabase` DEFAULT CHARACTER SET latin1...
Dans vos documents XML :
<?xml version="1.0" encoding="ISO-8859-1"?>
Dans vos CSS, au niveau de la feuille de style, en première ligne obligatoirement, ajoutez :
@charset "ISO-8859-1";
Dans vos CSS, au niveau de la déclaration de la ressource externe :
<link charset="ISO-8859-1" rel="stylesheet".../>
Vous pouvez également configurer le serveur web pour automatiquement envoyer le bon jeu de caractère. Ainsi, avec Apache, dans un fichier .htaccess, utilisez la syntaxe suivante :
AddType 'text/html; charset=ISO-8859-1' html
Concernant IIS, dans le manager, utilisez l'onglet « Entêtes HTTP », bouton « Types de fichiers » pour associer un jeu de caractères spécifique à une extension de fichier.
Pour vérifier l'envoi des entêtes d'un serveur, je ne saurai trop vous conseiller l'excellente extension pour Firefox "Live HTTP Headers". Vous avez aussi cette possibilité d'analyse via le site Mozilla Web Sniffer.
D'ailleurs, testez ce dernier avec le site google.fr, puis le site google.com. Vous constaterez dans le premier cas l'utilisation du charset ISO-8859-1, dans le second cas, UTF-8.
Maintenant, que se passe-t-il si un serveur (via Apache, IIS, PHP etc...) renvoie un jeu de caractère différent de celui décrit dans la balise META « content-type »?
Et bien le navigateur prendra en considération le jeu renvoyé par le serveur web dû à la précédence de l'information : l'entête du serveur est envoyé avant le document. Cela peut présenter un bel avantage si le site sur lequel vous travaillez est statique et composé de nombreuses pages incluant la balise META « content-type ». La déclaration au niveau serveur permet de répondre facilement à un conflit sans avoir à modifier l'ensemble des documents.
Au fait, j'ai pris l'exemple du jeu ISO-8859-1 car il s'agit de celui utilisé dans mes applications. Mais si vous commencez un nouveau projet, utilisez Unicode.
Conscient depuis longtemps des problèmes engendrés par le principe des jeux de caractères (surtout avec l'avènement d'Internet et l'internationalisation de la portée d'un document), UNICODE vient à la rescousse pour simplifier le système en intégrant tous les signes utilisés dans le monde.
Un exemple de la complexité d'un jeu de caractère pour les développeurs : saviez-vous que la norme LATIN-1 est un alias de la norme ISO-8859-1 mais qu'au même titre, le codage windows 1252 n'est pas conforme dans son intégralité à la norme ISO-8859-1 (bien que cette idée soit largement répandue), et que l'alias « french » utilisé en syntaxe de collation des bases de données SQL Server est un alias de la plage de code 1252?
Difficile - et absurde - donc d'être sensibilisé à toutes les conventions, règles, normes, nommage et portabilité des jeux de caractères. Unicode se veut la solution à ces problèmes. Il est la représentation d'un jeu de caractère universel, définissant en un même ensemble tous les caractères utilisés de part le monde.
Remarquez également que PHP emboîte le pas avec sa prochaine version 6, prévue pour être compatible en natif à 100% Unicode, ceci expliquant certainement le retard pris pour sa sortie. Imaginez la complexité du travail nécessaire à la conversion des fonctions de comparaison / tri sur les chaînes...
Enfin, au niveau de PHP, si vous êtes confronté à des problèmes d'encodage des caractères, tentez votre chance avec les fonctions de chaînes MB_STRING. Avec un zeste de réussite, vous pourrez rapidement convertir un jeu vers un autre pour rendre votre système d'information cohérent.
Quelques références dont je recommande éminemment la lecture et m'ayant servi de base à la rédaction de ce billet :
Lien permanent |
|
Stumble It!
Jeudi 17 Juillet 2008 à 06h03::Navigateurs | Serveur webCookie, nom d'hôte et Internet Explorer
Bon à savoir : Internet Explorer refuse la création d'un cookie si le nom d'hôte / domaine du serveur web ne répond pas aux conventions de nommages spécifiées dans la norme rfc952.
Par exemple, si le nom d'hôte du serveur contient un underscore (i.e. "mon_serveur.domaine.com"), toutes les versions d'IE à partir de la 5.5 refuseront explicitement la création du cookie entre le client et le serveur. Firefox quant à lui ne pose pas ce genre de problème.
Ce qui pourrait être apparenté à un bogue du navigateur n'en est finalement pas un et résulte d'une action volontaire pour répondre à une faille de sécurité. Celle ci permettait à un utilisateur malveillant de prendre possession des cookies stockés sur un ordinateur client et d'en modifier les valeurs.
A la découverte de ce problème Microsoft a diffusé un correctif pour ses navigateurs (MS01-055) interdisant les serveurs à la syntaxe incorrecte de mettre en place des cookies.
Cette correction prévaut aujourd'hui dans toutes les versions d'Internet Explorer.
La seule solution valable si vous êtes confronté à ce problème est de changer le nom du serveur. Un "nom" (d'hôte, de domaine...) ne doit pas excéder 24 caractères, eux mêmes devant être compris dans les plages [A-Z], [0-9], signe moins (-) et le point (.).
Notez pour les utilisateurs de Reporting Services que ce dernier peut ne pas fonctionner pour les mêmes raisons, vu qu'il utilise les cookies pour établir une session entre le client et le serveur.
Lien permanent |
|
Stumble It!
Mercredi 16 Juillet 2008 à 05h49::SQLContrainte d'unicité sensible à la casse
Les contraintes en SQL servent à assurer l'intégrité des données d'une base, dont voici quelques possibilités :
- contraintes "non null" pour interdire une saisie vide sur la valeur d'une colonne,
- contraintes de vérifications "check" permettant de contrôler une expression utilisateur,
- contraintes de clés primaires (un mixe des contraintes d'unicité et "non null"),
- contraintes de clés étrangères pour maintenir l'intégrité référentielle (liaisons entre les tables),
- et enfin les contraintes d'unicité permettant de garantir que les données d'un groupe de colonnes sont singulières rapport aux autres rangées de la table.
Dès qu'une contrainte n'est pas respectée, le SGBD lève une erreur et stoppe le traitement SQL en cours si le lot de requêtes est englobé dans une transaction.
Dans le cadre d'un import de fichier CSV avec association entre les données utilisateurs et certaines tables de paramétrages de la base, je fus confronté à un bogue lié à une contrainte d'unicité.
Prenons la structure de table suivante :
CREATE TABLE import_civilite (
code_import_civilite integer NOT NULL IDENTITY(1,1),
code_civilite NOT NULL,
lib_civilite VARCHAR(20) -- Valeur surévaluée car en provenance du fichier utilisateur
);
Le champ "code_import_civilite" est la clé primaire de la table. Il s'agit d'un index en mode d'auto incrémentation (IDENTITY étant la syntaxe SQL Server équivalente à l'option AUTOINCREMENT de MySQL).
La colonne "code_civilite" représente la clé étrangère de la table des civilités avec lesquelles doivent être consolidées les différentes valeurs "civilité" trouvées dans le fichier CSV à importer.
Enfin la colonne "lib_civilité" est le libellé trouvé dans le fichier CSV que l'utilisateur associe dans l'interface graphique de l'import à la table des civilités de la base.
Le fonctionnement de ce système nécessite donc une contrainte d'unicité sur les colonnes "code_civilite" et "lib_civilite" pour assurer l'absence de doublons. Pour résumer, les contraintes suivantes sont associées à la table "import_civilite" :
ALTER TABLE import_civilite
ADD CONSTRAINT pk_import_civilite PRIMARY KEY(code_import_civilite),
ADD CONSTRAINT fk_import_civilite FOREIGN KEY(code_civilite) REFERENCES civilite.code_civilite,
ADD CONSTRAINT u_import_civilite UNIQUE ON(code_civilite, lib_civilite);
Lors de l'import, le programme distingue deux libellés de civilité pour lui différentes, mais identiques pour le SGBD :
INSERT INTO import_civilite(code_civilite, lib_civilite) VALUES (1, 'MR');
INSERT INTO import_civilite(code_civilite, lib_civilite) VALUES (1, 'Mr');
La différence porte donc sur la casse. Lors de l'exécution successive de ces deux requêtes, SQL Server génère une exception sur la contrainte UNIQUE lors de l'exécution de la seconde requête, spécifiant une erreur de doublon.
Deux solutions sont alors envisageables :
1. Lancer un post traitement sur le fichier d'import et appliquer une transformation UPPERCASE sur les libellés nécessitant consolidation,
2. Modifier la structure de la table pour forcer la distinction entre deux chaînes différentes par leur casse.
Pour modifier la structure de la table, nous devons jouer sur la collation (options de configuration des données alpha-numériques lors des opérations de tri et de comparaison).
Il s'agit d'utiliser la syntaxe COLLATE sur une colonne suivi d'un code représentant le jeu de caractère utilisé et les options de sensibilité liées à la casse et à la gestion des signes diacritique.
Pour ma problématique, nous devons préciser l'influence de la casse et des accents. SQL Server distingue quatre mots clés :
- CS (Case Sensitive) ou CI (Case Insensitive)
- AS (Accent Sensitive) ou AI (Accent Insensitive)
La solution consiste à définir lors de la création de la table le comportement du SGBD vis à vis de la colonne "lib_civilite" :
CREATE TABLE produit (
code_import_civilite integer NOT NULL IDENTITY(1,1),
code_civilite NOT NULL,
lib_civilite VARCHAR(50) COLLATE French_CS_AS
);
Dorénavant, "Mr" est bien considéré comme différent de la chaîne "MR" et ne génère plus d'exceptions lors des insertions.
Notez qu'il était envisageable de supprimer la contrainte d'unicité et d'y préférer un ensemble de clés primaires sur les colonnes "code_civilite" et "lib_civilite" (en ayant pris soin de supprimer la colonne "code_import_civilite").
Cela n'aurait pas pour autant réglé le problème si la collation n'était pas précisée correctement.
Lien permanent |
|
Stumble It!
Mardi 15 Juillet 2008 à 05h51::NavigateursTester différentes versions d'Internet Explorer sur une même machine
L'installation du navigateur Internet Explorer ne laisse aucun choix au développeur pour conserver les anciennes versions sur son poste de travail. Lors du passage à IE 7, vous ne pouvez plus exécuter IE 6. Impossible pour autant de laisser de côté les tests clients : que ce soit au niveau des CSS ou de JavaScript, trop de différences subsistent entre les programmes pour croire qu'une application testée avec IE 7 fonctionnera sur les versions précédentes.
Pour pallier à ce problème, MicroSoft propose une solution : utiliser la version gratuite de Virtual PC 2004 et une image "bridée" du système d'exploitation XP SP2 sur laquelle IE6 est fourni. Malheureusement, cette solution présente quelques désavantages. Premièrement, il faut posséder une machine puissante, avec beaucoup de mémoire vive pour espérer travailler convenablement. D'autre part, l'image proposée est en anglais, avec le clavier en mode "qwerty", ce qui le rend désagréable à utiliser pour nous autres petits francophones. Autre attribut à fort caractère d'agacement, l'utilisation de l'image est limitée dans le temps (d'ici septembre 2008 pour les images disponibles lors de la rédaction de ce billet). Enfin, dernière contrainte de taille, Virtual PC 04 ne fonctionne pas sous Vista, obligeant à acquérir la version 2007.
Sous Windows XP, une solution plus ou moins efficace est apparue : Multiple IE permit enfin aux développeurs d'installer différentes versions d'IE sur leur poste.

Là encore, malheureusement, l'efficacité du produit est toute relative. Impossibilité d'utiliser les favoris, ils causent un plantage du programme, la gestion du cache semble hasardeuse, obligeant à de nombreux CTRL+F5 pour un rafraîchissement complet du code client. Néanmoins, nous finissions par nous en sortir.
Hélas, ce programme fort pratique est incompatible avec Windows VISTA. Après une rapide recherche, il existe une solution alternative via un nouveau programme baptisé IETester.

Dans ce dernier, il est possible d'ouvrir une session par onglet et utiliser IE 5, 6, 7... Cette version, toujours en béta, n'est pas dépourvue de bogues : arrêt inopiné du programme sans raison apparente et les problèmes de cache des fichiers JavaScripts / CSS sont monnaie courante.
Quoi qu'il en soit, il s'agit là de la seule solution du moment à ma connaissance. Malgré tout, je ne peux m'empêcher de rester sceptique en me demandant s'il s'agit d'un navigateur en tout point identique à la version originale. Donc pour le moment, mes tests se concluent toujours en prenant possession d'un PC équipé de Windows XP n'ayant pas effectué sa mise à jour vers IE7...
Lien permanent |
|
Stumble It!