Un guide de l'Unicode compréhensible en Perl 5

Rafael Garcia-Suarez

Traducteur

Laurent Rosenfeld

L'Unicode est une norme internationale permettant de représenter informatiquement les caractères de la plupart des langues du monde, ainsi qu'une multitude de symboles (mathématiques ou monétaires, par exemple) ou de pictogrammes normalisés.

Que cela nous plaise ou non, l'époque où l'on pouvait se contenter des caractères ASCII (même étendus) est révolue depuis des années. L'Unicode est devenu indispensable et incontournable de nos jours et le sera encore plus dans les années à venir.

Ce guide de Rafael Garcia-Suarez, ancien « pumpking » (responsable de version) de Perl et l'un des meilleurs experts français des entrailles de Perl, devrait vous aider à comprendre comment utiliser l'Unicode en Perl 5.

N'hésitez pas à envoyer des remarques pour l'améliorer. Commentez

- Titre : Un guide de l'Unicode compréhensible en Perl 5

- Auteur : Rafael Garcia-Suarez

- Traducteur : Laurent Rosenfeld

- Parution : 21 juin 2016

- Mise à jour : 21 juin 2016

- Licence : Le contenu de cet article est rédigé par Rafael Garcia-Suarez et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Voir également :

- Encodage des caractères avec Perl

Sommaire

- 1. Les chaînes de caractères en Perl

- 1-1. Syntaxe des chaînes

- 2. La fonctionnalité unicode_strings

- 3. L'option -C de la ligne de commande

- 3-1. Affichages en sortie

- 3-1-1. UTF-8

- 3-2. Données en entrée

- 3-3. La variable d'environnement PERL_UNICODE

- 4. Quelques warnings Unicode

- 4-1. Wide character in ...

- 4-2. Malformed UTF-8 character

- 4-3. "..." does not map to Unicode

- 5. The pragma utf8

- 6. Les fonctions utf8::encoding et utf8::decoding

- 7. Les fonctions utf8::upgrading et utf8::downgrading

- 8. Le module Encode

- 9. Changement de casse et la fonction fc

- 10. Graphèmes et l'atome \X des expressions régulières

- 11. Non-caractères

- 12. L'option /a des expressions régulières

- 12-1. Encore plus ASCII avec l'option /aa

- 13. Les couches d'entrée-sortie de PerlIO

- 13-1. La couche d'entrée-sortie :utf8 de PerlIO

- 13-2. La couche :encoding(UTF-8) de PerlIO

- 13-3. Manipulation de couches : binmode

- 13-4. Spécifier les couches par défaut : use open

- 13-5. Lire depuis des scalaires

- 14. Modules Unicode standard

- 14-1. Unicode::Normalize

- 14-2. Unicode::Collate

- 14-3. Unicode::UCD

- 15. Ressources complémentaires

- 16. Remerciements

1 - Les chaînes de caractères en Perl

En Perl, les chaînes de caractères peuvent contenir tous les caractères Unicode (jusqu'à un point de code maximal de U+10FFFF). En interne, Perl représente les chaînes avec un encodage UTF-8 à taille variable (actuellement de 1 à 4 octets), en raison de la bonne utilisation de l'espace de cet encodage et du fait que l'encodage est le même que l'ASCII pour les caractères jusqu'à 127, ce qui est pratique.

Les chaînes de caractères qui proviennent de l'extérieur de votre programme (noms de fichiers, contenu de fichiers, arguments de la ligne de commande, entrée standard STDIN, etc.) et même le texte même de votre programme ne sont toujours qu'un flux d'octets (valeurs comprises entre 0 et 255), car c'est la façon dont les programmes communiquent entre eux. C'est la raison pour laquelle il faut les décoder lorsque l'on attend qu'ils contiennent des caractères Unicode. Dans la terminologie Perl, le décodage est précisément l'opération qui consiste à convertir un flux d'octets en un flux de caractères Unicode. Il existe plusieurs manières de le faire.

Inversement, les chaînes de caractères en sortie de votre programme (noms de fichiers, contenu de fichiers, sortie standard STDIN, sortie d'erreur STDERR, etc.) doivent être encodées en octets à partir de l'encodage attendu.

À moins qu'un module dédié du CPAN ne s'en charge, le décodage des entrées et l'encodage des sorties doivent également être mis en place pour les accès aux serveurs de bases de données et l'utilisation de formats de données sérialisés comme JSON, XML, YAML, etc.

1-1 - Syntaxe des chaînes

Dans une chaîne de caractères Perl, vous pouvez spécifier un point de code quelconque avec un nombre (hexadécimal) en utilisant la séquence d'échappement \x{...} à l'intérieur d'une chaîne entre guillemets doubles. Les accolades ne sont pas nécessaires lorsque le nombre hexadécimal ne contient que deux chiffres. Par exemple, "\x42" correspond à U+0042 LATIN CAPITAL LETTER B, et "\x{0442}" représente le caractère U+442 CYRILLIC SMALL LETTER TE.

Le pragma charnames vous permet d'accéder aux caractères par leur nom Unicode, si bien que dans la portée d'une instruction use charnames ':full', vous pouvez également écrire ces caractères comme suit : "\N{LATIN CAPITAL LETTER B}" et "\N{CYRILLIC SMALL LETTER TE}".

2 - La fonctionnalité unicode_strings

Mais commençons par le commencement. Avant de nous plonger dans les subtilités du décodage et de l'encodage, voyons comment configurer un programme ou un module Perl pour éviter quelques-unes des surprises les plus déplaisantes susceptibles d'arriver avec des chaînes Unicode.

La façon la plus simple d'y arriver est de commencer votre programme avec le pragma :

use 5.12.0;

Ou, mieux, si votre version de Perl est suffisamment récente :

use 5.16.0;

Ceci dit à Perl de ne pas tenir compte d'une ancienne sémantique historique (connue sous le nom d'Unicode Bug, avec un B majuscule, parmi les hackers des entrailles de Perl) qui faisait que certaines opérations sur les chaînes dépendaient de la variable interne « UTF-8 flag » utilisée par Perl pour se souvenir de l'encodage interne des chaînes. Autrement dit, utiliser cette version minimale de Perl garantit que les opérations sur les chaînes seront conformes à la sémantique Unicode et que vous n'avez pas à vous préoccuper de la représentation interne de vos chaînes.

Pour entrer dans les détails, cette instruction use dit à Perl de charger la fonctionnalité unicode_strings feature et de l'activer pour l'ensemble de votre fichier (mais pas des modules qu'il charge !). Si vous désirez n'activer que cette fonctionnalité, vous pouvez également écrire :

use feature 'unicode_strings';

Pour des scripts unilignes, vous pouvez également utiliser l'option -E de la ligne de commande au lieu de l'instruction use <version>.

Voici un bref exemple de ce bogue Unicode. Le caractère U+00FF LATIN SMALL LETTER Y WITH DIAERESIS (y avec tréma ou ÿ) a une version en capitales qui tombe en dehors de l'intervalle 0-255, U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS. Si la fonctionnalité unicode_strings a été activée, uc() renvoie le caractère voulu :

$ perl -CS -lE '$_="\xff";print;print uc'
ÿ
Ÿ

(Ceci suppose que votre terminal comprend l'UTF-8 - sinon, l'affichage sera légèrement perturbé.)

Mais sans ce pragma, uc() renvoie souvent son argument inchangé :

$ perl -CS -le '$_="\xff";print;print uc'
ÿ
ÿ

Mais pas toujours :

$ perl -CS -le '$_=substr("\xff\x{100}",0,1);print;print uc'
ÿ
Ÿ

La raison de cette incohérence est que l'état interne de la chaîne, telle qu'elle était implémentée en Perl, fuit, ce qui change la sémantique de la fonction uc(). Ceci est à éviter, donc protégez-vous et activez la fonctionnalité unicode_strings !

3 - L'option -C de la ligne de commande

Dans un script uniligne, vous pouvez utiliser l'option -C de la ligne de commande pour dire à Perl de s'attendre (ou pas) à trouver de l'Unicode dans une partie au moins de l'environnement d'exécution du programme. Les différentes syntaxes de cette option sont décrites dans la documentation perlrun, mais celle qui nous intéresse au premier chef ici est -CS, qui dit que l'entrée standard, la sortie standard et la sortie d'erreur ont toutes un encodage en UTF-8. (L'option inverse est -C0, et assure que rien ne doit s'attendre à des données Unicode par défaut.)

3-1 - Affichages en sortie

Voici quelques exemples. Prenons le caractère U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS (ÿ), et voyons ce que Perl sort en inspectant la sortie standard à l'aide de xxd, un utilitaire Linux créant une représentation hexadécimale de données binaires :

$ perl -E 'print "\xff"' | xxd
0000000: ff                                       .
$ perl -CS -E 'print "\xff"' | xxd
0000000: c3bf                                     ..

Sans l'option -CS, Perl produit un seul octet, 0xff, qui n'est pas un code UTF-8 valide. En utilisant cette option, Perl produit l'encodage UTF-8 correct du caractère U+00FF, à savoir les deux octets 0xc3 et 0xbf, dans cet ordre. Super, ça marche !

3-1-1 - UTF-8

L'UTF-8 est un encodage à longueur variable. Chaque caractère nécessite entre 1 et 4 octets.

Octet(s)

Bits

Encodé

1

6..0

0bbbbbbb

2

10..6,5..0

110bbbbb 10bbbbbb

3

15..12,11..6,5..0

1110bbbb 10bbbbbb 10bbbbbb

4

20..18,17..12,11..6,5..0

11110bbb 10bbbbbb 10bbbbbb 10bbbbbb

Le caractère U+00FF a besoin de 8 bits pour stocker la valeur 0xff. Le flux de bits encodé nécessite donc deux octets :

$ perl -e 'printf "%b\n", hex "c3bf"'
1100001110111111
# 110_00011 + 10_111111
#     00011      111111
#        11      111111
$ perl -e 'printf "%b\n", hex "ff"'
11111111

3-2 - Données en entrée

Faisons-le dans l'autre sens maintenant. Que voit Perl quand il lit les deux octets 0xc3 et 0xbf ?

$ perl -E 'print "\xc3\xbf"' | perl -E '$x = <>; printf "%d %x\n", length $x, ord $x'
2 c3
$ perl -E 'print "\xc3\xbf"' | perl -CS -E '$x = <>; printf "%d %x\n", length $x, ord $x'
1 ff

Dans le premier cas, nous avons dit à Perl qu'il recevait des octets et c'est ce qu'il a eu : deux octets, dans une chaîne longue de deux caractères.

Dans l'autre cas, Perl a décodé ces octets comme une chaîne encodée en UTF-8 et a lu un caractère, à savoir ÿ.

3-3 - La variable d'environnement PERL_UNICODE

Par défaut, Perl se comporte comme s'il avait reçu l'option -C0 à la ligne de commande, mais vous pouvez modifier ce comportement par défaut en exportant la variable d'environnement PERL_UNICODE avant de démarrer le programme (cela ne fonctionnera pas si vous le faites après le démarrage du programme). Cette variable simule ce qui vient après l'option -C de la ligne de commande. Il y aura trois comportements différents selon que cette variable n'est pas définie, qu'elle vaut une chaîne vide ou qu'elle vaut 0. Tout ceci est décrit dans la section de la documentation perlrun, relative à l'option -C.

4 - Quelques warnings Unicode

Perl émet différents warnings quand il détecte que vous mélangez des octets et des chaînes Unicode, ou que vous utilisez des choses étranges en Unicode. Ces avertissements appartiennent à la catégorie des warnings utf8 et à ses sous-catégories.

4-1 - Wide character in ...

Revenons à l'exemple précédent. Nous avons joué avec le caractère U+00FF, mais considérons sa forme plus moderne U+0133, LATIN SMALL LIGATURE IJ (\x{0133} — oui, il s'agit bien d'un seul caractère en néerlandais). En UTF-8, ce caractère est encodé sous la forme de deux octets, 0xc4 et 0xb3, comme on le voit ci-dessous :

$ perl -CS -E 'print "\x{133}"' | xxd
0000000: c4b3

Notez que vous pouvez aussi avoir trois ou quatre octets :

$ perl -CS -E 'print "\x{2A6A5}"' | xxd
0000000: f0aa 9aa5

Mais que se passe-t-il si nous oublions l'option -CS ? Ceci signifierait que Perl attendrait des octets sur l'entrée standard. Mais les octets sont par définition compris entre 0x0 et 0xff, et 0x133 n'appartient pas à cet intervalle. Cependant, Perl va tout de même encoder le caractère en UTF-8 et vous avertir de l'anomalie :

$ perl -E 'print "\x{133}"' | xxd
Wide character in print at -e line 1.
0000000: c4b3

4-2 - Malformed UTF-8 character

Que se passe-t-il si vous donnez à Perl quelque chose que vous pensez être de l'UTF-8 correct, mais n'en est pas ? Nous avons vu, par exemple, que l'octet 0xff n'est pas une chaîne UTF-8 correcte :

$ perl -E 'print "\xff"' | perl -CS -pE1 | xxd
Malformed UTF-8 character (unexpected end of string) in print, <> line 1.
0000000: ff                                       .

Dans ce cas, Perl garde la donnée en entrée inchangée et émet un avertissement.

4-3 - "..." does not map to Unicode

Il existe de nombreuses façons d'aboutir à un caractère UTF-8 mal formé. Voici l'une de mes préférées. Le caractère REVERSE SOLIDUS (alias « backslash » ou barre oblique inverse) \ correspond au point de code Unicode U+005C ; sa représentation en UTF-8 consiste en un seul octet, 0x5c. Cependant, compte tenu des règles d'encodage de l'UTF-8, il pourrait également s'encoder sous la forme de deux octets, 0xc1 0x9c. C'est ce qu'on appelle une forme plus longue que nécessaire, et ces formes inutilement longues sont interdites par la norme et ne doivent pas être utilisées en UTF-8. Le script uniligne suivant montre que Perl est assez strict lors du décodage de chaînes UTF-8.

$ perl -E 'print "\xc1\x9c"' | perl -CS -wpE1 | xxd
utf8 "\xC1" does not map to Unicode, <> line 1.
0000000: c19c                                     ..

À noter que l'option -w de la ligne de commande est nécessaire ici pour activer ce warning optionnel.

Pourquoi est-ce important ? Parce que si Perl était moins regardant lors du décodage, il pourrait rendre une barre oblique inverse dans ce cas, mais ne pas parvenir à le détecter lors qu'une recherche naïve de l'octet 0x5c dans la chaîne. Plusieurs vulnérabilités de sécurité ont utilisé des formes UTF-8 plus longues que nécessaire pour court-circuiter les mécanismes de vérification XSS ou d'autres choses du même genre. Estimez-vous heureux que Perl vous protège automatiquement de ce genre de problèmes.

5 - The pragma utf8

La seule utilisation du pragma utf8 comme ci-dessous :

use utf8;

au début de votre fichier est de dire à Perl que le reste du fichier, en particulier les chaînes et les expressions régulières, est encodé en UTF-8. C'est tout. Ce pragma n'a pas d'impact mesurable sur la durée de compilation ou d'exécution du programme.

Mon conseil personnel serait cependant d'éviter de mettre de l'UTF-8 dans vos programmes, à moins que vous ne soyez sûr à 100 % que ça ne va pas être corrompu par des éditeurs, des terminaux ou des collègues. Le pragma utf8 vous permet également d'utiliser de l'UTF-8 dans les noms des fonctions ou des variables, mais, de mon expérience, c'est rarement une bonne idée. Par exemple, les deux formes suivantes du caractère « è » sont définies comme canoniquement équivalentes en Unicode, mais si vous utilisez l'une pour l'autre, Perl sera perdu, parce que le compilateur ne normalise pas (encore) les identifiants canoniquement équivalents de façon unique :

$ perl -CS -E '$_="sub ad\xe8le { say q/ok/ } ade\x{300}le()";eval $_ or print $@'
Undefined subroutine &main::ade\x{0300}le called at (eval 1) line 1.

(Soit dit en passant, eval() fonctionne correctement ici avec du code Perl en Unicode grâce à l'option -E de la ligne de commande, qui active la fonctionnalité unicode_eval ; sans cela, eval() garderait sa sémantique historique de toujours comprendre son argument comme un flux d'octets.)

6 - Les fonctions utf8::encoding et utf8::decoding

Perl dispose de deux fonctions internes, utf8::encode et utf8::decode, qui assurent l'encodage et le décodage de bas niveau de l'UTF-8. À noter que ce sont des fonctions internes, il n'y a pas besoin d'activer utf8 ou un quelconque autre pragma pour les charger, comme pour toutes les fonctions internes. Qu'est-ce que cela signifie ? Reprenons la liste d'exemples que nous avons déjà vus.

L'encodage est la conversion en un flux d'octets que vous pouvez envoyer vers l'extérieur de votre programme :

$ perl -wE '$x="\xff";utf8::encode($x);print $x' | xxd
0000000: c3bf                                     ..
$ perl -wE '$x="\x{133}";utf8::encode($x);print $x' | xxd
0000000: c4b3                                      ..

Le décodage est la conversion d'un flux d'octets en caractères Unicode (si possible) :

$ perl -wE '$x="\xc3\xbf";utf8::decode($x);printf "%d %x\n", length $x, ord $x'
1 ff
$ perl -wE '$x="\xc4\xb3";utf8::decode($x);printf "%d %x\n", length $x, ord $x'
1 133

Les deux fonctions transforment leurs arguments en place. utf8::encode ne renvoie rien ; utf8::decode renvoie une valeur booléenne spécifiant si le décodage a pu être effectué avec succès.

Ces deux fonctions sont d'assez bas niveau : elles n'émettent pas de warnings, et n'effectuent que les vérifications de base ; il convient généralement de les manipuler avec précaution. Pour des primitives d'encodage et de décodage de plus haut niveau, considérez le module Encode (voir § 8 ci-dessous).

7 - Les fonctions utf8::upgrading et utf8::downgrading

Ces fonctions sont de plus bas niveau encore que les précédentes et leur utilité est très limitée en général. Alors que les fonctions utf8::encode et utf8::decode peuvent convertir le flux d'octets ou de points de code représentant une chaîne, utf8::downgrade et utf8::upgrade n'y toucheront pas. Leur rôle consiste uniquement à s'assurer qu'en interne, la chaîne est encodée en UTF-8 (upgrade) ou non (downgrade).

À noter que, par défaut, utf8::downgrade terminera le programme en erreur si la chaîne reçue en paramètre ne peut pas être représentée sous la forme d'octets.

$ perl -E '$x="\x{133}"; eval { utf8::downgrade($x);1 } or print "error: $@"'
error: Wide character in subroutine entry at -e line 1.

8 - Le module Encode

Le module Encode fournit des routines de conversion et de validation d'encodage et prend en charge non seulement l'UTF-8 mais aussi une vaste palette d'autres encodages spécifiques.

Les fonctions encode et decode exportées par le module Encode ont le même but que les versions utf8:: ayant le même nom, elles convertissent respectivement vers et depuis l'encodage spécifié. Contrairement à ces fonctions de bas niveau, elles renvoient la valeur convertie :

$ perl -MEncode -wE 'print encode("UTF-8","\xff")' | xxd
0000000: c3bf                                     ..
$ perl -MEncode -wE '$x=decode("UTF-8","\xc3\xbf");printf "%d %x\n", length $x, ord $x'
1 ff

La gestion des erreurs peut être ajustée finement. Par défaut, tout caractère qui ne peut pas être décodé sera remplacé par le caractère spécial U+FFFD REPLACEMENT CHARACTER \x{fffd} qui, d'après la norme, s'utilise pour remplacer tout caractère entrant dont la valeur est inconnue ou non représentable en Unicode :

$ perl -MEncode -wE '$x=decode("UTF-8","\xff");printf "%d %x\n", length $x, ord $x'
1 fffd

Mais il est aussi possible de déclencher une exception :

$ perl -MEncode -wE 'say decode("UTF-8","\xff",Encode::FB_CROAK)'
utf8 "\xFF" does not map to Unicode at /Users/rgs/perl5/perlbrew/perls/perl-5.18.2/lib/5.18.2/darwin-2level/Encode.pm line 176.

ou de demander le renvoi d'une version plus facile à lire pour un humain de la chaîne qui ne peut être décodée :

$ perl -MEncode -wE 'say decode("UTF-8","\xff",Encode::FB_PERLQQ)'
\xFF

Voir la documentation du module Encode pour une liste complète des options et possibilités.

Le module Encode fournit enfin deux fonctions internes, _utf8_on et _utf8_off, qui sont essentiellement équivalentes à utf8::decode et utf8::encode respectivement. Elles n'effectuent aucune vérification et leur emploi est déconseillé.

$ perl -MEncode -wE '$x="\xc4\xb3";Encode::_utf8_on($x);printf "%d %x\n", length $x, ord $x'
1 133
$ perl -MEncode -wE '$x="\x{133}";Encode::_utf8_off($x);printf "%d %x\n", length $x, ord $x'
2 c4

9 - Changement de casse et la fonction fc

Depuis la version 5.16, Perl a une nouvelle fonction interne, fc, qui assure les changements de casse (conversion entre capitales et minuscules) en Unicode. C'est la façon adéquate de mettre en œuvre une comparaison insensible à la casse entre des chaînes. Par exemple, en ce qui concerne les formes finales et non finales de la lettre grecque sigma (respectivement \x{03c2} et \x{03c3}), la forme finale est convertie en forme non finale, ce qui n'est pas le cas avec la fonction lc, puisque les deux formes sont déjà en minuscules :

$ perl -CS -E '$_="\x{3A3}\x{3C3}\x{3C2}";say;say lc;say uc;say fc'
\x{03a3}\x{03c3}\x{03c2}
\x{03c3}\x{03c3}\x{03c2}
\x{03a3}\x{03a3}\x{03a3}
\x{03c3}\x{03c3}\x{03c3}

10 - Graphèmes et l'atome \X des expressions régulières

En Unicode, un graphème est un caractère logique qui peut se représenter avec plusieurs caractères Unicode distincts et combinés.

Par exemple, le c cédille (LATIN SMALL LETTER ç) peut s'écrire sous la forme d'un caractère unique U+00E7, ou sous celle d'une juxtaposition de la lettre « c » ordinaire (U+0063) et du caractère U+0327, COMBINING CEDILLA:

$ perl -CS -wE 'say chr 0xe7, " ", "c", chr 0x327'
ç c\x{0327}

Le premier a une longueur de 1 et le second de 2 :

$ perl -CS -wE 'say length for "\xe7", "c\x{327}"'
1
2

C'est le comportement voulu, mais ça peut conduire à des résultats inattendus si vous avez l'intention de travailler avec des graphèmes, mais travaillez en fait avec des caractères.

Il existe une fonctionnalité de Perl qui comprend les graphèmes : l'atome \X des expressions régulières, qui reconnaît un graphème unique, même s'il se compose de plusieurs caractères. Cela vous permet d'extraire des graphèmes uniques d'une chaîne :

$ perl -CS -wE 'say length, " $_" for "\xe7c\x{327}c" =~ /\X/g'
1 ç
2 c\x{0327}
1 c

À noter également que la version 5.22.0 de Perl introduit une nouvelle assertion d'expression régulière, \b{gcb} (pour «  grapheme cluster boundary ») qui reconnaît uniquement les limites entre les graphèmes.

11 - Non-caractères

L'Unicode réserve de façon permanente 66 points de code comme « non-caractères », qui ne doivent pas être utilisés pour les échanges de données entre les applications. Comme le dit la norme, ces codes sont réservés aux utilisations internes aux processus. Leur utilisation déclenche des warnings, même si les warnings ne sont pas activés :

$ perl -CS -E 'say "\x{fffe}"' | xxd
Unicode non-character U+FFFE is illegal for open interchange at -e line 1.
0000000: efbf be0a

Pour désactiver ce warning, vous devez le faire explicitement avec une instruction no warning nonchar, ou un équivalent :

$ perl -M-warnings=nonchar -CS -E 'say "\x{fffe}"' | xxd
0000000: efbf be0a

12 - L'option /a des expressions régulières

À partir de Perl 5.14, il existe une option /a pour forcer une sémantique ASCII sur la reconnaissance des expressions régulières. Qu'est-ce que cela veut dire ?

Par défaut, les séquences \d, \s, \w et les classes de caractères Posix reconnaissent les caractères Unicode :

$ perl -Mcharnames=:full -E 'say "\N{FULLWIDTH DIGIT SEVEN}" =~ /\d/ ? "matches" : "not"'
matches

Avec le flag /a, la reconnaissance se limite aux caractères ASCII, si bien, par exemple, que \d ne reconnaîtra que les chiffres ASCII de l'intervalle 0 à 9 :

$ perl -Mcharnames=:full -E 'say "\N{FULLWIDTH DIGIT SEVEN}" =~ /\d/a ? "matches" : "not"'
not
$ perl -E 'say 7 =~ /\d/a ? "matches" : "not"'
matches

12-1 - Encore plus ASCII avec l'option /aa

Si vous répétez le flag a une seconde fois, le comportement sera plus strict et interdira tout mélange d'ASCII et d'Unicode, ce qui peut avoir de l'importance pour les reconnaissances insensibles à la casse. L'exemple canonique concerne le symbole du Kelvin, « k » (U+212A) :

$ perl -Mcharnames=:full -E 'say "\N{KELVIN SIGN}" =~ /K/i ? "match" : "no"'
match
$ perl -Mcharnames=:full -E 'say "\N{KELVIN SIGN}" =~ /K/ai ? "match" : "no"'
match
$ perl -Mcharnames=:full -E 'say "\N{KELVIN SIGN}" =~ /K/aai ? "match" : "no"'
no

Cette option empêche aussi la reconnaissance des ligatures, par exemple "\x{fb01}", U+FB01 LATIN SMALL LIGATURE FI :

$ perl -Mcharnames=:full -E 'say "\N{LATIN SMALL LIGATURE FI}" =~ /fi/i ? "match" : "no"'
match
$ perl -Mcharnames=:full -E 'say "\N{LATIN SMALL LIGATURE FI}" =~ /fi/ai ? "match" : "no"'
match
$ perl -Mcharnames=:full -E 'say "\N{LATIN SMALL LIGATURE FI}" =~ /fi/aai ? "match" : "no"'
no

13 - Les couches d'entrée-sortie de PerlIO

Perl fournit des moyens facilitant la lecture d'un fichier ou l'écriture dans un fichier tout en, respectivement, décodant ou décodant les données.

13-1 - La couche d'entrée-sortie :utf8 de PerlIO

Pour décoder les caractères UTF-8 lus depuis un fichier, vous pouvez ouvrir le fichier avec la syntaxe à trois arguments d'open en spécifiant la couche :utf8 d'entrée-sortie après le caractère indiquant le mode :

open my $fh, "<:utf8", $file or die $!;

Ceci a le même effet que l'option -CI de ligne de commande sur l'entrée standard, comme vous pouvez le constater dans cet exemple :

$ perl -E 'say for PerlIO::get_layers(STDIN)'
unix
perlio
$ perl -CI -E 'say for PerlIO::get_layers(STDIN)'
unix
perlio
utf8

De même, pour les sorties, voici comment ouvrir un fichier en mode écriture :

open my $fh, ">:utf8", $file or die $!;

et ceci équivaut à l'option -CO sur la sortie standard, ou -CE sur la sortie en erreur. (À noter que -CS est un raccourci typographique pour les trois options, -CIOE.)

13-2 - La couche :encoding(UTF-8) de PerlIO

Toutefois, la couche :utf8 est de très bas niveau et n'effectue que très peu de validation. Elle ne permet pas non plus de configurer ce qu'il faut faire en cas d'erreur. La couche PerlIO::encoding, qui se base en fait sur le module Encode, effectue les mêmes vérifications et utilise les mêmes solutions de repli qu'Encode, et est donc bien plus recommandée.

13-3 - Manipulation de couches : binmode

La fonction interne binmode() peut être utilisée pour ajouter des couches PerlIO à n'importe quel descripteur de fichier. Par exemple, si vous désirez lire et valider de l'UTF-8 provenant de l'entrée standard :

binmode STDIN, ':encoding(UTF-8)';

13-4 - Spécifier les couches par défaut : use open

Le pragma open permet de spécifier les couches PerlIO par défaut pour les fichiers en entrée et en sortie. Cependant, en raison de sa nature globale, son utilité reste marginale.

13-5 - Lire depuis des scalaires

La couche spéciale PerlIO::scalar permet de lire des chaînes scalaires se trouvant en mémoire, au lieu de fichiers. La documentation précise que le scalaire est considéré comme un flux d'octets (comme s'il s'agissait d'entrées externes, comme un fichier ordinaire). À compter de Perl 5.17.9, Perl ne pourra pas lire des chaînes qui contiennent des points de code supérieur à 0xFF et émettra un avertissement :

$ perl -CS -wE 'open $fh,"<",\"oeuf" or die "Cannot open scalar: $!";say <$fh>'
oeuf
$ perl -CS -wE 'open $fh,"<",\"œuf" or die "Cannot open scalar: $!";say <$fh>'
Strings with code points over 0xFF may not be mapped into in-memory file handles
Cannot open scalar: Invalid argument at -e line 1.

La solution consiste à faire le décodage vous-même :

$ perl -CS -wE 'utf8::encode($x="œuf"); open $fh,"<", \$x; utf8::decode($y=<$fh>); say $y'
œuf

14 - Modules Unicode standard

Perl est livré avec deux modules Unicode qui aident à manipuler les chaînes de caractères conformément aux règles de la norme.

14-1 - Unicode::Normalize

Comme indiqué brièvement dans le chapitre5, l'Unicode définit les relations d'équivalence entre les caractères ou les groupes de caractères. Il existe deux sortes de relations d'équivalence : l'équivalence canonique décrit les différentes façons de représenter le même caractère des séquences de points de code différentes ; et l'équivalence de compatibilité, qui est plus faible, permet aussi les variations d'aspect des caractères. Le module standard Unicode::Normalize met en œuvre quatre fonctions qui convertissent les chaînes de caractères vers l'une des quatre formes normalisées.

Par exemple, la forme NFD (décomposition canonique) du caractère U+00E7 (ç) est constituée, dans cet ordre, des deux caractères correspondant à la lettre c et à la cédille :

$ perl -MUnicode::Normalize -E 'printf "%04x\n", ord for split //, NFD("\xe7")'
0063
0327

La décomposition de compatibilité fonctionne de façon analogue. Par exemple, si l'on considère le symbole \x{24b6}, à savoir U+24B6 (CIRCLED LATIN CAPITAL LETTER A), nous verrons qu'il sera converti en un simple A :

$ perl -MUnicode::Normalize -E 'say NFKD("\x{24B6}")'
A

Les formes normalisées sont particulièrement utiles pour tester si des chaînes sont égales.

14-2 - Unicode::Collate

Il faut parfois trier des chaînes. Cela peut très vite s'avérer loin d'être simple. Le Consortium Unicode est venu à la rescousse en définissant un imposant algorithme de collation qui est mis en œuvre dans le module standard Unicode::Collate.

14-3 - Unicode::UCD

Unicode::UCD donne un accès à la base de données des caractères Unicode.

Par exemple, si vous préférez les noms de caractères aux valeurs numériques employées dans l'un des exemples de code ci-dessus, vous pouvez utiliser la fonction charinfo :

$ perl -MUnicode::UCD=charinfo -MUnicode::Normalize -E 'say charinfo(ord)->{name} for split //, NFD("\xe7")'
LATIN SMALL LETTER C
COMBINING CEDILLA

15 - Ressources complémentaires

- Unicode Introduction, sur le site perltuts.com

- La documentation standard livrée avec Perl :

- le document de référence sur l'Unicode en Perl : perlunicode ;

- le livre de recettes (cookbook) : perlunicook ;

- la foire aux questions (FAQ) : perlunifaq ;

- une introduction à l'Unicode en Perl : perluniintro. Il existe une version française de ce document (perluniintro), mais elle est ancienne et en grande partie périmée ;

- un tutoriel pour commencer : perlunitut ;

- la page de réponses de Tom Christiansen sur Stackoverflow.

16 - Remerciements

Nous remercions Rafael Garcia-Suarez de nous avoir donné l'aimable autorisation de publier cette version française de son article, dont l'original se trouve à cette adresse.

Nous remercions Laurent Rosenfeld pour la traduction de ce document, Djibril pour la mise au format de ce site et la relecture de la traduction, et ClaudeLeloup pour la relecture orthographique.