Avez-vous déjà entendu parler de la normalisation Unicode? Tu n'es pas seul. Mais tout le monde doit savoir cela. La normalisation peut vous éviter beaucoup de problèmes. Tôt ou tard, quelque chose de similaire à celui illustré dans la figure suivante se produit avec n'importe quel développeur.
Zoë n'est pas ZoëEt cela, en passant, n'est pas un exemple d'un autre
étrange JavaScript. L'auteur du matériel, dont nous publions la traduction aujourd'hui, dit qu'il peut montrer comment le même problème se manifeste en utilisant presque tous les langages de programmation existants. En particulier, nous parlons de scripts Python, Go et même shell. Comment y faire face?
Contexte
J'ai rencontré le problème Unicode pour la première fois il y a de nombreuses années lorsque j'ai écrit une application (en Objective-C) qui importait une liste de contacts du carnet d'adresses de l'utilisateur et de ses réseaux sociaux, après quoi j'ai éliminé les doublons. Dans certaines situations, il s'est avéré que certaines personnes figurent deux fois sur la liste. Cela est dû au fait que leurs noms, selon le programme, n'étaient pas la même chaîne.
Bien que dans l'exemple ci-dessus, les deux lignes se ressemblent exactement, la façon dont elles sont présentées dans le système, les octets dans lesquels elles sont stockées sur le disque diffèrent. Dans le prénom,
"Zoë"
le caractère ë (e avec tréma) représente un seul point de code Unicode. Dans le second cas, nous avons affaire à la décomposition, avec l'approche de représentation des caractères à l'aide de plusieurs caractères. Si vous, dans votre application, travaillez avec des chaînes Unicode, vous devez tenir compte du fait que les mêmes caractères peuvent être représentés de différentes manières.
Comment nous sommes arrivés aux emoji: en bref sur l'encodage des caractères
Les ordinateurs fonctionnent avec des octets, qui ne sont que des nombres. Afin de pouvoir traiter des textes sur ordinateur, les gens se sont mis d'accord sur la correspondance des caractères et des nombres, et se sont mis d'accord sur l'apparence de la représentation visuelle des caractères.
Le premier accord de ce type était représenté par le codage ASCII (American Standard Code for Information Interchange). Cet encodage utilisait 7 bits et pouvait représenter 128 caractères, dont l'alphabet latin (lettres majuscules et minuscules), les chiffres et les signes de ponctuation de base. ASCII comprenait également de nombreux caractères «non imprimables», tels qu'un saut de ligne, des tabulations, des retours chariot et autres. Par exemple, en ASCII, la lettre latine M (majuscule m) est codée 77 (4D en notation hexadécimale).
Le problème avec ASCII est que même si 128 caractères peuvent être suffisants pour représenter tous les caractères que les personnes travaillant avec des textes anglais utilisent habituellement, ce nombre de caractères n'est pas suffisant pour représenter des textes dans d'autres langues et divers caractères spéciaux comme les emojis.
La solution à ce problème a été l'adoption de la norme Unicode, qui visait la possibilité de représenter chaque caractère utilisé dans tous les textes modernes et anciens, y compris les caractères comme les emojis. Par exemple, dans la norme Unicode 12.0 la plus récente, il y a plus de 137 000 caractères.
La norme Unicode peut être implémentée à l'aide de diverses méthodes de codage de caractères. Les plus courants sont UTF-8 et UTF-16. Il convient de noter que dans l'espace Web, le plus courant est la norme de codage des textes UTF-8.
La norme UTF-8 utilise 1 à 4 octets pour représenter les caractères. UTF-8 est un sur-ensemble d'ASCII, donc ses 128 premiers caractères correspondent aux caractères représentés dans la table de codes ASCII. La norme UTF-16, d'autre part, utilise 2 à 4 octets pour représenter 1 caractère.
Pourquoi existe-t-il les deux normes? Le fait est que les textes dans les langues occidentales sont généralement plus efficacement encodés en utilisant la norme UTF-8 (car la plupart des caractères de ces textes peuvent être représentés sous la forme de codes de 1 octet). Si nous parlons des langues orientales, nous pouvons dire que les fichiers qui stockent des textes écrits dans ces langues obtiennent généralement moins lors de l'utilisation de l'UTF-16.
Points de code Unicode et codage de caractères
Chaque caractère de la norme Unicode se voit attribuer un numéro d'identification appelé point de code. Par exemple, un emoji de point de code

est
U + 1F436 .
Lors du codage de cette icône, elle peut être représentée sous la forme de différentes séquences d'octets:
- UTF-8: 4 octets,
0xF0 0x9F 0x90 0xB6
- UTF-16: 4 octets,
0xD83D 0xDC36
Dans le code JavaScript ci-dessous, les trois commandes impriment le même caractère sur la console du navigateur.
//
console.log('
') // => 
// Unicode (ES2015+)
console.log('\u{1F436}') // => 
// UTF-16
// ( 2 )
console.log('\uD83D\uDC36') // => 
Les mécanismes internes de la plupart des interprètes JavaScript (y compris Node.js et les navigateurs modernes) utilisent UTF-16. Cela signifie que l'icône de chien que nous envisageons est stockée à l'aide de deux unités de code UTF-16 (16 bits chacune). Par conséquent, ce que les sorties de code suivantes ne devraient pas vous sembler incompréhensibles:
console.log('
'.length) // => 2
Combinaison de caractères
Revenons maintenant à notre point de départ, à savoir, pourquoi les symboles qui se ressemblent pour une personne ont une représentation interne différente.
Certains caractères Unicode sont conçus pour modifier d'autres caractères. Ils sont appelés combinaison de caractères. Ils s'appliquent aux caractères de base. Par exemple:
n + ˜ = ñ
u + ¨ = ü
e + ´ = é
Comme vous pouvez le voir dans l'exemple précédent, les caractères combinables vous permettent d'ajouter des signes diacritiques aux caractères de base. Mais les capacités de transformation de caractères d'Unicode ne se limitent pas à cela. Par exemple, certaines séquences de caractères peuvent être représentées sous forme de ligatures (donc ae peut se transformer en æ).
Le problème est que les caractères spéciaux peuvent être représentés de différentes manières.
Par exemple, la lettre é peut être représentée de deux manières:
- En utilisant un seul point de code U + 00E9 .
- En utilisant une combinaison de la lettre e et du signe aigu, c'est-à-dire en utilisant deux points de code - U + 0065 et U + 0301 .
Les caractères résultant de l'utilisation de l'une de ces façons de représenter la lettre é auront la même apparence, mais en comparaison, il s'avère que les caractères sont différents. Les lignes les contenant auront des longueurs différentes. Vous pouvez le vérifier en exécutant le code suivant dans la console du navigateur.
console.log('\u00e9') // => é console.log('\u0065\u0301') // => é console.log('\u00e9' == '\u0065\u0301') // => false console.log('\u00e9'.length) // => 1 console.log('\u0065\u0301'.length) // => 2
Cela peut entraîner des erreurs inattendues. Par exemple, ils peuvent s'exprimer par le fait que le programme, pour des raisons inconnues, n'est pas en mesure de trouver certaines entrées dans la base de données, en ce que l'utilisateur, en entrant le mot de passe correct, ne peut pas se connecter au système.
Normalisation de ligne
Les problèmes ci-dessus ont une solution simple, qui consiste à normaliser les chaînes, à les amener à la "représentation canonique".
Il existe quatre formes standard (algorithmes) de normalisation:
- NFC: Forme de normalisation Composition canonique.
- NFD: Décomposition canonique de normalisation.
- NFKC: Composition de compatibilité de forme de normalisation.
- NFKD: décomposition de compatibilité de forme de normalisation.
La forme de normalisation la plus couramment utilisée est le NFC. Lors de l'utilisation de cet algorithme, tous les caractères sont d'abord décomposés, après quoi toutes les séquences de combinaison sont recomposées dans l'ordre défini par la norme. Pour une utilisation pratique, vous pouvez choisir n'importe quelle forme. L'essentiel est de l'appliquer de manière cohérente. Par conséquent, la réception des mêmes données à l'entrée du programme conduira toujours au même résultat.
En JavaScript, à partir de la norme ES2015 (ES6), il existe une méthode intégrée pour normaliser les chaînes -
String.prototype.normalize ([form]) . Vous pouvez l'utiliser dans l'environnement Node.js et dans presque tous les navigateurs modernes. L'argument
form
de cette méthode est l'identificateur de chaîne du formulaire de normalisation. La valeur par défaut est le formulaire NFC.
Nous revenons à l'exemple précédemment considéré, en appliquant cette fois la normalisation:
const str = '\u0065\u0301' console.log(str == '\u00e9') // => false const normalized = str.normalize('NFC') console.log(normalized == '\u00e9') // => true console.log(normalized.length) // => 1
Résumé
Si vous développez une application Web et utilisez ce que l'utilisateur y entre, normalisez toujours les données texte reçues. En JavaScript, vous pouvez utiliser la méthode de chaîne standard
normalize () pour effectuer la normalisation.
Chers lecteurs! Avez-vous rencontré des problèmes avec les chaînes qui peuvent être résolus avec la normalisation?
