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

Conseil sur gestion unicode dans un module

18 réponses
Avatar
Patrick Mevzek
Bonsoir,

Je maintiens une distribution disponible sur CPAN (ensemble de 70 modules
environ, plus de 12000 lignes de perl pur) et je suis à la pêche aux
conseils sur la gestion unicode _à l'intérieur_ de cette bibliothèque
déjà utilisée dans la nature.

La bibliothèque génère du XML à partir de données fournies par
l'application utilisatrice, et réciproquement fournit des données
extraites de messages XML reçus depuis le réseau.

J'ai des retours de la part des utilisateurs qui partent (les retours) un
peu dans tous les sens, et me font poser des questions sur la gestion
unicode.

Les messages XML sont en UTF-8 et ma question se résume donc en : la
bibliothèque en elle-même doit elle s'occuper de quelque chose ou juste
transmettre les données, étant donné que
1) les utilisateurs savent qu'ils doivent donner des chaînes en UTF8
2) on ne s'occupe pas de la compatibilité avec des perls avant 5.8

En particulier, la bibliothèque doit-elle faire quelque chose du style
Encode::encode("UTF-8",$data)
sur toutes les données récupérées de l'application avant de les insérer
dans le flux XML et réciproquement decode() à partir du flux XML avant
de retourner les données ?
Ou autre(s) chose(s) ?

Merci d'avance pour vos lumières. Je n'ai pas trouvé de documentation
parlant d'unicode dans les bibliothèques, à part
«Interaction with Extensions» dans perlunicode, mais qui me laisse
grandement sur ma faim.

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>

8 réponses

1 2
Avatar
Nicolas George
Paul Gaborit wrote in message :
Et donc pour répondre au PO, de mon point de vue, dans votre module,
il vous faut distinguer les méthodes/fonctions manipulant du texte et
celles manipulant des données. Ça doit être clair tant dans votre code
que dans la doc...



Voilà, c'est exactement ce que je voulais dire, en nettement plus clair.

Celles qui manipulent du texte doivent l'émettre ou
le recevoir en utf-8



Je ne suis pas tout à fait d'accord avec ça. Plus exactement, il y a une
confusion pénible apportée par la documentation de perl. J'explique.

Il y a vraiment deux types *différents* de chaînes dans perl. On peut s'en
convaincre avec le programme suivant (à exécuter dans perl 5.8 minimum,
évidemment) :

my $t1 = pack "C", 0xE9;
my $t2 = pack "U", 0xE9;
print "Pareil.n" if $t1 eq $t2;
print ord uc $t1, "n";
print ord uc $t2, "n";

On voit que du point de vue de l'opérateur eq, $t1 et $t2 sont identiques,
mais pourtant, elles donnent des résultats différents sur l'opérateur uc, ce
qui prouve qu'elles ont des caractéristiques différentes.

$t1 est ce que j'ai appelé une chaîne d'octets. C'est le type de chaîne
qu'il faut utiliser pour manipuler des données informatiques binaires, et en
particulier pour communiquer avec le système d'exploitation, qui ne connaît
que ça.

$t2 est ce que j'ai appelé une chaîne de caractères Unicode, ou chaîne de
caractères tout court pour faire plus simple. C'est le type de chaîne qu'il
faut utiliser pour manipuler du texte.

En particulier, on ne devrait jamais utiliser d'expressions rationnelles, ou
d'opérateurs « linguistiques » tels qu'uc sur des chaînes d'octets,
uniquement sur des chaînes de caractères. En pratique, ça va marcher quand
même sur les cas simples (quand on s'intéresse à l'ASCII), mais si on veut
un système fiable et qui gère correctement l'internationalisation, il faut
éviter.

La conversion entre un type de chaîne et l'autre se fait avec les fonctions
Encode::encode (sens chaîne de caractères -> chaîne d'octets) et
Encode::decode (sens inverse), ou potentiellement d'autres fonctions
similaires ou qui les empaquètent. Il peut également y avoir des conversions
effectuées automatiquement au niveau des filehandles (dont les données
finales sont _toujours_ des chaînes d'octets, puisque l'OS ne connaît que
ça) avec les modules :utf8 et :encoding.

Là où ça devient subtil, c'est ce qui se passe quand une chaîne d'octets et
une chaîne de caractères entrent en contact, par exemple par concaténation.
Apparemment, la chaîne d'octets est promue en chaîne de caractères en
préservant la valeur de chaque élément (ce qui, soit dit en passant, est
équivalent à Encode::decode("ISO-8859-1", ...)). Je pense qu'il vaut mieux
éviter de se retrouver dans une situation de ce genre. Il serait intéressant
d'avoir une paire de warnings "Unicode string used as byte string" / "Byte
string used as Unicode string".

Le problème de la doc de perl, c'est qu'elle utilise un peu partout le terme
utf-8 pour désigner ce que j'appelle les chaînes de caractères Unicode.
C'est une mauvaise chose parce que :

- Même si perl utilise UTF-8 en interne pour stocker ces chaînes dans la
mémoire de l'ordinateur, rien dans les API de perl ne permet de s'en
rendre compte. La représentation interne des chaînes de caractères Unicode
pourrait changer sans qu'aucun programme en soit affecté.
(Je parle des programmes en perl, là ; les extensions en C peuvent voir la
représentation interne des chaînes et en seraient affectées, évidemment.)

- UTF-8 est une notion sur les octets, une description de la représentation
informatique d'un texte. L'expression « pack("C2", 0xC3, 0xA9) » est une
chaîne UTF-8 qui représente le caractère « e accent aigu », et pourtant ce
n'est pas ce que la doc de perl appelle une chaîne « utf-8 ».

Je pense que le terme approprié est vraiment « chaîne de caractères
Unicode », puisque c'est précisément ça que les interfaces de perl nous
exposent.

Et c'est d'autant plus vrai que je racontais des bêtises dans mon
premier message (pas très réveillé ce matin!) : un document XML ne
peut pas contenir de données binaires arbitraires !


Ah, j'ai eu peur : pendant trois minutes j'ai cru que j'avais raté tout un
pan d'XML :)


Avatar
Paul Gaborit
À (at) Thu, 16 Feb 2006 12:32:28 +0000 (UTC),
Nicolas George <nicolas$ écrivait (wrote):
Là où ça devient subtil, c'est ce qui se passe quand une chaîne d'octets et
une chaîne de caractères entrent en contact, par exemple par concaténation.
Apparemment, la chaîne d'octets est promue en chaîne de caractères en
préservant la valeur de chaque élément (ce qui, soit dit en passant, est
équivalent à Encode::decode("ISO-8859-1", ...)). Je pense qu'il vaut mieux
éviter de se retrouver dans une situation de ce genre. Il serait intéressant
d'avoir une paire de warnings "Unicode string used as byte string" / "Byte
string used as Unicode string".


Tout à fait d'accord. D'autant qu'il n'est pas toujours possible de
promouvoir une suite d'octets arbitraires en chaînes Unicode "valide".

Le problème de la doc de perl, c'est qu'elle utilise un peu partout le terme
utf-8 pour désigner ce que j'appelle les chaînes de caractères Unicode.
C'est une mauvaise chose parce que :
[...]

Je pense que le terme approprié est vraiment « chaîne de caractères
Unicode », puisque c'est précisément ça que les interfaces de perl nous
exposent.


En fait aucune de ces deux appelations ne sont bonnes puisque Perl
accepte de chaînes utf-8 (codage interne) invalides du point de vue
Unicode (sémantique applicative)... Je ne sais plus où c'est expliqué
dans la doc. Mais c'est ce qui autorise la promotion citée ci-dessus
sans poser de problème...

De toutes manières la prise en compte des différents encodages, des
"locale" et maintenant d'unicode est un véritable casse-tête dans tous
les langages (et systèmes). De ce point de vue, Perl est parti de loin
(les premières tentatives d'intégration étaient catastrophiques) mais
l'état actuel, même si il n'est pas parfait, me semble tout de même
bien pensé et cohérent. Ceci étant, on n'est pas au bout de nos
peines...

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

Avatar
Nicolas George
Paul Gaborit wrote in message :
En fait aucune de ces deux appelations ne sont bonnes puisque Perl
accepte de chaînes utf-8 (codage interne) invalides du point de vue
Unicode (sémantique applicative)...


Tu peux donner un exemple plus précis ? Tu veux parler de l'utilisation
invalide de combinants ?

De ce point de vue, Perl est parti de loin
(les premières tentatives d'intégration étaient catastrophiques) mais
l'état actuel, même si il n'est pas parfait, me semble tout de même
bien pensé et cohérent.


Oui. À vrai dire, perl est celui dont la gestion d'Unicode est la mieux
pensée parmi les langages que je connais. Mas seule critique est sur la
formulation de la doc, qui utilise « utf-8 » là où à mon avis elle ne
devrait pas.

Avatar
Paul Gaborit
À (at) Thu, 16 Feb 2006 15:25:30 +0000 (UTC),
Nicolas George <nicolas$ écrivait (wrote):
Tu peux donner un exemple plus précis ? Tu veux parler de l'utilisation
invalide de combinants ?


Si toute suite d'octets peut-être converti en une chaîne
Unicode/UTF-8, c'est que Perl accepte de suites d'octets ne
représentant aucun caractère Unicode (et donc invalide) en UTF-8. Je
ne sais pas si c'est ça que vous appelez une "utilisation invalide de
combinants" ?

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

Avatar
Nicolas George
Paul Gaborit wrote in message :
Si toute suite d'octets peut-être converti en une chaîne
Unicode/UTF-8, c'est que Perl accepte de suites d'octets ne
représentant aucun caractère Unicode (et donc invalide) en UTF-8.


Je ne comprends toujours pas de quoi tu veux parler. Je vois deux
interprétations possibles :

- Accepter une suite d'octets du style « 0xA9 0xC3 », qui n'est
effectivement pas de l'UTF-8 valide. Mais non, puisque de fait, Perl
n'accepte pas ça.

- Accepter une suite d'octets comme « 0xFC 0x92 0x8D 0x85 0x99 0xB8 », qui
est bien de l'UTF-8 valide, mais ne représente pas un caractère Unicode
existant, ni même pouvant exister (Unicode s'arrête à U+10FFFD).

C'est de ce dernier point que tu voulais parler ?

Je
ne sais pas si c'est ça que vous appelez une "utilisation invalide de
combinants" ?


Non, je pensais en l'occurence à accepter une chaîne du style :

my $t = "x{0301}";

qui n'est pas correcte puisque U+0301 est un combinant (COMBINING ACUTE
ACCENT) et qu'il n'a rien avec quoi se combiner.

Avatar
Paul Gaborit
À (at) Fri, 17 Feb 2006 12:47:35 +0000 (UTC),
Nicolas George <nicolas$ écrivait (wrote):
Je ne comprends toujours pas de quoi tu veux parler. Je vois deux
interprétations possibles :


"Les deux mon capitaine !" ;-)

- Accepter une suite d'octets du style « 0xA9 0xC3 », qui n'est
effectivement pas de l'UTF-8 valide. Mais non, puisque de fait, Perl
n'accepte pas ça.


Je viens de refaire des tests... et je m'aperçois qu'effectivement, ça
a encore changé (mes derniers tests devaient datés de la 5.8.4 ou
avant). Avant, Perl acceptait tout. Maintenant non.

- Accepter une suite d'octets comme « 0xFC 0x92 0x8D 0x85 0x99 0xB8 », qui
est bien de l'UTF-8 valide, mais ne représente pas un caractère Unicode
existant, ni même pouvant exister (Unicode s'arrête à U+10FFFD).

C'est de ce dernier point que tu voulais parler ?


Ça aussi. Mais là, il n'y a rien à faire. Unicode peut-être étendu à
n'importe quel moment. Il ne faut pas que le langage bloque à cause de
cela. D'ailleurs, la décision d'arrêt à U+10FFFD est-elle définitive ?

Je
ne sais pas si c'est ça que vous appelez une "utilisation invalide de
combinants" ?


Non, je pensais en l'occurence à accepter une chaîne du style :

my $t = "x{0301}";

qui n'est pas correcte puisque U+0301 est un combinant (COMBINING ACUTE
ACCENT) et qu'il n'a rien avec quoi se combiner.


Ok. Là encore, je viens de faire quelques tests et ça ne colle plus
avec mes expériences précédentes.

Par exemple, avec le script suivant (test.pl) :

binmode(STDOUT, ":utf8");
my $t = "x{20}x{41}x{ff}";
print "$t";

On teste :

% perl -w test.pl | od -x
0000000 2041 c3bf
0000004

D'où provient le 'c3bf' ?

un x{0301} est transformé en 'cc81'...

Il va falloir que je me replonge là-dedans pour mieux comprendre
l'ensemble.

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


Avatar
Nicolas George
Paul Gaborit wrote in message :
Je viens de refaire des tests... et je m'aperçois qu'effectivement, ça
a encore changé (mes derniers tests devaient datés de la 5.8.4 ou
avant). Avant, Perl acceptait tout. Maintenant non.


Ça me paraît bizarre, dans la mesure où justement on n'a jamais accès à la
représentation interne des chaînes.

D'ailleurs, la décision d'arrêt à U+10FFFD est-elle définitive ?


Ça semble assez inévitable : UTF-16 ne peut pas dépasser ça, et il y a des
boulets particulièrement lourds (lire : microsoft et sun) qui utilisent
UTF-16.

Par exemple, avec le script suivant (test.pl) :

binmode(STDOUT, ":utf8");
my $t = "x{20}x{41}x{ff}";
print "$t";

On teste :

% perl -w test.pl | od -x
0000000 2041 c3bf
0000004

D'où provient le 'c3bf' ?


C'est la représentation UTF-8 du x{ff}, considéré comme U+00FF LATIN SMALL
LETTER Y WITH DIAERESIS.

un x{0301} est transformé en 'cc81'...


C'est assez simple, en fait. On regarde l'écriture binaire (je prends deux
exemples en plus au pif) :

00FF -> 1111 1111
0301 -> 11 0000 0001
20AC -> 10 0000 1010 1100
59EB -> 101 1001 1110 1011

Ensuite, on découpe par paquets de 6, à partir de la droite, sauf le dernier
qui doit faire au plus 5 :

00FF -> 11 111111
0301 -> 1100 000001
20AC -> 10 000010 101100
59EB -> 101 100111 101011

On complète le paquet de tête par n 1 puis des zéros, où n est le nombre de
paquets, et les suivants par 10 :

00FF -> 11000011 10111111
0301 -> 11001100 10000001
20AC -> 11100010 10000010 10101100
59EB -> 11100101 10100111 10101011

Et si on veut, on reconvertit le tout en hexadécimal pour la lisibilité :

00FF -> 0xC3 0xBF
0301 -> 0xCC 0x81
20AC -> 0xE2 0x82 0xAC
59EB -> 0xE5 0xA7 0xAB

Avatar
Paul Gaborit
À (at) Fri, 17 Feb 2006 14:33:34 +0000 (UTC),
Nicolas George <nicolas$ écrivait (wrote):
Paul Gaborit wrote in message :
Je viens de refaire des tests... et je m'aperçois qu'effectivement, ça
a encore changé (mes derniers tests devaient datés de la 5.8.4 ou
avant). Avant, Perl acceptait tout. Maintenant non.


Ça me paraît bizarre, dans la mesure où justement on n'a jamais accès à la
représentation interne des chaînes.


En disant 5.8.4, j'étais *très* optimiste. En fait, en vérifiant la
date de mes scripts de tests, je me suis aperçu qu'à cette époque je
n'avais pas encore installé de perl 5.8 (en tous cas pas de version
fonctionnelle). D'ailleurs les 'use utf8' le montre bien. De plus, je
me basais en partie sur des regexep pour analyser le contenu des
chaînes et ça ne fonctionne plus avec 5.8 (en 5.6 les regexp ne gérait
pas l'utf-8).

Je vais refaire d'autres tests plus propres maintenant que
l'implémentation est quasi stable.

Votre proposition d'un "petit" article de synthèse me semble une très
bonne chose pour tous ceux qui souhaitent gérer proprement l'unicode
en Perl... ;-)

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


1 2