Qu'est-ce que l'UTF-8 normalisé?

129

Le projet ICU (qui a également maintenant une bibliothèque PHP ) contient les classes nécessaires pour aider à normaliser les chaînes UTF-8 afin de faciliter la comparaison des valeurs lors de la recherche.

Cependant, j'essaie de comprendre ce que cela signifie pour les applications. Par exemple, dans quels cas est-ce que je veux "Equivalence canonique" au lieu de "Equivalence de compatibilité", ou vis-versa?

Xeoncross
la source
230
Qui ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t Horrors LIE AU COURS DES DARK HEART UNICODE ͞
ObscureRobot
@ObscureRobot Je veux vraiment savoir si ces symboles supplémentaires peuvent avoir des états ou non
eonil
1
@Eonil - Je ne suis pas sûr de ce que signifie l'état dans le contexte de l'unicode.
ObscureRobot
@ObscureRobot Par exemple, un point de code comme ceci: (begin curved line) (char1) (char2) … (charN) (end curved line)plutôt que ceci: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). En d'autres termes, unité minimale qui peut être rendue?
eonil
2
Cela semble être une bonne question en soi.
ObscureRobot

Réponses:

181

Tout ce que vous ne vouliez jamais savoir sur la normalisation Unicode

Normalisation canonique

Unicode comprend plusieurs façons d'encoder certains caractères, notamment les caractères accentués. La normalisation canonique transforme les points de code en une forme d'encodage canonique. Les points de code résultants doivent apparaître identiques à ceux d'origine, à l'exception de tout bogue dans les polices ou le moteur de rendu.

Quand utiliser

Étant donné que les résultats semblent identiques, il est toujours prudent d'appliquer la normalisation canonique à une chaîne avant de la stocker ou de l'afficher, tant que vous pouvez tolérer que le résultat ne soit pas bit par bit identique à l'entrée.

La normalisation canonique se présente sous 2 formes: NFD et NFC. Les deux sont équivalents dans le sens où l'on peut convertir entre ces deux formes sans perte. Comparer deux chaînes sous NFC donnera toujours le même résultat que les comparer sous NFD.

NFD

NFD a les personnages entièrement développés. C'est la forme de normalisation la plus rapide à calculer, mais les résultats en plus de points de code (c'est-à-dire utilise plus d'espace).

Si vous souhaitez simplement comparer deux chaînes qui ne sont pas déjà normalisées, c'est la forme de normalisation préférée, sauf si vous savez que vous avez besoin d'une normalisation de compatibilité.

NFC

NFC recombine les points de code lorsque cela est possible après l'exécution de l'algorithme NFD. Cela prend un peu plus de temps, mais entraîne des chaînes plus courtes.

Normalisation de la compatibilité

Unicode comprend également de nombreux caractères qui n'appartiennent vraiment pas, mais qui ont été utilisés dans les anciens jeux de caractères. Unicode les a ajoutés pour permettre au texte de ces jeux de caractères d'être traité comme Unicode, puis d'être reconverti sans perte.

La normalisation de compatibilité les convertit en la séquence correspondante de caractères "réels" et effectue également une normalisation canonique. Les résultats de la normalisation de la compatibilité peuvent ne pas sembler identiques aux originaux.

Les caractères contenant des informations de mise en forme sont remplacés par ceux qui n'en contiennent pas. Par exemple, le caractère est converti en 9. D'autres n'impliquent pas de différences de formatage. Par exemple, le caractère numérique romain est converti en lettres normales IX.

Évidemment, une fois cette transformation effectuée, il n'est plus possible de reconvertir sans perte le jeu de caractères d'origine.

Quand utiliser

Le Consortium Unicode suggère de penser à la normalisation de la compatibilité comme une ToUpperCasetransformation. C'est quelque chose qui peut être utile dans certaines circonstances, mais vous ne devez pas simplement l'appliquer bon gré mal gré.

Un excellent cas d'utilisation serait un moteur de recherche, car vous voudriez probablement que la recherche 9corresponde .

Une chose que vous ne devriez probablement pas faire est d'afficher le résultat de l'application de la normalisation de compatibilité à l'utilisateur.

NFKC / NFKD

Le formulaire de normalisation de compatibilité se présente sous deux formes: NFKD et NFKC. Ils ont la même relation qu'entre NFD et C.

Toute chaîne dans NFKC est intrinsèquement également dans NFC, et il en va de même pour NFKD et NFD. Ainsi NFKD(x)=NFD(NFKC(x)), et NFKC(x)=NFC(NFKD(x)), etc.

Conclusion

En cas de doute, optez pour la normalisation canonique. Choisissez NFC ou NFD en fonction du compromis espace / vitesse applicable, ou en fonction de ce qui est requis par quelque chose avec lequel vous interagissez.

Kevin Cathcart
la source
42
Une référence rapide pour se rappeler ce que signifient les abréviations: NF = forme normalisée D = décomposer (décompresser) , C = composer (compresser) K = compatibilité (puisque "C" a été pris).
Mike Spross
12
Vous voulez toujours NFD toutes les chaînes en entrée comme toute première chose, et NFC toutes les chaînes en sortie comme toute dernière chose. Ceci est bien connu.
tchrist
3
@tchrist: C'est généralement un bon conseil, sauf dans les rares cas où vous désirez que la sortie soit octet pour octet identique à l'entrée quand aucune modification n'est effectuée. Il existe d'autres cas où vous voulez NFC en mémoire ou NFD sur disque, mais ils sont l'exception plutôt que la règle.
Kevin Cathcart
@Kevin: Oui, l'entrée NFD et la sortie NFC détruiront les singletons. Je ne suis pas sûr que quiconque se soucie de cela, mais peut-être.
tchrist
2
Vous pourriez penser que, mais à partir de l'annexe: "Pour transformer une chaîne Unicode en un formulaire de normalisation Unicode donné, la première étape est de décomposer complètement la chaîne". Ainsi, même en utilisant NFC, Q-Caron deviendrait d'abord Q + Caron, et ne pourrait pas se recomposer, puisque les règles de stabilité interdisent d'ajouter le nouveau mappage de composition. NFC est effectivement défini comme NFC(x)=Recompose(NFD(x)).
Kevin Cathcart
40

Certains caractères, par exemple une lettre avec un accent (disons, é) peuvent être représentés de deux manières - un point de code unique U+00E9ou la lettre simple suivie d'un accent de combinaison U+0065 U+0301. La normalisation ordinaire choisira l'un de ceux-ci pour toujours le représenter (le point de code unique pour NFC, la forme de combinaison pour NFD).

Pour les caractères qui pourraient être représentés par plusieurs séquences de caractères de base et combinant des marques (par exemple, "s, point ci-dessous, point au-dessus" vs placer le point au-dessus puis le point en dessous ou en utilisant un caractère de base qui a déjà l'un des points), NFD choisissez également l'un d'entre eux

Les décompositions de compatibilité incluent un certain nombre de caractères qui "ne devraient pas vraiment" être des caractères, mais sont parce qu'ils étaient utilisés dans les encodages hérités. La normalisation ordinaire ne les unifiera pas (pour préserver l'intégrité aller-retour - ce n'est pas un problème pour les formes combinées car aucun encodage hérité [sauf une poignée d'encodages vietnamiens] n'utilisait les deux), mais la normalisation de compatibilité le fera. Pensez comme le signe du kilogramme "kg" qui apparaît dans certains encodages d'Asie de l'Est (ou le katakana et l'alphabet demi-largeur / pleine largeur), ou la ligature "fi" dans MacRoman.

Voir http://unicode.org/reports/tr15/ pour plus de détails.

Aléatoire832
la source
1
C'est en effet la bonne réponse. Si vous utilisez uniquement la normalisation canonique sur du texte provenant d'un jeu de caractères hérité, le résultat peut être reconverti dans ce jeu de caractères sans perte. Si vous utilisez la décomposition de compatibilité, vous vous retrouvez sans aucun caractère de compatibilité, mais il n'est plus possible de reconvertir le jeu de caractères d'origine sans perte.
Kevin Cathcart
13

Les formes normales (d'Unicode, pas de bases de données) traitent principalement (exclusivement?) De caractères qui ont des signes diacritiques. Unicode fournit certains caractères avec des signes diacritiques «intégrés», tels que U + 00C0, «Majuscule latine A avec Grave». Le même caractère peut être créé à partir d'un "Latin Capital A" (U + 0041) avec un "Combining Grave Accent" (U + 0300). Cela signifie que même si les deux séquences produisent le même caractère résultant, un octet par octet la comparaison les montrera comme étant complètement différents.

La normalisation est une tentative pour y remédier. La normalisation garantit (ou du moins essaie de) que tous les caractères sont encodés de la même manière - soit tous en utilisant un signe diacritique de combinaison distinct si nécessaire, soit tous en utilisant un seul point de code dans la mesure du possible. Du point de vue de la comparaison, peu importe le choix que vous choisissez - à peu près n'importe quelle chaîne normalisée se comparera correctement à une autre chaîne normalisée.

Dans ce cas, «compatibilité» signifie la compatibilité avec le code qui suppose qu'un point de code équivaut à un caractère. Si vous avez un code comme celui-là, vous voudrez probablement utiliser la forme normale de compatibilité. Bien que je ne l'ai jamais vu énoncé directement, les noms des formes normales impliquent que le consortium Unicode considère qu'il est préférable d'utiliser des signes diacritiques de combinaison séparés. Cela nécessite plus d'intelligence pour compter les caractères réels dans une chaîne (ainsi que des choses comme casser une chaîne intelligemment), mais est plus polyvalent.

Si vous utilisez pleinement l'ICU, il est probable que vous souhaitiez utiliser la forme normale canonique. Si vous essayez d'écrire vous-même du code qui (par exemple) suppose qu'un point de code est égal à un caractère, vous voulez probablement la forme normale de compatibilité qui rend cela vrai aussi souvent que possible.

Jerry Coffin
la source
C'est donc la partie où les fonctions Grapheme entrent alors en jeu. Non seulement le caractère est plus d'octets que l'ASCII - mais plusieurs séquences peuvent être un seul caractère, n'est-ce pas? (Contrairement aux fonctions de chaîne MB .)
Xeoncross
4
Non, le 'un point de code est un caractère' correspond à peu près à NFC (celui avec les marques de combinaison est NFD, et aucun d'eux n'est "compatibilité") - Les normalisations de compatibilité NFKC / NFKD sont un problème différent; compatibilité (ou absence de compatibilité) pour les encodages hérités qui, par exemple, avaient des caractères séparés pour le grec mu et 'micro' (c'est amusant à évoquer car la version "compatibilité" est celle qui se trouve dans le bloc Latin 1)
Random832
@ Random832: Oups, tout à fait raison. Je devrais savoir mieux que de partir de mémoire quand je n'ai pas travaillé avec depuis un an ou deux.
Jerry Coffin
@ Random832 Ce n'est pas vrai. Votre "à peu près" est trop là-bas. Considérez les deux graphèmes, ō̲̃ et ȭ̲. Il existe de nombreuses façons d'écrire chacune d'entre elles, dont exactement une est NFC et une NFD, mais d'autres existent également. Ce n'est pas un cas qu'un seul point de code. NFD pour le premier est "o\x{332}\x{303}\x{304}", et NFC est "\x{22D}\x{332}". Pour le deuxième NFD est "o\x{332}\x{304}\x{303}"et NFC est "\x{14D}\x{332}\x{303}". Cependant, il existe de nombreuses possibilités non canoniques qui leur sont canoniquement équivalentes. La normalisation permet la comparaison binaire de graphèmes canoniquement équivalents.
tchrist
5

Si deux chaînes Unicode sont canoniquement équivalentes, les chaînes sont vraiment les mêmes, en utilisant uniquement des séquences Unicode différentes. Par exemple, Ä peut être représenté en utilisant le caractère Ä ou une combinaison de A et ◌̈.

Si les chaînes sont uniquement équivalentes à la compatibilité, les chaînes ne sont pas nécessairement les mêmes, mais elles peuvent être identiques dans certains contextes. Par exemple, ff pourrait être considéré comme identique à ff.

Donc, si vous comparez des chaînes, vous devez utiliser l'équivalence canonique, car l'équivalence de compatibilité n'est pas une équivalence réelle.

Mais si vous souhaitez trier un ensemble de chaînes, il peut être judicieux d'utiliser l'équivalence de compatibilité car elles sont presque identiques.

NikiC
la source
5

C'est en fait assez simple. UTF-8 a en fait plusieurs représentations différentes du même «caractère». (J'utilise des caractères entre guillemets car ils sont différents par octet, mais pratiquement identiques). Un exemple est donné dans le document lié.

Le caractère "Ç" peut être représenté par la séquence d'octets 0xc387. Mais il peut également être représenté par un C(0x43) suivi de la séquence d'octets 0xcca7. Vous pouvez donc dire que 0xc387 et 0x43cca7 sont le même caractère. La raison qui fonctionne, c'est que 0xcca7 est une marque de combinaison; c'est-à-dire qu'il prend le caractère avant lui (un Cici), et le modifie.

Maintenant, en ce qui concerne la différence entre l'équivalence canonique et l'équivalence de compatibilité, nous devons examiner les caractères en général.

Il existe 2 types de caractères, ceux qui transmettent un sens à travers la valeur et ceux qui prennent un autre caractère et le modifient. 9 est un personnage significatif. Un super-script ⁹ prend ce sens et le modifie par présentation. Donc canoniquement, ils ont des significations différentes, mais ils représentent toujours le caractère de base.

L'équivalence canonique est l'endroit où la séquence d'octets rend le même caractère avec la même signification. L'équivalence de compatibilité se produit lorsque la séquence d'octets rend un caractère différent avec la même signification de base (même si elle peut être modifiée). Les 9 et ⁹ sont équivalents de compatibilité puisqu'ils signifient tous les deux "9", mais ne sont pas canoniquement équivalents puisqu'ils n'ont pas la même représentation.

ircmaxell
la source
@tchrist: Relisez la réponse. Je n'ai même jamais mentionné les différentes façons de représenter le même point de code. J'ai dit qu'il y avait plusieurs façons de représenter le même caractère imprimé (via des combinateurs et plusieurs caractères). Ce qui s'applique à la fois à UTF-8 et à Unicode. Donc, votre vote défavorable et votre commentaire ne s'appliquent pas du tout à ce que j'ai dit. En fait, je faisais essentiellement le même argument que l'affiche du haut ici (mais pas aussi bien) ...
ircmaxell
4

Le fait que l'équivalence canonique ou l'équivalence de compatibilité soit plus pertinente pour vous dépend de votre application. La manière ASCII de penser les comparaisons de chaînes correspond à peu près à l'équivalence canonique, mais Unicode représente de nombreux langages. Je ne pense pas qu'il soit sûr de supposer qu'Unicode encode toutes les langues d'une manière qui vous permet de les traiter comme l'ASCII d'Europe occidentale.

Les figures 1 et 2 donnent de bons exemples des deux types d'équivalence. En équivalence de compatibilité, il semble que le même nombre sous forme de sous-script et de super-script se compare égal. Mais je ne suis pas sûr que cela résout le même problème que la forme arabe cursive ou les caractères pivotés.

La dure vérité du traitement de texte Unicode est que vous devez réfléchir profondément aux exigences de traitement de texte de votre application, puis y répondre aussi bien que possible avec les outils disponibles. Cela ne répond pas directement à votre question, mais une réponse plus détaillée nécessiterait des experts linguistiques pour chacune des langues que vous prévoyez de prendre en charge.

ObscurRobot
la source
1

Le problème de la comparaison de chaînes : deux chaînes dont le contenu est équivalent pour la plupart des applications peuvent contenir des séquences de caractères différentes.

Voir l'équivalence canonique d'Unicode : si l'algorithme de comparaison est simple (ou doit être rapide), l' équivalence Unicode n'est pas effectuée. Ce problème se produit, par exemple, dans la comparaison canonique XML, voir http://www.w3.org/TR/xml-c14n

Pour éviter ce problème ... Quelle norme utiliser? "UTF8 étendu" ou "UTF8 compact"?
Utilisez "ç" ou "c + ◌̧."?

Le W3C et d'autres (ex. Noms de fichiers ) suggèrent d'utiliser le "composé comme canonique" (prenez en compte C des chaînes plus courtes "les plus compactes") ... Donc,

La norme est C ! en cas de doute, utilisez NFC

Pour l'interopérabilité, et pour les choix de «convention sur la configuration» , la recommandation est l'utilisation de NFC , pour «canoniser» les chaînes externes. Pour stocker du XML canonique, par exemple, stockez-le dans le "FORM_C". Le groupe de travail CSV sur le Web du W3C recommande également NFC (section 7.2).

PS: de "FORM_C" est la forme par défaut dans la plupart des bibliothèques. Ex. dans le normalizer.isnormalized () de PHP .


Le terme « forme de compostion » ( FORM_C) est utilisé à la fois pour dire que «une chaîne est sous la forme C-canonique» (le résultat d'une transformation NFC) et pour dire qu'un algorithme de transformation est utilisé ... Voir http: //www.macchiato.com/unicode/nfc-faq

(...) chacune des séquences suivantes (les deux premières étant des séquences à un seul caractère) représente le même caractère:

  1. U + 00C5 (Å) LETTRE MAJUSCULE LATINE A AVEC BAGUE EN CHEF
  2. SIGNE ANGSTROM U + 212B (Å)
  3. U + 0041 (A) LETTRE MAJUSCULE LATINE A + U + 030A (̊) ANNEAU DE COMBINAISON CI-DESSUS

Ces séquences sont appelées canoniquement équivalentes. Le premier de ces formulaires est appelé NFC - pour le formulaire de normalisation C, où le C est pour la composition . (...) Une fonction transformant une chaîne S en forme NFC peut être abrégée en toNFC(S), tandis qu'une fonction qui teste si S est en NFC est abrégée en isNFC(S).


Remarque: pour tester la normalisation de petites chaînes (références d'entités UTF-8 ou XML pures), vous pouvez utiliser ce convertisseur de test / normalisation en ligne .

Peter Krauss
la source
Je suis confus. Je suis allé sur cette page de testeur en ligne et j'y entre: "TÖST MÉ pleasé". et essayez les 4 normalisations données - aucune ne change mon texte de quelque manière que ce soit, sauf que cela change les codes utilisés pour présenter ces caractères. Est-ce que je pense à tort que «normalisation» signifie «supprimer tous les signes diacritiques et similaires», et cela signifie en fait - il suffit de changer le codage utf en dessous?
userfuser
Salut @userfuser vous avez peut-être besoin d'un poste, sur l'application: est-ce pour comparer ou normaliser votre texte? Mon message ici ne concerne que les applications "standardisées". PS: quand tout le monde utilise la norme, le problème de comparaison disparaît.
Peter Krauss