Normalisation Unicode

Unicode propose quatre formes de normalisation, et elles ne sont pas interchangeables. NFC et NFD réorganisent le même caractère entre un point de code unique et une lettre de base suivie d’une marque combinante ; NFKC et NFKD vont plus loin et réécrivent les caractères qui ne font que se ressembler — la ligature fi, le A pleine chasse A, le cercle ①, le chiffre romain Ⅻ. Cette page fait passer le même texte par les quatre formes, affiche une ligne par caractère indiquant ce qui a changé et pourquoi, compare le nombre de graphèmes aux points de code et aux octets UTF-8, et compare deux chaînes sous la forme que vous choisissez. La seconde moitié porte sur les caractères qui n’apparaissent jamais : espaces de largeur nulle, liants, commandes bidirectionnelles, sélecteurs de variante, caractères de contrôle C0 et C1, points de code de zone privée et moitiés de substitution cassées, chacun avec sa position et son nom, et l’option de les retirer d’une copie du texte.
Tout le traitement s’effectue localement dans votre navigateur — vos données ne sont jamais envoyées à un serveur.

Vous ne savez pas ce qu’est la normalisation ? La normalisation Unicode expliquée simplement — pourquoi é peut être un ou deux caractères, et pourquoi cela casse une connexion.
Forme à afficher et à utiliser pour la comparaison :
Comparer les deux zones après :
NFC (rien pour l’instant)
NFD (rien pour l’instant)
NFKC (rien pour l’instant)
NFKD (rien pour l’instant)
Rien n’a encore été comparé. Collez les deux chaînes et appuyez sur Comparer les deux chaînes.
première chaîne, normalisée (rien pour l’instant)
deuxième chaîne, normalisée (rien pour l’instant)
Rien n’a encore été analysé. Appuyez sur Rechercher les caractères invisibles et dangereux, puis sur Retirer si vous voulez le texte sans ces caractères.
texte sans les classes sélectionnées (rien pour l’instant)
Ctrl+Entrée dans la première zone lance le mode actif.
Choisissez un mode et cliquez sur son bouton d’action. Normaliser produit les quatre formes de votre texte et une ligne par caractère ; Comparer indique si deux chaînes sont identiques après normalisation ; Analyser liste les caractères qui ne s’affichent pas et peut les retirer. Le bouton Autotest rejoue tous les cas documentés dans le fichier de données de cette page, chaque caractère que les tables d’invisibles et de contrôle citent, et affiche le nombre ici.
Chaque caractère, une ligne chacun :ce qu’est le caractère, ce que font les quatre formes et pourquoi. Une ligne ombrée est un caractère qu’au moins une forme modifie ; la colonne NFD indique en quoi le caractère se décompose lorsqu’il se décompose.
Combien cela fait-il de caractères ?Le même texte compté de quatre façons : graphèmes (ce qu’un lecteur compte), points de code (ce qu’Unicode attribue), unités UTF-16 (ce qu’indexe JavaScript) et octets UTF-8 (ce qui voyage). La normalisation fait passer les nombres entre la deuxième et la troisième colonne sans modifier la première.
Ce qui a été trouvé, dans l’ordre d’apparition :la position compte les points de code, la classe est celle à laquelle appartient le caractère, et la remarque indique ce que ce caractère fait à ce qui lit ensuite le texte.
Les classes que cette page considère comme suspectes :le tableau est imprimé à partir de l’analyseur lui-même, de sorte que les mots de cette page et le comportement des boutons ne puissent pas diverger. La gravité concerne l’effet sur une comparaison, et non la probabilité que le caractère soit inoffensif.
Les caractères de zone privée sont signalés comme suspects pour une autre raison que les autres : ils s’affichent bien, mais n’ont aucune signification convenue — le même point de code est une image différente dans chaque police qui le définit.
Taille maximale de saisie acceptée par cette page, et pourquoi :la normalisation reconstruit toute la chaîne, et cette page en fait plusieurs, plus une analyse et un décompte de graphèmes, si bien que le coût croît linéairement avec la longueur de la saisie. Les plafonds ci-dessous existent parce que le tableau par caractère est dessiné dans le document, et qu’un tableau d’un million de lignes est précisément ce qui bloque un navigateur.
Qu’est-ce que la normalisation Unicode ?

Unicode offre plus d’une façon d’écrire certains caractères. La lettre é peut être un seul point de code, U+00E9, ou un e ordinaire suivi d’un accent aigu combinant, U+0065 U+0301. Les deux semblent identiques à l’écran, s’impriment de la même façon, et en tant que chaînes elles sont différentes : elles n’ont pas la même longueur, pas le même nombre d’octets UTF-8, et 'é' === 'e\u0301' vaut false.

Ce n’est pas une curiosité, c’est une famille de bogues. Obtenir l’une ou l’autre forme dépend de la disposition du clavier, du système d’exploitation, de la méthode de saisie et du fait que le texte soit passé ou non par un traitement de texte. Un contrôle de mot de passe, un index unique de base de données, une liste de blocage ou une clé de cache qui compare les chaînes octet par octet traiteront les deux graphies comme deux valeurs différentes — un utilisateur peut donc définir un mot de passe sur un appareil et ne pas pouvoir se connecter depuis un autre, et le même mot peut apparaître deux fois dans une liste de noms « déjà pris ».

La version en 30 secondes : NFC et NFD sont le même texte écrit de deux façons — NFC compose (e + accent devient un seul caractère), NFD décompose (un caractère devient e + accent). Elles sont censées être sans perte et ce sont les deux formes à utiliser pour la comparaison et le stockage. NFKC et NFKD font tout ce que font les deux premières, puis remplacent aussi les caractères de compatibilité : la ligature fi devient deux lettres, ½ devient 1⁄2, le chiffre romain ⅲ devient XII, un A pleine chasse devient un A ordinaire. C’est exactement ce que veut un index de recherche ou un filtre de mots-clés, et exactement ce qu’il ne faut jamais appliquer à un mot de passe.
Pourquoi quatre formes et non une seule

La séparation existe parce que deux objectifs différents étaient servis. Composer et décomposer un texte est une modification canonique : le résultat a le même sens et, pour les deux premières formes, rien n’est perdu. Replier les ligatures, exposants, lettres cerclées et formes pleine chasse est une modification de compatibilité : elle se fait pour la commodité de la correspondance, et elle détruit la distinction entre deux chaînes que Unicode considère comme différentes. Les quatre formes sont toutes les combinaisons de ces deux décisions.

Le tableau ci-dessus est la liste de cas du fichier de données de cette page, vérifiée par rapport à String.prototype.normalize de la plateforme, et l’Autotest revérifie chacune de ses lignes sous vos yeux. Lisez les deux premières lignes ensemble : c’est le même texte, écrit de deux façons, et après normalisation ce sont la même chaîne.

Exemples commentés

Ce sont les chaînes que parcourt le bouton Exemple, normalisées en direct par la page plutôt que saisies comme réponses. Chacune a été choisie parce qu’une forme lui fait quelque chose de visible.

Caractères de compatibilité — ce que coûte NFKC

NFKC et NFKD sont avec perte, et cette perte est délibérée. Elles répondent à la question « est-ce le même mot », et non à « est-ce la même chaîne ». Trois conséquences en découlent, et les trois sont faciles à rater en production :

  • Ne stockez jamais un mot de passe, un jeton ou une signature sous une forme de compatibilité. Deux saisies différentes se réduiraient à la même valeur stockée, ce qui affaiblit un contrôle de longueur ou de caractères. Normalisez les mots de passe en NFC (ou pas du tout) et comparez-les octet par octet après avoir fait exactement la même chose des deux côtés.
  • Un repliement de compatibilité peut transformer un caractère en plusieurs. La ligature fi est un point de code qui devient deux lettres ; un champ validé en longueur avant normalisation peut donc être plus long après. Normalisez d’abord, validez ensuite, puis stockez.
  • NFKC ne supprime pas tout ce qui est invisible. Un sélecteur de variante survit à NFKC et NFKD, et un liant de largeur nulle aussi. C’est pourquoi cette page propose un mode d’analyse : la normalisation répond à une question sur la forme et l’identité d’un caractère, l’analyseur répond à la question de savoir si quelque chose est présent.
Graphèmes, points de code et octets

La normalisation modifie le second de ces décomptes et laisse en général le premier intact. Les trois nombres sont tous « la longueur du texte » et sont tous différents, c’est pourquoi un champ de formulaire limité à 20 caractères peut accueillir une chaîne de 20 points de code, 30 unités UTF-16 et 47 octets.

Le nombre de la colonne des graphèmes est celui qu’une personne compte. L’écart entre lui et le nombre de points de code est précisément l’objet de l’analyseur : un nom composé de 12 points de code peut s’afficher comme 12 lettres, comme 3 lettres séparées par 9 marques invisibles, ou comme un seul emoji.

Les caractères que vous ne voyez pas

Une espace de largeur nulle n’occupe aucune largeur, ne modifie aucun glyphe, et c’est un vrai caractère avec un vrai point de code. Deux identifiants qui semblent identiques à l’écran peuvent différer par l’un d’eux — c’est ainsi qu’un nom dans une liste peut être un imposteur qui se trie, se recherche et s’authentifie différemment du nom voisin. Il en va de même pour les commandes bidirectionnelles, qui peuvent faire afficher une chaîne dans un ordre différent de celui de sa saisie — une astuce utilisée pour déguiser des noms de fichiers.

L’analyseur du troisième mode les liste tous avec leur position : caractères de largeur nulle et liants, commandes bidirectionnelles, sélecteurs de variante, espaces inhabituelles, caractères de contrôle C0 et C1, points de code de zone privée, et une substitution qui n’est pas la moitié d’une paire valide, qui n’est pas un caractère du tout. Le tableau des classes plus haut sur cette page explique ce que fait chaque classe, et le bouton de retrait supprime une copie de ces caractères sans toucher à votre saisie.

Les erreurs que cette page est conçue pour détecter
  • Comparer deux chaînes sans les normaliser. 'é' === 'e\u0301' vaut faux. Normalisez les deux côtés avec la même forme avant de comparer, et utilisez la même forme dans l’index auquel vous comparez.
  • Ne normaliser que d’un côté. Une recherche qui normalise la requête mais pas la valeur stockée ne trouve rien, et ressemble à un problème de données plutôt qu’à un problème de normalisation.
  • Appliquer une forme de compatibilité à un formulaire en la croyant sans risque pour NFC. NFKC appliqué à un mot de passe réduit l’espace des mots de passe possibles et fusionne des saisies qui devaient rester distinctes.
  • Croire qu’une forme de normalisation règle une usurpation. Elle ne supprime ni un liant de largeur nulle, ni un sélecteur de variante, ni un homoglyphe — un а cyrillique et un a latin sont des points de code différents que NFC laisse tels quels.
  • Faire confiance à un nombre de caractères. Le décompte que voit l’utilisateur, celui qu’impose la colonne de la base de données et celui qu’impose une limite d’octets sont trois nombres différents, et les lignes ci-dessus montrent à quel point ils peuvent s’écarter.
  • Croire que vous obtiendrez du NFC. Un texte venant d’une méthode de saisie, d’un fichier ou d’un autre système est sous la forme qu’a produite ce système. Cette page démarre sur NFC parce que c’est la forme attendue sur le web, et non parce que c’est celle de votre saisie.
Réponses rapides

« Pourquoi ces deux chaînes ne sont-elles pas égales alors qu’elles semblent identiques ? »

Parce que l’une est écrite avec un caractère composé et l’autre avec une lettre de base suivie d’une marque combinante, ou parce que l’une contient un caractère qui n’affiche rien du tout. Passez au mode Comparer et mettez une chaîne dans chaque zone : la page indique la première position où les chaînes normalisées diffèrent et ce qui se trouve à cette position dans chacune.

« Quelle forme dois-je utiliser dans une application ? »

NFC pour tout ce qu’un humain lira, affichera, stockera et recherchera — c’est la forme recommandée par le W3C pour le web. NFD est la bonne réponse lorsque le code qui vous entoure attend des lettres de base et des marques séparées, ce qui est courant dans le traitement de texte et dans certains systèmes de fichiers sous macOS. N’utilisez NFKC ou NFKD que là où vous comparez plutôt que vous ne stockez : index de recherche, filtres de mots-clés, détection de doublons.

« Une chaîne déjà normalisée reste-t-elle normalisée après que j’ai ajouté du texte à la fin ? »

Pas nécessairement. La normalisation n’est pas préservée par la concaténation : joindre deux chaînes en NFC peut produire une suite que NFC aurait écrite autrement, car une marque combinante en tête de la seconde chaîne peut s’attacher au caractère en fin de la première. Normalisez après assemblage, pas seulement avant.

« La normalisation supprime-t-elle les caractères invisibles ? »

Non, avec une exception partielle : NFKC et NFKD remplacent bien l’espace insécable par une espace ordinaire, ce qui est un résultat visible d’un repliement de compatibilité. Tout le reste — espaces de largeur nulle, liants, commandes bidirectionnelles, sélecteurs de variante — traverse les quatre formes sans être modifié. C’est précisément à cela que sert le troisième mode.

« Pourquoi le nombre d’octets change-t-il alors que le nombre de caractères ne change pas ? »

Parce que les deux formes sont construites à partir de points de code différents qui coûtent un nombre d’octets différent en UTF-8. Un é précomposé fait deux octets ; la paire décomposée en fait trois. Le tableau des décomptes ci-dessus affiche les quatre nombres côte à côte pour la saisie et pour chaque forme, la différence est donc visible plutôt que décrite.

« Comment savoir que les résultats de cette page sont justes ? »

Appuyez sur Autotest. Il normalise chaque cas du fichier de données de cette page, revérifie les quatre formes les unes par rapport aux autres selon les relations documentées (NFC est idempotent, NFD d’une chaîne NFC est égal au NFD de l’original, etc.), fait passer chaque caractère cité dans les tables d’invisibles et de contrôle par l’analyseur, et compte les cas de graphèmes avec Intl.Segmenter.

Normes et crédits

Les quatre formes et leurs définitions proviennent de l’Annexe 15 de la norme Unicode (Unicode Normalization Forms). Les points de code utilisés comme exemples de décomposition sont ceux que la norme attribue ; cette page n’implémente pas elle-même les tables de décomposition — elle appelle String.prototype.normalize du navigateur, c’est-à-dire la même implémentation que la plateforme utilise pour la saisie, le tri et la comparaison de texte, de sorte que ce qui est affiché ici est ce que fait le reste du système de la même chaîne. Les groupes de graphèmes proviennent de Intl.Segmenter lorsque le navigateur le fournit, et d’une approximation documentée sinon.

Le texte de la page a été écrit pour ce site. Chaque exemple commenté de cette page a été produit par le code de la page elle-même, puis vérifié par rapport à la liste de cas enregistrée dans le fichier de données, elle-même vérifiée par rapport aux données NormalizationTest d’Unicode pour les mêmes caractères. Les tables de caractères invisibles et dangereux forment un sous-ensemble choisi, rédigé pour des avertissements lisibles par un humain, et non le profil de sécurité Unicode complet (UTS #39) ; la page le dit plutôt que de suggérer une exhaustivité qu’elle n’a pas.