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>

10 réponses

1 2
Avatar
Nicolas George
Patrick Mevzek wrote in message
:
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 ?


Il y a en Perl deux sortes de chaînes : les chaînes de caractères (Unicode)
et les chaînes d'octets.

Mon avis est que, dès lors qu'on envisage de manier du texte, et en
particulier du texte internationalisé, il est préférable de manier des
chaînes de caractères partout dans la mesure du possible.

Les communications avec l'extérieur (fichiers, pipes, réseau) se font
toujours sous forme de chaînes d'octets, c'est inévitable. Il est donc
souhaitable de convertir les données entrantes en chaînes de caractères dès
que possible, et réciproquement de convertir les données sortantes en octets
aussi tard que possible.

En revanche, les communications entre modules se font avec des données
natives de Perl, donc il convient d'utiliser des chaînes de caractères.

Dans le cas d'une bibliothèque XML, ce que j'ai dit s'applique ainsi :

- le code XML lui-même est constitué d'octets ;

- les fonctions pour manipuler l'arbre XML de manière abstraite utilisent
des chaînes de caractères.

Je ne sais pas si j'ai été très clair. Il faudrait vraiment que je rédige le
document que je prévois depuis si longtemps à ce sujet.

Avatar
Patrick Mevzek
Je ne sais pas si j'ai été très clair. Il faudrait vraiment que je rédige le
document que je prévois depuis si longtemps à ce sujet.


C'est clair mais je n'y trouve pas la réponse à ma question :-(
Chez moi ca marche, mais certains de mes utilisateurs me demandent de
mettre des Encode::encode() dans la bibliothèque. Chose qui me paraît
étrange, d'où ma question.

Simplifions le problème. Mettons qu'on ait un module qui implémente une
classe Personne, avec des accesseurs, nom() prenom() adresse()
commentaire() etc...
Et une méthode as_string() qui créé un document XML à partir des
données dans l'objet : pour créer le document XML peut-on prendre
directement les chaînes que les utilisateurs auront mises dans les
accesseurs précédemment, ou faut-il les traiter d'une façon ou d'une
autre (avec le postulat que le XML est en UTF-8) ?

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

Avatar
Nicolas George
Patrick Mevzek wrote in message
:
C'est clair mais je n'y trouve pas la réponse à ma question :-(


Normalement, ça y est, donc je n'ai pas été clair :(

Simplifions le problème. Mettons qu'on ait un module qui implémente une
classe Personne, avec des accesseurs, nom() prenom() adresse()
commentaire() etc...
Et une méthode as_string() qui créé un document XML à partir des
données dans l'objet : pour créer le document XML peut-on prendre
directement les chaînes que les utilisateurs auront mises dans les
accesseurs précédemment, ou faut-il les traiter d'une façon ou d'une
autre (avec le postulat que le XML est en UTF-8) ?


Je pense que nom, prenom, etc., doivent prendre et retourner des chaînes de
caractère, et as_string (bof pour le nom, mais c'est classique ; le dirais
plutôt as_xml_data) doit retourner une chaîne d'octets.

Dans ces conditions, il faut un Encode::encode quelque part, et je dirais
que la bonne place où le mettre, c'est tout près du code qui remplace & par
&amp;.

Réciproquement, pour from_xml_data, il faut un Encode::decode, qui se
trouvera naturellement là où &amp; est remplacé par un simple &.

Avatar
Patrick Mevzek
Dans ces conditions, il faut un Encode::encode quelque part, et je dirais
que la bonne place où le mettre, c'est tout près du code qui remplace & par
&amp;.


Ok. Mieux vaut alors un seul Encode::encode sur toute la chaîne qui a
été créé par concaténation de morceaux statiques et de données
utilisateur, ou un Encode::encode sur chaque donnée des utilisateurs,
puis une concaténation ?

Réciproquement, pour from_xml_data, il faut un Encode::decode, qui se
trouvera naturellement là où &amp; est remplacé par un simple &.


Ok, aussi, même si je pensais que le parseur XML faisait cette dernière
transformation (XML::LibXML dans mon cas).

Merci en tout cas pour la confirmation détaillée, je voulais être sûr
de bien comprendre, avant de me faire trucider par mes utilisateurs :-)

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

Avatar
Nicolas George
Patrick Mevzek wrote in message
:
Ok. Mieux vaut alors un seul Encode::encode sur toute la chaîne qui a
été créé par concaténation de morceaux statiques et de données
utilisateur, ou un Encode::encode sur chaque donnée des utilisateurs,
puis une concaténation ?


S'agissant d'UTF-8, c'est équivalent. Mais il me semble plus logique de le
faire chaîne par chaîne.

Ok, aussi, même si je pensais que le parseur XML faisait cette dernière
transformation (XML::LibXML dans mon cas).


Ah, mais ça change tout. On n'est plus en train de parler à l'extérieur qui
manipule des octets, là : on est en train de parler à une bibliothèque, et
c'est cette bibliothèque qui parle à l'extérieur.

S'agissant de XML::LibXML, je viens de mener quelques tests, et elle se
comporte comme je l'apprécie :

- les fonctions de manipulation de l'arbre XML prennent ou renvoient des
chaînes de caractères Unicode ;

- ses fonctions de sortie utilisent des chaînes d'octets.

Il faut bien comprendre qu'il n'y a à utiliser encode/decode que quand on
passe d'un monde à l'autre, et qu'une interface bien faite place ce
changement au bord du programme. Si on est en train d'implémenter
l'interface, il est clair qu'il faut s'en soucier. Mais si on utilise une
interface déjà faite, et bien conçue, il n'y a rien à faire. En l'occurence,
XML::LibXML fait bien les choses.

Avatar
Patrick Mevzek
[encode() bout par bout ou sur la totalité]
S'agissant d'UTF-8, c'est équivalent. Mais il me semble plus logique de
le faire chaîne par chaîne.


Pourquoi est-ce plus logique qu'une seule fois sur l'intégralité, après
concaténation ?
Non, parce que pour mettre en oeuvre la fainéantise, un seul encode() me
simplifierait la vie, juste avant le print() sur le réseau :-)

Ok, aussi, même si je pensais que le parseur XML faisait cette dernière
transformation (XML::LibXML dans mon cas).


Ah, mais ça change tout. On n'est plus en train de parler à l'extérieur qui
manipule des octets, là : on est en train de parler à une bibliothèque, et
c'est cette bibliothèque qui parle à l'extérieur.


Je parlais de ma bibliothèque, qui est appelée par des
programmes, et ma bibliothèque utilise elle-même d'autres
bibliothèques, comme XML::LibXML pour le sale boulot, à savoir parser la
chaîne d'octets que je viens de recevoir du réseau et qui est du XML.
Indépendamment, je génère aussi des documents XML envoyés sur le réseau.
Donc parfois je suis interfacé directement avec «l'extérieur», parfois
non, ca dépend du sens.

S'agissant de XML::LibXML, je viens de mener quelques tests, et elle se
comporte comme je l'apprécie :

- les fonctions de manipulation de l'arbre XML prennent ou renvoient des
chaînes de caractères Unicode ;

- ses fonctions de sortie utilisent des chaînes d'octets.

Il faut bien comprendre qu'il n'y a à utiliser encode/decode que quand on
passe d'un monde à l'autre, et qu'une interface bien faite place ce
changement au bord du programme. Si on est en train d'implémenter
l'interface, il est clair qu'il faut s'en soucier. Mais si on utilise une
interface déjà faite, et bien conçue, il n'y a rien à faire. En l'occurence,
XML::LibXML fait bien les choses.


Ca doit être l'heure, mais je ne suis de nouveau plus sûr de comprendre.
La chaîne d'octets que je reçois du réseau, est parsée par
XML::LibXML (je lui donne ce que j'ai reçu tel quel donc, sans encode()
ou decode()), après les différents éléments de mon document (valeurs
des noeuds, et attributs qui m'intéressent), auxquels j'accède donc via
l'API DOM de XML::LibXML, je peux les retourner tel quel à l'appelant ou
il me faut un decode() ? C'est probablement le «fonctions de sortie»
plus haut que je ne suis pas sûr de bien saisir.

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


Avatar
Nicolas George
Patrick Mevzek wrote in message
:
Pourquoi est-ce plus logique qu'une seule fois sur l'intégralité, après
concaténation ?


J'allais dire : parce que le balisage XML n'est moralement pas concerné par
l'encodage. Mais en fait, je me suis trompé : il est plus logique de faire
l'encodage sur le tout, parce que justement, le balisage XML _est_ concerné
par l'encodage.

Ca doit être l'heure, mais je ne suis de nouveau plus sûr de comprendre.
La chaîne d'octets que je reçois du réseau, est parsée par
XML::LibXML (je lui donne ce que j'ai reçu tel quel donc, sans encode()
ou decode()), après les différents éléments de mon document (valeurs
des noeuds, et attributs qui m'intéressent), auxquels j'accède donc via
l'API DOM de XML::LibXML, je peux les retourner tel quel à l'appelant ou
il me faut un decode() ? C'est probablement le «fonctions de sortie»
plus haut que je ne suis pas sûr de bien saisir.


Je vais faire un dessin :

+-----------------------------------------------------+
|programme perl | monde
| .... ..... ...... ..... ..... . | extérieur
| .' '' ''.'' ''' '.' '' '. |
| : '. | connexion
| : : <===>
| : : | réseau
| : chaînes de caractères Unicode : |
| : : <===>
| : : |
| : : |
| '. : |
| ''.. : |
| '.....'''''.... .... .' |
| chaînes d'octets '' '. ..' |
| ''......'' |
+---------------^-------^------------^----------------+
| | | fichiers
^ ^ ^

Idéalement, on veut manipuler des chaînes de caractères Unicode partout,
parce que c'est ça qu'il est agréable de manipuler : les expressions
rationnelles se comportent bien, lc et uc connaissent toutes les langues,
etc.

Hélas, les API du système d'exploitation sont ainsi faites que toutes les
communications avec l'extérieur sont faites sous forme de chaines d'octets.
Il doit donc y avoir une conversion quelque part. Mais _une seule_
conversion au plus dans chaque sens. On ne veut pas qu'une donnée passe son
temps à alterner entre un monde et l'autre.

Si on regarde un document XML :

0000000: 3c3f 786d 6c20 7665 7273 696f 6e3d 2231 <?xml version="1
0000010: 2e30 2220 656e 636f 6469 6e67 3d22 7574 .0" encoding="ut
0000020: 662d 3822 3f3e 0a3c 6772 6565 7469 6e67 f-8"?>.<greeting
0000030: 3e41 76c3 a926 2361 303b 213c 2f67 7265 >Av..&#a0;!</gre
0000040: 6574 696e 673e 0a eting>.

Ce document fait partie du monde extérieur, c'est une donnée bassement
informatique, avec les contraintes qui y sont liées : une bête suite
d'octets.

Cette suite d'octets est la représentation XML de la donnée abstraite qu'on
pourrait décrire par :

Un noeud racine greeting, avec un seul fils texte « Avé ! ».

Là, il s'agit de vrai texte : il s'agit d'un vrai E accent aigu (LATIN SMALL
LETTER E WITH ACUTE) et d'une vraie espace insécable (NO-BREAK SPACE), _pas_
de la suite d'octets 0xC3 0xA9.

Une bibliothèque XML bien conçue va donc attendre/renvoyer des chaînes
d'octets quand il s'agit de la représentation XML, avec des < et des >
partout, et avec des chaînes de caractères Unicode quand il s'agit de la
structure abstraite du document. Il va donc y avoir des encode/decode
quelque part dans les fonctions qui se situent entre les deux.

Dans le cas de XML::LibXML, qui est bien conçue, ça donne :

- XML::LibXML::Parser::parse_string attend une chaîne d'octets ;

- XML::LibXML::Document::toString renvoie une chaîne d'octets ;

- toutes les méthodes de XML::LibXML::Element/Node/... prennent et renvoient
des chaînes de caractères Unicode.

Avatar
Paul Gaborit
À (at) Thu, 16 Feb 2006 00:56:09 +0000 (UTC),
Nicolas George <nicolas$ écrivait (wrote):
S'agissant de XML::LibXML, je viens de mener quelques tests, et elle se
comporte comme je l'apprécie :

- les fonctions de manipulation de l'arbre XML prennent ou renvoient des
chaînes de caractères Unicode ;

- ses fonctions de sortie utilisent des chaînes d'octets.

Il faut bien comprendre qu'il n'y a à utiliser encode/decode que quand on
passe d'un monde à l'autre, et qu'une interface bien faite place ce
changement au bord du programme. Si on est en train d'implémenter
l'interface, il est clair qu'il faut s'en soucier. Mais si on utilise une
interface déjà faite, et bien conçue, il n'y a rien à faire. En l'occurence,
XML::LibXML fait bien les choses.


L'approche que vous proposez me semble la bonne pour un module en
général... mais dans le cas de XML, elle me pose toujours un problème.

Deux remarques :

- un document XML n'est pas obligatoirement encodé en UTF-8. C'est son
en-tête qui en décide. Ça n'empêche pas de manipuler tous les
caractères voulus dans un champ texte en passant par des entités.

- les noeuds d'un document XML ne contiennent pas obligatoirement du
texte. Il peuvent contenir des données binaires.

Donc votre première remarque concernant XML::LibXML (les fonctions de
manipulation de l'arbre XML prennent ou renvoient des chaînes de
caractères Unicode) me semble très limitative. Comment insérer des
données binaires dans mon arbre XML si je ne peux passer que de
l'Unicode à XML::LibXML ?

Autres questions : comment choisit-on l'encodage d'entrée, de sortie ?
À quel moment les noeuds textes sont-ils encodés/décodés (et leurs
caractères éventuellement transformés en entités ou inversement) ?
Comment indique-t-on à la bibliothèque la nature textuel ou binaire
d'un noeud ?

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

Avatar
Paul Gaborit
À (at) Thu, 16 Feb 2006 09:14:26 +0100,
Paul Gaborit écrivait (wrote):
Donc votre première remarque concernant XML::LibXML (les fonctions de
manipulation de l'arbre XML prennent ou renvoient des chaînes de
caractères Unicode) me semble très limitative. Comment insérer des
données binaires dans mon arbre XML si je ne peux passer que de
l'Unicode à XML::LibXML ?


Et comme d'habitude... la réponse est dans la documentation !

En fait, XML::LibXML na manipule pas de l'unicode ou de l'utf-8. La
séparation est bien faite entre XML::LibXML::Node qui manipule les
noeuds (en général) et XML::LibXML::Text qui manipule les noeuds
textuels (et qui dans ce cas ne communique qu'en utf-8 avec le code
Perl appelant ; respectant donc l'approche décrite).

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... Celles qui manipulent du texte doivent l'émettre ou
le recevoir en utf-8 (*). Les autres ne doivent pas toucher au contenu
(et reçoivent ou émettent donc des chaînes d'octets).

Les seuls cas où l'approche ci-dessus ne serait plus vrai concernent
une communication avec l'extérieur du programme. Mais comme vous
utilisez XML::LibXML, ce n'est pas votre problème.

(*) Si vous vouliez la compatibilité avec perl5.x (x < 8), il faudrait
prévoir des mécanismes pour gérer les différentes situations...

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

Avatar
Paul Gaborit
À (at) Thu, 16 Feb 2006 09:36:23 +0100,
Paul Gaborit écrivait (wrote):
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... Celles qui manipulent du texte doivent l'émettre ou
le recevoir en utf-8


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 !

Seul ma dernière phrase est à corriger :
Les autres ne doivent pas toucher au contenu (et reçoivent ou
émettent donc des chaînes d'octets).


Il faudrait dire : les autres (qui manipulent des noeuds XML) ne
doivent pas toucher au contenu. C'est à l'appelant de savoir ce qu'il
vous envoie. En fait, ce genre de fonctions/méthodes seraient par
exemple, une méthode qui donnerait accès directement à
XML::LibXML. Dans ce cas, votre module ne sert que d'intermédiaire et
ne doit donc rein modifier...

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

1 2