Xml::Simple et utf-8
Le
gvdmoort
Bonjour à tous,
J'utilise Xml::simple pour analyser un fichier d'adresse xml (http://
adx.elektronengehirn.net/). Chaque contact se présente sous une forme
comme
<contact>
<forename></forename>
<surname></surname>
<displayname></displayname>
<nickname></nickname>
<email use="home"></email>
<tag></tag>
<tag>adx:frequency=high</tag>
</contact>
L'objet est de chercher dans le contenu de ces tags une correspondance
avec un motif et d'afficher les adresses mails correspondantes. C'est
destiné à être utilisé avec le lecteur de mail mutt.
Le problème est que ça ne marche pas si la chaîne recherchée compre=
nd
un caractère utf-8, soit en pratique pour les données en question les
caractères accentués latins. Je me suis rendu compte avec Data::Dumper
que les données étaient représentées de manière interne par
'nickname' => "V\x{e9}ronique"
Je jure pourtant que le caractère en question est correctement encodé
dans le fichier en utf-8. Je travaille dans un xterm en utf-8 sous
Linux debian, l'argument que je passe à ce script est bien lui aussi
en utf-8.
Pour que mon script renvoie un résultat, je devrais lui passer les
caractères accentués sous cette forme:
$ ./addresses.pl "V\x{e9}ronique"
1 match(es):
Véronique <veronique.xxxxx@xxxxxxx.com>
ce qui n'est évidemment pas envisageable.
Y a-t-il une solution «propre» à ce problème. Je suis aussi étonn=
é que
par défaut, les chaînes provenant de la structure créée par XmlSimp=
le
soient renvoyées en latin1, ce qui m'oblige à les ré-encoder.
Le script:
#!/usr/bin/perl -w
use strict;
use XML::Simple;
use Data::Dumper;
use Encode;
my $s = $ARGV[0];
my $xmlFile = 'addressbook.xml';
my $results = [];
my $ads = XMLin( $xmlFile,
ForceArray => ['email','tag'],
SuppressEmpty => 1);
foreach my $c (@{$ads->{contact}}) {
next if (! defined $c->{email});
my $match = 0;
if (defined $c->{surname}) {$match = 1 if $c->{surname} =~ /$s/i};
if (defined $c->{forename}) {$match = 1 if $c->{forename} =~ /$s/i};
if (defined $c->{nickname}) {$match = 1 if $c->{nickname} =~ /$s/i};
foreach my $tag (@{$c->{tag}}) {
$match = 1 if $tag =~ /$s/i;
}
if ($match) {
my $nickname = $c->{nickname}?$c->{nickname}:"$c->{forename} $c-
>{surname}";
foreach my $email (@{$c->{email}}) {
if (defined $email->{content}) {
push @$results,[$c->{surname}, $nickname, $email->{content}]
}
}
}
}
my $num = scalar(@$results);
print "$num résultat(s):";
foreach my $e (sort {$a->[0] cmp $b->[0]} @$results) {
print encode("utf8", "$e->[1] <$e->[2]>")
}
Cordialement,
G.
J'utilise Xml::simple pour analyser un fichier d'adresse xml (http://
adx.elektronengehirn.net/). Chaque contact se présente sous une forme
comme
<contact>
<forename></forename>
<surname></surname>
<displayname></displayname>
<nickname></nickname>
<email use="home"></email>
<tag></tag>
<tag>adx:frequency=high</tag>
</contact>
L'objet est de chercher dans le contenu de ces tags une correspondance
avec un motif et d'afficher les adresses mails correspondantes. C'est
destiné à être utilisé avec le lecteur de mail mutt.
Le problème est que ça ne marche pas si la chaîne recherchée compre=
nd
un caractère utf-8, soit en pratique pour les données en question les
caractères accentués latins. Je me suis rendu compte avec Data::Dumper
que les données étaient représentées de manière interne par
'nickname' => "V\x{e9}ronique"
Je jure pourtant que le caractère en question est correctement encodé
dans le fichier en utf-8. Je travaille dans un xterm en utf-8 sous
Linux debian, l'argument que je passe à ce script est bien lui aussi
en utf-8.
Pour que mon script renvoie un résultat, je devrais lui passer les
caractères accentués sous cette forme:
$ ./addresses.pl "V\x{e9}ronique"
1 match(es):
Véronique <veronique.xxxxx@xxxxxxx.com>
ce qui n'est évidemment pas envisageable.
Y a-t-il une solution «propre» à ce problème. Je suis aussi étonn=
é que
par défaut, les chaînes provenant de la structure créée par XmlSimp=
le
soient renvoyées en latin1, ce qui m'oblige à les ré-encoder.
Le script:
#!/usr/bin/perl -w
use strict;
use XML::Simple;
use Data::Dumper;
use Encode;
my $s = $ARGV[0];
my $xmlFile = 'addressbook.xml';
my $results = [];
my $ads = XMLin( $xmlFile,
ForceArray => ['email','tag'],
SuppressEmpty => 1);
foreach my $c (@{$ads->{contact}}) {
next if (! defined $c->{email});
my $match = 0;
if (defined $c->{surname}) {$match = 1 if $c->{surname} =~ /$s/i};
if (defined $c->{forename}) {$match = 1 if $c->{forename} =~ /$s/i};
if (defined $c->{nickname}) {$match = 1 if $c->{nickname} =~ /$s/i};
foreach my $tag (@{$c->{tag}}) {
$match = 1 if $tag =~ /$s/i;
}
if ($match) {
my $nickname = $c->{nickname}?$c->{nickname}:"$c->{forename} $c-
>{surname}";
foreach my $email (@{$c->{email}}) {
if (defined $email->{content}) {
push @$results,[$c->{surname}, $nickname, $email->{content}]
}
}
}
}
my $num = scalar(@$results);
print "$num résultat(s):";
foreach my $e (sort {$a->[0] cmp $b->[0]} @$results) {
print encode("utf8", "$e->[1] <$e->[2]>")
}
Cordialement,
G.

Poser une question


Ça peut être dû à la version de perl que tu n'as pas indiqué. Essaie déjà
d'ajouter "use utf8" en haut de ton script, ça ne peut pas faire de mal
et même rêgler le problème :)
--
In the modern world the stupid are cocksure while the intelligent are
full of doubt.
Bertrand Russell
Ça n'a d'influence que pour les caractères non-ASCII présents dan le script
lui-même. J'espère que tu en as conscience.
gvdmoort
[...]
C'est normal : e9 est bien la valeur hexadécimale du point de code du
caractère "é" en Unicode.
C'est tout à fait possible.
Heureusement que non ! ;-)
Voir plus loin...
Première remarque, n'utilisez *jamais* Encode (ou tout autre package de
réencodage) avant d'être sûr que vous maîtrisez parfaitement les
mécanismes d'encodage de vos entrées/sorties. Perl gère très bien tout
seul son encodage interne dans la mesure où on lui indique correctement
le codage des données qu'il reçoit ou émet.
Ici, vous avez deux sources de données (un fichier XML et @ARGV) et une
sortie de données (STDOUT mais on peut y inclure aussi STDERR et
STDIN). Donc trois choses à vérifier :
1- Le module XML::Simple gère l'encodage tout seul comme un grand
(puisque par définition un fichier XML indique son propre codage)
donc la lecture du fichier XML se fait correctement.
2- Pour indiquer que les flux standards (STDIN, STDOUT, STDERR) sont
en utf8, vous devez faire appel à 'binmode' ou vous pouvez aussi
le faire en passant la valeur S à l'option '-C' de perl (lire
perlrun).
3- Pour indiquer que les valeurs reçues via @ARGV sont en utf8, il
n'y a pas d'autres moyens qu'en passant la valeur A à l'option
'-C' de perl (ou alors il faut faire appel à Encode lors de leur
lecture).
Donc, l'ajout du flag -CSA au lancement de perl dans votre script
devrait suffire :
#!/usr/bin/perl -w -CSA
--
Paul Gaborit - Perl en français -
On 22 déc, 01:56, Paul Gaborit
Vos interventions sont toujours aussi pertinentes que rapides. Je vous
offrirais volontiers une bonne bouteille.
D'abord merci pour les flags -CA qui règlent le problème de la
reconnaissance de l'encodage de l'input. Cependant,
Pour alléger le message initial, je n'avais pas fait mention d'avoir
essayé auparavant
binmode(STDOUT, ":utf8");
qui réglait les problèmes d'affichage en sortie des chaînes de
caractères construites par XmlSimple, mais compromettait l'affichage
des chaînes encodées dans le script lui-même.
En effet, ce bête script induit un affichage incorrect, alors que ça
se passe correctement sans le binmode
#!/usr/bin/perl -w
binmode(STDOUT, ":utf8");
print "éèàçn"
La situation est la même avec
#!/usr/bin/perl -w -CSA
print "éèàçn"
Je comprends à présent que dès qu'on ajoute ces flags ou binmode, il
est nécessaire d'ajouter "use utf8;" si le script comprend lui-même de
tels caractères.
Cordialement,
G.
C'est vrai. Maintenant je ne connais pas le détail du comportement des
versions diverses de perl vis-à-vis de ce pragma.
--
The reasonable man adapts himself to the world; the unreasonable one
persists in trying to adapt the world to himself. Therefore, all
progress depends on the unreasonable man.
George Bernard Shaw