Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Xml::Simple et utf-8

9 réponses
Avatar
gvdmoort
Bonjour =E0 tous,

J'utilise Xml::simple pour analyser un fichier d'adresse xml (http://
adx.elektronengehirn.net/). Chaque contact se pr=E9sente sous une forme
comme

<contact>
<forename></forename>
<surname></surname>
<displayname></displayname>
<nickname></nickname>
<email use=3D"home"></email>
<tag></tag>
<tag>adx:frequency=3Dhigh</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=E9 =E0 =EAtre utilis=E9 avec le lecteur de mail mutt.

Le probl=E8me est que =E7a ne marche pas si la cha=EEne recherch=E9e compre=
nd
un caract=E8re utf-8, soit en pratique pour les donn=E9es en question les
caract=E8res accentu=E9s latins. Je me suis rendu compte avec Data::Dumper
que les donn=E9es =E9taient repr=E9sent=E9es de mani=E8re interne par

'nickname' =3D> "V\x{e9}ronique"

Je jure pourtant que le caract=E8re en question est correctement encod=E9
dans le fichier en utf-8. Je travaille dans un xterm en utf-8 sous
Linux debian, l'argument que je passe =E0 ce script est bien lui aussi
en utf-8.

Pour que mon script renvoie un r=E9sultat, je devrais lui passer les
caract=E8res accentu=E9s sous cette forme:

$ ./addresses.pl "V\x{e9}ronique"
1 match(es):
V=E9ronique <veronique.xxxxx@xxxxxxx.com>

ce qui n'est =E9videmment pas envisageable.

Y a-t-il une solution =ABpropre=BB =E0 ce probl=E8me. Je suis aussi =E9tonn=
=E9 que
par d=E9faut, les cha=EEnes provenant de la structure cr=E9=E9e par XmlSimp=
le
soient renvoy=E9es en latin1, ce qui m'oblige =E0 les r=E9-encoder.

Le script:

#!/usr/bin/perl -w
use strict;
use XML::Simple;
use Data::Dumper;
use Encode;
my $s =3D $ARGV[0];
my $xmlFile =3D 'addressbook.xml';
my $results =3D [];
my $ads =3D XMLin( $xmlFile,
ForceArray =3D> ['email','tag'],
SuppressEmpty =3D> 1);
foreach my $c (@{$ads->{contact}}) {
next if (! defined $c->{email});
my $match =3D 0;
if (defined $c->{surname}) {$match =3D 1 if $c->{surname} =3D~ /$s/i};
if (defined $c->{forename}) {$match =3D 1 if $c->{forename} =3D~ /$s/i};
if (defined $c->{nickname}) {$match =3D 1 if $c->{nickname} =3D~ /$s/i};
foreach my $tag (@{$c->{tag}}) {
$match =3D 1 if $tag =3D~ /$s/i;
}
if ($match) {
my $nickname =3D $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 =3D scalar(@$results);
print "$num r=E9sultat(s):\n";
foreach my $e (sort {$a->[0] cmp $b->[0]} @$results) {
print encode("utf8", "$e->[1] <$e->[2]>\n")
}


Cordialement,

G.

9 réponses

Avatar
Emmanuel Florac
Le Wed, 21 Dec 2011 12:28:20 -0800, gvdmoort a écrit:


Le problème est que ça ne marche pas si la chaîne recherchée comprend 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' => "Vx{e9}ronique"



Ç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
Avatar
Nicolas George
Emmanuel Florac , dans le message
<4ef24ee1$0$4195$, a écrit :
d'ajouter "use utf8" en haut de ton script



Ç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.
Avatar
Paul Gaborit
À (at) Wed, 21 Dec 2011 12:28:20 -0800 (PST),
gvdmoort écrivait (wrote):

[...]
Le problème est que ça ne marche pas si la chaîne recherchée comprend
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' => "Vx{e9}ronique"



C'est normal : e9 est bien la valeur hexadécimale du point de code du
caractère "é" en Unicode.

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.



C'est tout à fait possible.

Pour que mon script renvoie un résultat, je devrais lui passer les
caractères accentués sous cette forme:

$ ./addresses.pl "Vx{e9}ronique"
1 match(es):
Véronique

ce qui n'est évidemment pas envisageable.



Heureusement que non ! ;-)

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 XmlSimple
soient renvoyées en latin1, ce qui m'oblige à les ré-encoder.



Voir plus loin...

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):n";
foreach my $e (sort {$a->[0] cmp $b->[0]} @$results) {
print encode("utf8", "$e->[1] <$e->[2]>n")
}



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 - <http://perso.mines-albi.fr/~gaborit/>
Perl en français - <http://perl.mines-albi.fr/>
Avatar
gvdmoort
Bonjour,

On 22 déc, 01:56, Paul Gaborit wrote:
À (at) Wed, 21 Dec 2011 12:28:20 -0800 (PST),



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,

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).



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.
Avatar
Emmanuel Florac
Le Wed, 21 Dec 2011 22:11:42 +0000, Nicolas George a écrit:


Ç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.



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
Avatar
Nicolas George
gvdmoort , dans le message
, a
écrit :
#!/usr/bin/perl -w
binmode(STDOUT, ":utf8");
print "éèàçn"



Tu as des caractères non-ASCII dans ton script et tu ne dis pas à perl dans
quel encodage ils sont. C'est ici qu'il faut utiliser « use utf8 ».

D'une manière générale, il faut comprendre qu'une chaîne peut être soit une
chaîne d'octets soit une chaîne de caractères Unicode, et qu'il faut éviter
de mélanger les deux. Si on ne précise rien, toutes les chaînes sont des
chaînes d'octets. binmode(FH, ":utf8") indique que les chaînes qui entrent
et sortent par ce filehandle sont des chaînes de caractères Unicode,
converties en UTF-8 pour le dialogue avec l'extérieur.

La chaîne dans ton print est, en l'absence de « use utf8 », une chaîne
d'octets, alors que tu viens de dire que STDOUT travaille avec des chaînes
de caractères Unicode.
Avatar
Paul Gaborit
À (at) Thu, 22 Dec 2011 02:25:45 -0800 (PST),
gvdmoort écrivait (wrote):

Vos interventions sont toujours aussi pertinentes que rapides. Je vous
offrirais volontiers une bonne bouteille.



En ces temps festifs, cette bouteille, même virituelle, me ravit.

Hips ! ;-)

(... si vous y tenez vraiment, je peux vous donner mon adresse
postale ... ;-))

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).



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"



(Note: le fait que ça se passe correctement sans le binmode est une
illusion. En interne, perl n'a pas reconnu les bons caractères.
L'illusion provient d'une double erreur qui s'annule : perl croit que le
script est en latin1 et que STDOUT est en latin1. Il ne convertit donc
rien.)

En fait, vous venez de rajouter à perl une nouvelle source d'information
dont il faut vérifier l'encodage: le code de votre script lui-même.

Donc, après avoir vérifié que le code de votre script est bien encodé en
utf8, vous pouvez ajouter la ligne suivante dans votre script :

use utf8;

Ce pragma sert exactement à cela : indiquer que le fichier source du
script est en utf8.


--
Paul Gaborit - <http://perso.mines-albi.fr/~gaborit/>
Perl en français - <http://perl.mines-albi.fr/>
Avatar
Nicolas George
Paul Gaborit , dans le message ,
a écrit :
L'illusion provient d'une double erreur qui s'annule : perl croit que le
script est en latin1 et que STDOUT est en latin1. Il ne convertit donc
rien.)



Petite correction : perl croit que le script est en ASCII étendu, pas en
latin-1. La différence peut se voir avec le script suivant, à encoder en
latin-1 :

#use encoding "iso-8859-1", stdout => "iso-8859-1";
print uc "Joyeux Noël !n";

Comparer le résultat selon si on active ou non la première ligne.
Avatar
Paul Gaborit
À (at) 22 Dec 2011 13:17:11 GMT,
Nicolas George <nicolas$ écrivait (wrote):

Paul Gaborit , dans le message ,
a écrit :
L'illusion provient d'une double erreur qui s'annule : perl croit que le
script est en latin1 et que STDOUT est en latin1. Il ne convertit donc
rien.)



Petite correction : perl croit que le script est en ASCII étendu, pas en
latin-1. La différence peut se voir avec le script suivant, à encoder en
latin-1 :

#use encoding "iso-8859-1", stdout => "iso-8859-1";
print uc "Joyeux Noël !n";

Comparer le résultat selon si on active ou non la première ligne.




Oui, c'est exact.

--
Paul Gaborit - <http://perso.mines-albi.fr/~gaborit/>
Perl en français - <http://perl.mines-albi.fr/>