Accueil
 chercher             Plan du site             Info (English version) 
L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.Les listes de discussions XMLfr sont à votre disposition pour réagir sur nos articles ou simplement poser une question.Si vous ètes passionnée(e) par XML, pourquoi ne pas en faire votre métier ?XMLfr n'est heureusement pas le seul site où l'on parle de XML. Découvrez les autres grâce à XMLfr et à l'ODP.Les partenaires grâce auxquels XMLfr peut se développer.Pour tout savoir sur XMLfr.XMLfr sans fil, c'est possible !Pour ceux qui veulent vraiment en savoir plus sur XML.L'index du site.
 Si vous vous posez une question, vous n'êtes peut-être pas le premier...Les traductions en français des bibles XML.Ces articles sont des références dans leur domaine.Tout ce qu'il faut savoir pour démarrer sur un sujet XML...


Encodages et XML::Parser

Cet article décrit ce qu'est un encodage, quel usage en est fait en XML, puis comment Perl les traite, avant de finir par quelques exemples pratiques d'utilisation de XML::Parser avec différents documents.

Michel Rodriguez
Somerville,  dimanche 7 mai 2000

Cet article a été publié sur XML.com sous le titre "Character Encodings in XML and Perl".

Introduction

Le sujet du jour est: encodages et Perl en général et XML::Parser en particulier. Cet article décrit ce qu'est un encodage, quel usage en est fait en XML, puis comment Perl les traite, avant de finir par quelques exemples pratiques d'utilisation de XML::Parser avec différents documents.

Un mot d'avertissement préliminaire: ceci est un sujet brûlant, qui génère moult débats enflammés sur les mailing listes XML, généralement entre les apôtres du "tout-Unicode" et les tenants du " je bosse moi, M'sieur!".

Encodages

Ah les encodages! La face cachée d'XML. Pour beaucoup, surtout aux US, XML est un format qui décrit des éléments, des attributs, et la manière de les ordonner proprement dans une jolie structure arborescente.

Mais en vérité je vous le dis, pour pouvoir stocker un texte, la première chose à faire c'est de définir son encodage. Le plus connu des encodages, du moins en occident est bien sûr ASCII, EBCDIC rappellera à certains d'entre vous le bon vieux temps où IBM et ordinateur étaient synonymes, tandis que Shift-JIS est l'un des encodages japonais et Big5 un des chinois.

Tous ces encodages ont en commun d'être largement... incompatibles. Ceci pour diverses raisons, la première étant que les langues occidentales peuvent se contenter de 256 caractères codés sur 8 bits, alors que bon nombre de langues asiatiques nécessitent bien plus, et donc requièrent un encodage sur plusieurs octets. Un standard récent tente d'unifier et de remplacer ces divers encodages: Unicode, alias ISO-10646 (en fait Unicode est le produit du consortium Unicode et ISO-10646 est publie par l'ISO, mais ces 2 standards sont largement compatibles et dans cet article nous les considérerons équivalents

Unicode et UTF-8

Unicode est un standard dont le but est de remplacer tous les autres standards par un encodage unique, extensible de surcroît, Unicode couvre tous les langages modernes, occidentaux comme asiatiques, ainsi qu'une large gamme de symboles mathématiques et techniques (plus besoin de GIFs pour les lettres grecques dans les standards IEEE!). Le mécanisme d'extension permet l'addition de nouveaux caractères (comme les caractères crées en chinois chaque année) ou nouveaux langages.

Sous Unix, Unicode est habituellement... encodé en UTF-8, une couche supplémentaire qui permet aux systèmes implémentant POSIX de le traiter sans (trop de) problèmes. UTF-8 est également rendu correctement par les browser web récents, à l'exception notable d'Opera. Pour afficher des caractères non-ASCII il faut cependant que les fontes Unicode installées incluent ces caractères. UTF-8 Sampler est une page contenant des textes dans différentes langues qui vous permettra de tester vos fontes.

La plupart des systèmes d'exploitation incluent des fontes Unicode, plus ou moins complêtes: par exemple, les fontes fournies par Windows 98 incluent exactement 1 caractère asiatique (Yen), mais affichent (de gauche a droite au lieu du sens inverse qui serait correct) les caractères hébreux , contrairement à ma station Solaris.

Au passage, pour le plus grand bonheur des anglais et américains, les caractères de base ASCII, ceux encodés de 1 à 127, ont le même code en UTF-8, Donc un texte ASCII anglais est aussi un texte UTF-8, sans conversion, ni traitement spécial... rien, ça marche!

Mon but dans cet article n'est pas de décrire Unicode en détail, n'étant pas un expert en la matière. Pour plus d'informations (en anglais), voir Unicode Home Page ou UTF-8 and Unicode FAQ for Unix/Linux.

Pour simplifier les choses sachez aussi qu'Unicode soulève certaines réticences en Asie: la fin de l'article Brief History of Character Codes offre une opinion intéressante. La plupart des outils traitant UTF-8 sont également limités à des caractères encodés sur au plus 2 octets alors que certaines langues en demandent plus.

Encodages et XML

La spécification XML, disponible sur le site du W3C ou, accompagnée des commentaires de Tim Bray sur XML.com ne prescrit aucun encodage particulier. Un document peut inclure n'importe quel encodage de la liste définie par l' IANA. (the Internet Assigned Numbers Authority).

Le seul problème est qu'un parser XML n'est obligé de reconnaître que 2 encodages: UTF-8 et UTF-16 (une autre méthode pour encoder les caractères Unicode sur 2 octets, surtout employée en Java).

Par exemple expat, le parser sur lequel XML::Parser est basé, reconnaît UTF-8, UTF-16 et ISO-8859-1 alias latin 1 qui couvre la plupart des langues ouest-européennes et africaines (à l'exception notable de l'arabe). Nous verrons comment l'étendre à d'autres encodages un peu plus tard.

La plus importante caractéristique d'expat est qu'il convertit tous les caractères en UTF-8 quel que soit l'encodage de départ du document, à condition qu'il soit reconnu. Une application basée sur expat recevra tous les textes en UTF-8.

Perl et Unicode

A partir de la version 5.6 Perl supporte UTF-8. Ce qui veut dire notamment qu'il devient possible d'utiliser les expressions régulières sans crainte que des caractères encodés sur plusieurs octets ne posent problème.

En fait toutes les fonctions qui traitent des caractères ont été mises à jour pour se comporter correctement avec des caractères multi-octets. Plus quelques extra, après tout Perl reste Perl. Pour plus de détails sur les nouvelles fonctionnalités de Perl (et leurs limitations) voir l'article de Simon Cozens: What's New in Perl 5.6.0?

Donc tout va bien ! Nos documents encodés en UTF-8 sont lus par expat, puis traités par correctement par Perl... facile non ? Pas tout à fait...

Le problème est que la plupart des applications XML sont interfaces avec d'autres logiciels, bases de données, traitement de texte... qui ne supportent pas Unicode et UTF-8. Ce qui force les applications XML à non seulement accepter des entrées en divers encodages mais aussi à produire des sorties de même, pour qu'elles soient utilisables par le reste de l'environnement.

C'est pourquoi nous allons maintenant détailler les outils Perl pouvant être utilisés pour transcoder des documents XML.

XML::Parser et Unicode

Le modèle de traitement imposé par XML::Parser est le même que celui d'expat: quel que soit l'encodage de départ les données sont transmises à l'application en UTF-8.

XML::Parser accepte nativement des documents en UTF-8, UTF-16 et ISO-8859-1. Pour pouvoir traiter des documents encodés différemment le module XML::Encoding lui ajoute d'autres tables d'encodages.

Pour pouvoir produire d'autres encodages en sortie il faut utiliser le module XML::UM, le module Unicode::String ou une substitution (tr///). Une autre option, risquée mais plus rapide, est d'utiliser la méthode d'XML::Parser qui fournit la chaîne que le parser a reconnue avant son transcodage en UTF-8.

Ajouter des encodages grâce a XML::Encoding

Le module XML::Encoding peut être utilisé pour ajouter de nouveaux encodages à XML::Parser.

XML::Encoding fournit des tables d'encodages utilisées par XML::Parser pour pouvoir parser des documents dans d'autres encodages. Le simple ajout de l'instruction "use XML::Encoding" donne accès a toutes les tables définies dans le répertoire source d'XML::Encoding.

La liste des encodages disponibles inclut notamment big5 (caractères chinois classiques), iso-8859-2 à 9 (qui couvrent toutes les langues européennes, le cyrillique, l'arabe et l'hébreu, quoique iso-8859-6, le jeu de caractères arabes semble poser problème), diverses variantes de x-euc-jp et s-sjis (2 encodages japonais, le fichier Japanese Encodings.msg donne plus de détails sur les variantes et la manière de les utiliser) et windows-1250. Le manque le plus crucial semble celui de GB, l'encodage des caractères chinois simplifiés utilisés en Chine continentale.

La méthode permettant de définir d'autres encodages est donnée dans la documentation d'XML::Encoding.

Le module Unicode::String

Le module Unicode::String permet la conversion entre caractères Unicode et latin 1:

$u= Unicode::String::utf8; # $utf8 est une chaîne UTF-8

$latin1= $u->latin1;

Unicode::String n'est plus nécessaire en Perl 5.6, l'opérateur tr suffit:

$string=~ tr/\0-\x{ff}//UC;

Le module XML::UM

Le module XML::UM utilise les tables d'encodage d'XML::Encoding pour effectuer l'opération inverse. Il crée des fonctions de transcodage d'UTF-8 vers l'encodage voulu.

Ce module est encore alpha mais il vaut la peine d'être essayé. Il est assez lent, et si un volontaire se propose pour le re-écrire en C il sera le bienvenu.

Note: la version d' XML::UM incluse dans libxml-enno-1.02 a un problème d' installation. Avant de faire perl Makefile.PL, éditer le fichier XML::UM.pm dans le répertoire lib/XML et remplacer la valeur de $ENCDIR par le répertoire contenant les tables d'encodage d' XML::Encoding (quelque chose comme /usr/local/src/XML-Encoding-1.01/maps ou /opt/src/XML-Encoding-1.01/maps/).

Un scénario typique se présente comme suit:

# créer la fonction de transcodage (une seule fois) 

$encode= XML::UM::get_encode( Encoding => 'big5');



# convertir la chaîne $utf8_en big5

$encoded_string= $encode( $utf8_string);

Le module XML::Code

Pour encoder des documents dans des langues européennes autres que l'anglais tout en s'assurant qu'ils pourront être au moins stockés proprement par des systèmes ne traitant que de l'ASCII-US (les caractères de 1 à 127), une méthode simple consiste à encoder tous les autres caractères dans des entités caractères XML (une entité dont le nom est le code Unicode du caractère, par exemple é devient é). Cette technique a une limite: les entités caractères sont interdites dans les noms d'éléments et d'attributs, qui devront donc utiliser les caractères ASCII de base. A ce détail prés cela vous permet de traiter les documents en UTF-8 sous Perl et de les stocker ou traiter également avec des logiciels non-Unicode.

Je ne connais pas de module CPAN offrant cette fonctionnalité, mais elle est proposée par XML::DOM, développé par Enno Derksen. J'ai donc extrait le code de DOM.pm et en ai fait un module rudimentaire, XML::Code. XML::Code est très simple, il encode des chaînes CDATA, PCDATA ou des balises. Il devrait en fait faire partie d'XML::UM, et y sera probablement intégré dans l'avenir.

La méthode original_string

Si rien d'autre ne marche, et si vous n'avez pas besoin d'utiliser des expressions régulières, il vous est possible d'utiliser la méthode original_stringd'XML::Parser.

Cette méthode, appelée depuis un handler, renvoie la chaîne de caractères reconnue par le parser, avant que les entités n'y soient résolues et qu'elle ne soit convertie en UTF-8. Au passage notons que la méthode recognized_string renvoie la chaîne de caractères reconnue après ces deux opérations.

Quelques détails ennuyeux:

  • expat doit pouvoir parser le document, ce qui implique la présence d'une table pour son encodage,

  • Si certains caractères sont encodés sur plusieurs octets le moteur d'expressions régulières de Perl risque d'avoir des problèmes (La seule exception à cette règle étant Perl 5.6 et Unicode),

  • Si vous utilisez des noms d'éléments, d'attributs ou même des valeurs d'attributs contenant des caractères non UTF-8 vous ne pouvez plus compter sur XML::Parser pour parser les balises et vous devrez écrire le bout de code qui extrait les différentes parties de la balise, avec le risque que le moteur d'expressions régulières ne soit perturbé par des caractères multi-octets.

Ces détails ne sont pas forcément dramatiques: beaucoup d'applications XML n'utilisent pas d'expressions régulières, après tout un document XML est déjà pré-découpé en éléments et les traitements qui lui sont appliqués consistent souvent à ré-organiser ces éléments et à modifier leurs propriétés, il est donc possible que vous puissiez vous accommoder de ces restrictions. A vos risques et périls cependant !

Exemples

Bon, il est temps de passer aux exemples pratiques. Ils ont tous le même but: prendre un document en entrée et le reproduire en sortie. Les deux documents sont identiques, à l'encodage près. Tous les scripts utilisent XML::Parser, sans style.

Ces exemples ne sont pas d'une grande utilité en eux-mêmes mais il vous donneront un point de départ pour traiter les problèmes d'encodage avec XML::Parser.

Note: cette section demande une connaissance minimum d'XML::Parser. Pour une introduction à ce module voir l'article de Clark Cooper: Using The Perl XML::Parser Module.

Les documents

Voici les 3 exemples que j'utilise, un en français, un en japonais et un en chinois., sous forme de graphiques afin de simplifier leur affichage :

Français:

Chinois:

Japonais:

Ces exemples sont déclinés en plusieurs versions, utilisant différents encodages.

Premier exemple (entrée et sortie en UTF-8)

Ce script (enc_ex1.pl) peut être appliqué aux documents suivants: doc_fr_utf.xml, doc_ch_utf.xml ou doc_jp_utf.xml.

Note: pour une raison indépendante de ma volonté le handler Default n'est pas activé pour les balises de début et de fin d'élément, les handlers Start et End doivent donc être définis.

#!/bin/perl -w

use strict;

use XML::Parser;

my $p= new XML::Parser( Handlers =>

                         { Start   => \&default,

                           End     => \&default,

                           Default => \&default

                         },

                      );

$p->parsefile($ARGV[0]);

exit;



# par défaut imprimer la chaîne de caractères convertie en UTF-8

  { my $p= shift;

    my $string= $p->recognized_string();

    print $string;

  }

Ce premier exemple ne fait pas grand chose, il lit le document en entrée et l'imprime en sortie, mais au moins il vérifie qu'il parse, qu'il est bien en UTF-8 et il résout les appels d'entités. Le but principal de ce script est de donner le cadre dans lequel nous allons intégrer les transcodages présentés dans les exemple suivants.

Le document en entrée est dans un encodage différent

Cet exemple est similaire au précédent, au détail prés qu'ici le script doit enlever la déclaration d'encodage de la déclaration XML de départ.

Vous pouvez utiliser ce script (enc_ex2.pl) avec les documents: doc_fr_latin1.xml (pas besoin d'utiliser XML::Encodings pour celui-là), doc_ch_big5.xml, doc_jp_sjis.xml ou doc_jp_euc.xml

#!/bin/perl -w

use strict;

use XML::Parser;

use XML::Encoding;

my $p= new XML::Parser( Handlers =>

                         { Start   => \&default,

                           End     => \&default,

                           Default => \&default,

                           XMLDecl => sub { decl( "UTF-8", @_); }

                         },

                      );

$p->parsefile($ARGV[0]);

exit;



# mettre à jour l'encodage

sub decl

  { my( $new_encoding, $p, $version, $encoding, $standalone)=@_;

    print "<?xml version=\"$version\" encoding=\"$new_encoding\"";

    print "standalone=\"$standalone\"" if( $standalone);

    print "?>\n";

  }



# par défaut imprimer la chaîne de caractères convertie en UTF-8

sub default

  { my $p= shift;

    my $string= $p->recognized_string();    

    print $string;

  }

Traiter des documents HTML

Cet exemple consiste a parser un document ASCII (donc également UTF-8) comportant des entités HTML (par exemple &eacute;). Ce genre de problème se pose fréquemment pour des documents en XHTML, ou en "HTML-plus".

Les entités HTML ne sont pas pré-définies en XML, il faut donc les déclarer. Pour cela j'utilise un fichier, html2utf8.ent, constitué à partir des différentes déclarations associées à la DTD  HTML du W3C.

Voici le script (enc_ex3.pl), toujours très simple:

#!/bin/perl -w

use strict;

use XML::Parser;

my $p= new XML::Parser( Handlers =>

                         { Start => \&default,

                           End   => \&default,

                           Char  => \&default, 

                         },

                        ParseParamEnt => 1

                      );

$p->parsefile($ARGV[0]);

exit;



# par défaut imprimer la chaîne de caractères convertie en UTF-8

sub default

  { my $p= shift;

    my $string= $p->recognized_string();

    print $string;

  }

Document d'entrée Unicode, sortie en latin 1

Le script (enc_ex4.pl) peut être écrit de 2 manières différentes suivant que vous utilisiez Perl 5.6 ou une version plus ancienne. Vous pouvez l'utiliser sur le document doc_fr_latin1.xml.

Avant Perl 5.6 il fallait utiliser le module Unicode::String:

#!/bin/perl -w

use strict;

use XML::Parser;

use Unicode::String;



my $p= new XML::Parser( Handlers =>

                 { Start     => \&default_latin1,

                   End        => \&default_latin1,

                   XMLDecl => sub { decl( "ISO-8859-1",@_); },

                   Default  => \&default_latin1

                         },

                      );

$p->parsefile($ARGV[0]);

exit;



sub decl

  { my( $new_encoding, $p, $version, $encoding, $standalone)=@_;

    print "<?xml version=\"$version\" encoding=\"$new_encoding\"";

    print "standalone=\"$standalone\"" if( $standalone);

    print "?>\n";

  }



sub default_latin1                          

  { my $p= shift;

    my $string= $p->recognized_string();   # récupérer l'Unicode

    my $u= Unicode::String::utf8( $string);# créer l'objet String 

    print $u->latin1;                      # le convertir en latin1

  }

Avec Perl 5.6 il n'est plus nécessaire d'utiliser Unicode::String, un simple tr/// fera l'affaire, la fonction default_latin1 devient:

sub default_latin1                          

  { my $p= shift;

    my $string= $p->recognized_string();   # récupérer l'UTF-8

    my $string=~ tr/\0-\x{ff}//UC;         # le convertir en latin 1

    print $string;

  }

Document en entrée non-unicode et sortie dans le même encodage

Si le document en entrée est en latin 1 vous pouvez utiliser le script précédent sinon vous devez utiliser XML::UM, voici un exemple (enc_ex5.pl):

#!/bin/perl -w

use strict;

use XML::Parser;

use XML::Encoding;

use XML::UM;



# indiquer à XML::UM où sont les tables d'encodage

$XML::UM::ENCDIR="/usr/local/src/XML-Encoding-1.01/maps/";



my $encode; # la fonction de transcodage, créée dans le

            # handler de déclaration XML (qui donne l'encodage)



my $p= new XML::Parser( Handlers =>

                         { Start   => \&default,

                           End     => \&default,

                           XMLDecl => \&decl,

                           Default => \&default,

                         },

                      );

$p->parsefile($ARGV[0]);

exit;

sub default

  { my $p= shift;

    print $encode->($p->recognized_string());

  }



# récupérer l'encodage et créer la fonction encode

sub decl

  { my($p, $version, $encoding, $standalone)=@_;

    print "<?xml version=\"$version\" encoding=\"$encoding\"";

    print "standalone=\"$standalone\"" if( $standalone);

    print "?>\n";

    $encode= XML::UM::get_encode( Encoding => $encoding);

  }

Conclusion

Unicode c'est génial. Vraiment ! Je suis sûr que vous avez tous eu les pires difficultés à transmettre des textes français, comportant des caractères accentués, entre différents systèmes. Et utiliser des GIFs pour afficher des lettres grecques au milieu d'un texte n'est pas vraiment une solution satisfaisante ! Unicode résout ces problèmes. Donc je vous encourage à utiliser autant que possible des logiciels qui le supportent et à faire pression sur les vendeurs pour qu'ils fassent d'Unicode une priorité dans leurs évolutions. A long terme cela vous épargnera bien des maux de têtes.

En attendant, si votre environnement vous impose d'autres encodages mais que vous ne voulez pas manquer la vague XML voici vos options:

  • vos documents XML sont tous en anglais. Pas de problème, XML::Parser fonctionne parfaitement, jusqu'à ce que vous tombiez sur un fragment comportant des caractères accentués, auquel cas une solution similaire à celle proposée par XML::Code fera l'affaire.

  • plus probablement, votre environnement utilise Latin 1, auquel cas vos documents peuvent utiliser cet encodage (n'oubliez pas de le déclarer en ajoutant encoding="ISO-5589-1" dans la déclaration XML), utilisez la sortie d'XML::Parser en UTF-8 pour les traitements et convertissez les sorties en utilisant Unicode::String ou tr///UC sous Perl 5.6,

  • votre environnement vous impose un autre encodage supporté par XML::Parser (grâce à XML::Encoding), et XML::UM vous permettra de produire des sorties dans cet encodage.

  • l'encodage que vous utilisez n'est pas reconnu par XML::Parser, auquel cas la meilleure solution est probablement de créer la table d'encodage correspondante et de l'inclure dans XML::Encoding ( et si en plus vous publiez cette table le problème disparaît pour tout le monde).

Voila ! J'espère que cet article vous aura aidé à comprendre les problèmes d'encodage, et vous aura donné des éléments pour résoudre vos problèmes dans le domaine. Bonne chance !

Ressources

La liste des modules référencés dans cet article:

  • XML::Parser: le parser XML Perl

  • XML::Encoding: permet a XML::Parser de lire des encodages additionnels

  • Unicode::String: convertit UTF-8 en ISO-8859-1 (latin 1)

  • XML::UM (inclus dans libxml-enno): convertit UTF-8 vers un jeu de caractère définit par XML::Encoding

  • XML::Code: conversion UTF-8 vers ASCII + Entités caractères XML


 

Mots clés.



L'histoire de XML s'écrit en ce moment même. XMLfr vous aide à la suivre et à en dégager les tendances.


Les documents publiés sur ce site le sont sous licence "Open Content"
Conception graphique
  l.henriot  

Conception, réalisation et hébergement
Questions ou commentaires
  redacteurs@xmlfr.org