¿De qué se trata el UTF-8 normalizado?

129

El proyecto ICU (que ahora también tiene una biblioteca PHP ) contiene las clases necesarias para ayudar a normalizar las cadenas UTF-8 para facilitar la comparación de valores al buscar.

Sin embargo, estoy tratando de averiguar qué significa esto para las aplicaciones. Por ejemplo, ¿en qué casos quiero "Equivalencia canónica" en lugar de "Equivalencia de compatibilidad", o viceversa?

Xeoncross
fuente
230
w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝? ob
15 de
@ObscureRobot Realmente quiero saber si esos símbolos adicionales pueden tener estados o no
eonil
1
@Eonil: no estoy seguro de qué estado significa en el contexto de Unicode.
ObscureRobot
@ObscureRobot Por ejemplo, algún momento código como el siguiente: (begin curved line) (char1) (char2) … (charN) (end curved line)en lugar de esto: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). En otras palabras, ¿unidad mínima que se puede representar?
eonil
2
Eso suena como una buena pregunta por sí solo.
ObscureRobot

Respuestas:

181

Todo lo que nunca quiso saber sobre la normalización Unicode

Normalización canónica

Unicode incluye múltiples formas de codificar algunos caracteres, especialmente los caracteres acentuados. La normalización canónica cambia los puntos de código en una forma de codificación canónica. Los puntos de código resultantes deben aparecer idénticos a los originales, salvo los errores en las fuentes o el motor de representación.

Cuándo usar

Debido a que los resultados parecen idénticos, siempre es seguro aplicar la normalización canónica a una cadena antes de almacenarla o mostrarla, siempre que pueda tolerar que el resultado no sea bit por bit idéntico a la entrada.

La normalización canónica viene en 2 formas: NFD y NFC. Los dos son equivalentes en el sentido de que uno puede convertir entre estas dos formas sin pérdida. Comparar dos cadenas bajo NFC siempre dará el mismo resultado que compararlas bajo NFD.

NFD

NFD tiene los personajes completamente expandidos. Esta es la forma de normalización más rápida de calcular, pero da como resultado más puntos de código (es decir, usa más espacio).

Si solo desea comparar dos cadenas que aún no están normalizadas, esta es la forma de normalización preferida a menos que sepa que necesita normalización de compatibilidad.

NFC

NFC recombina puntos de código cuando es posible después de ejecutar el algoritmo NFD. Esto lleva un poco más de tiempo, pero da como resultado cadenas más cortas.

Normalización de compatibilidad

Unicode también incluye muchos caracteres que realmente no pertenecen, pero se usaron en conjuntos de caracteres heredados. Unicode agregó estos para permitir que el texto en esos juegos de caracteres se procese como Unicode y luego se convierta de nuevo sin pérdida.

La normalización de compatibilidad los convierte en la secuencia correspondiente de caracteres "reales", y también realiza la normalización canónica. Los resultados de la normalización de compatibilidad pueden no parecer idénticos a los originales.

Los caracteres que incluyen información de formato se reemplazan por otros que no. Por ejemplo, el personaje se convierte a 9. Otros no implican diferencias de formato. Por ejemplo, el carácter del número romano se convierte en las letras regulares IX.

Obviamente, una vez que se ha realizado esta transformación, ya no es posible volver a convertir sin pérdidas al conjunto de caracteres original.

Cuándo usar

El Consorcio Unicode sugiere pensar en la normalización de compatibilidad como una ToUpperCasetransformación. Es algo que puede ser útil en algunas circunstancias, pero no solo debe aplicarlo de todas formas.

Un excelente caso de uso sería un motor de búsqueda, ya que probablemente desee una búsqueda 9que coincida .

Una cosa que probablemente no debería hacer es mostrar el resultado de aplicar la normalización de compatibilidad al usuario.

NFKC / NFKD

La forma de normalización de compatibilidad viene en dos formas NFKD y NFKC. Tienen la misma relación que entre NFD y C.

Cualquier cadena en NFKC es inherentemente también en NFC, y lo mismo para NFKD y NFD. Por lo tanto NFKD(x)=NFD(NFKC(x)), y NFKC(x)=NFC(NFKD(x)), etc.

Conclusión

En caso de duda, vaya con la normalización canónica. Elija NFC o NFD según la compensación de espacio / velocidad aplicable, o según lo que requiera algo con lo que está interactuando.

Kevin Cathcart
fuente
42
Una referencia rápida para recordar qué significan las abreviaturas: NF = forma normalizada D = descomponer (descomprimir) , C = componer (comprimir) K = compatibilidad (ya que se tomó "C").
Mike Spross
12
Siempre desea NFD todas las cadenas de entrada como la primera cosa, y NFC todas las cadenas de salida como la última cosa. Esto es bien sabido.
tchrist
3
@tchrist: Eso generalmente es un buen consejo, excepto en los casos excepcionales en los que desea que la salida sea byte por byte idéntica a la entrada cuando no se realizan cambios. Hay otros casos en los que desea NFC en la memoria o NFD en el disco, pero son la excepción más que la regla.
Kevin Cathcart
@Kevin: Sí, la entrada de NFD y la salida de NFC destruirán los singletons. No estoy seguro de que alguien se preocupe por eso, pero posiblemente.
tchrist
2
Puede pensar eso, pero desde el anexo: "Para transformar una cadena Unicode en una Forma de normalización Unicode dada, el primer paso es descomponer completamente la cadena". Por lo tanto, incluso si ejecutamos NFC, Q-Caron se convertiría en Q + Caron, y no podría recomponerse, ya que las reglas de estabilidad prohíben agregar el nuevo mapeo de composición. NFC se define efectivamente como NFC(x)=Recompose(NFD(x)).
Kevin Cathcart
40

Algunos caracteres, por ejemplo, una letra con acento (por ejemplo, é) se pueden representar de dos maneras: un solo punto de código U+00E9o la letra simple seguida de una marca de acento combinadaU+0065 U+0301 . La normalización ordinaria elegirá uno de estos para representarlo siempre (el punto de código único para NFC, la forma de combinación para NFD).

Para los caracteres que podrían representarse mediante múltiples secuencias de caracteres base y marcas combinadas (por ejemplo, "s, punto debajo, punto arriba" frente a poner punto arriba y luego punto abajo o usar un carácter base que ya tiene uno de los puntos), NFD también elija uno de estos (a continuación va primero, como sucede)

Las descomposiciones de compatibilidad incluyen una serie de caracteres que "realmente no deberían ser", pero se deben a que se usaron en codificaciones heredadas. La normalización ordinaria no los unificará (para preservar la integridad del viaje de ida y vuelta; esto no es un problema para las formas combinadas porque no se utilizó codificación heredada [excepto un puñado de codificaciones vietnamitas]), pero la normalización de compatibilidad sí lo hará. Piense como el signo de kilogramo "kg" que aparece en algunas codificaciones de Asia oriental (o el katakana de medio ancho / ancho completo y el alfabeto), o la ligadura "fi" en MacRoman.

Ver http://unicode.org/reports/tr15/ para más detalles.

Aleatorio832
fuente
1
Esta es de hecho la respuesta correcta. Si usa solo la normalización canónica en el texto que se originó en un conjunto de caracteres heredado, el resultado se puede volver a convertir en ese conjunto de caracteres sin pérdida. Si usa la descomposición de compatibilidad, terminará sin ningún carácter de compatibilidad, pero ya no es posible volver a convertir al juego de caracteres original sin pérdida.
Kevin Cathcart
13

Las formas normales (de Unicode, no de bases de datos) tratan principalmente (¿exclusivamente?) Con caracteres que tienen signos diacríticos. Unicode proporciona algunos caracteres con signos diacríticos "incorporados", como U + 00C0, "Latin A mayúscula con tumba". Se puede crear el mismo carácter a partir de una `A mayúscula latina" (U + 0041) con un "Acento grave combinado" (U + 0300). Eso significa que, aunque las dos secuencias producen el mismo carácter resultante, un byte por byte la comparación los mostrará como completamente diferentes.

La normalización es un intento de lidiar con eso. La normalización asegura (o al menos lo intenta) que todos los caracteres están codificados de la misma manera, ya sea utilizando una marca diacrítica de combinación separada donde sea necesario, o todos utilizando un único punto de código siempre que sea posible. Desde el punto de vista de la comparación, en realidad no importa mucho lo que elija: prácticamente cualquier cadena normalizada se comparará correctamente con otra cadena normalizada.

En este caso, "compatibilidad" significa compatibilidad con el código que supone que un punto de código equivale a un carácter. Si tiene un código como ese, probablemente quiera usar la forma normal de compatibilidad. Aunque nunca lo he visto expresado directamente, los nombres de las formas normales implican que el consorcio Unicode considera preferible usar marcas diacríticas combinadas por separado. Esto requiere más inteligencia para contar los caracteres reales en una cadena (así como cosas como romper una cadena de manera inteligente), pero es más versátil.

Si está haciendo un uso completo de la UCI, es probable que desee usar la forma canónica normal. Si está intentando escribir código por su cuenta que (por ejemplo) asume que un punto de código es igual a un carácter, entonces probablemente desee la forma normal de compatibilidad que hace que eso sea cierto con la mayor frecuencia posible.

Jerry Coffin
fuente
Entonces esta es la parte donde entran en juego las funciones de grafema . No solo el carácter tiene más bytes que ASCII, sino que varias secuencias pueden ser un solo carácter, ¿verdad? (A diferencia de las funciones de cadena MB .)
Xeoncross
44
No, el 'un punto de código es un carácter' corresponde aproximadamente a NFC (el que tiene las marcas de combinación es NFD, y ninguno de ellos es "compatibilidad") - Las normalizaciones de compatibilidad NFKC / NFKD son un problema diferente; compatibilidad (o falta de ella) para codificaciones heredadas que, por ejemplo, tenían caracteres separados para griego mu y 'micro' (es divertido mencionarlo porque la versión de "compatibilidad" es la que está en el bloque latino 1)
Random832
@ Random832: Vaya, muy bien. Debería saber mejor que ir de memoria cuando no he trabajado con él durante el último año o dos.
Jerry Coffin
@ Random832 Eso no es cierto. Tu "más o menos" está demasiado ahí afuera. Considere los dos grafemas, ō̲̃ y ȭ̲. Hay muchas formas de escribir cada una de ellas, de las cuales exactamente una es NFC y una NFD, pero también existen otras. En ningún caso es que solo un punto de código. NFD para el primero es "o\x{332}\x{303}\x{304}", y NFC es "\x{22D}\x{332}". Para el segundo NFD es "o\x{332}\x{304}\x{303}"y NFC es "\x{14D}\x{332}\x{303}". Sin embargo, existen muchas posibilidades no canónicas que son canónicamente equivalentes a estas. La normalización permite la comparación binaria de grafemas canónicamente equivalentes.
tchrist
5

Si dos cadenas unicode son canónicamente equivalentes, las cadenas son realmente iguales, solo que usan diferentes secuencias unicode. Por ejemplo, Ä puede representarse utilizando el carácter Ä o una combinación de A y ◌̈.

Si las cadenas son solo equivalentes de compatibilidad, las cadenas no son necesariamente las mismas, pero pueden ser iguales en algunos contextos. Por ejemplo, ff podría considerarse igual que ff.

Entonces, si está comparando cadenas, debe usar equivalencia canónica, porque la equivalencia de compatibilidad no es equivalencia real.

Pero si desea ordenar un conjunto de cadenas, podría tener sentido utilizar la equivalencia de compatibilidad, ya que son casi idénticas.

NikiC
fuente
5

Esto es realmente bastante simple. UTF-8 en realidad tiene varias representaciones diferentes del mismo "personaje". (Uso el carácter entre comillas, ya que los bytes son diferentes, pero prácticamente son lo mismo). Se da un ejemplo en el documento vinculado.

El carácter "Ç" se puede representar como la secuencia de bytes 0xc387. Pero también se puede representar con un C(0x43) seguido de la secuencia de bytes 0xcca7. Entonces puedes decir que 0xc387 y 0x43cca7 son el mismo personaje. La razón que funciona es que 0xcca7 es una marca de combinación; es decir, toma el carácter antes que él (a Caquí) y lo modifica.

Ahora, en lo que respecta a la diferencia entre equivalencia canónica versus equivalencia de compatibilidad, necesitamos observar los caracteres en general.

Hay 2 tipos de caracteres, los que transmiten significado a través del valor y los que toman otro carácter y lo alteran. 9 es un personaje significativo. Un súper script ⁹ toma ese significado y lo altera por presentación. Así que canónicamente tienen diferentes significados, pero aún representan el carácter base.

La equivalencia canónica es donde la secuencia de bytes representa el mismo carácter con el mismo significado. La equivalencia de compatibilidad es cuando la secuencia de bytes está representando un carácter diferente con el mismo significado base (aunque pueda estar alterado). El 9 y ⁹ son equivalentes de compatibilidad ya que ambos significan "9", pero no son canónicamente equivalentes ya que no tienen la misma representación.

ircmaxell
fuente
@tchrist: Lee la respuesta nuevamente. Nunca mencioné las diferentes formas de representar el mismo punto de código. Dije que hay múltiples formas de representar el mismo carácter impreso (a través de combinadores y múltiples caracteres). Lo que se aplica tanto a UTF-8 como a Unicode. Así que tu voto negativo y tu comentario realmente no se aplican en absoluto a lo que dije. De hecho, yo estaba haciendo básicamente el mismo punto que el cartel superior hace aquí (aunque no tan bien) ...
ircmaxell
4

Si la equivalencia canónica o la equivalencia de compatibilidad es más relevante para usted depende de su aplicación. La forma de pensar ASCII sobre las comparaciones de cadenas se correlaciona aproximadamente con la equivalencia canónica, pero Unicode representa muchos idiomas. No creo que sea seguro suponer que Unicode codifica todos los idiomas de una manera que le permite tratarlos como ASCII de Europa occidental.

Las Figuras 1 y 2 proporcionan buenos ejemplos de los dos tipos de equivalencia. Bajo equivalencia de compatibilidad, parece que el mismo número en forma de sub y scripts se compararía igual. Pero no estoy seguro de que resuelva el mismo problema que la forma árabe cursiva o los caracteres rotados.

La dura realidad del procesamiento de texto Unicode es que tiene que pensar profundamente sobre los requisitos de procesamiento de texto de su aplicación y luego abordarlos lo mejor que pueda con las herramientas disponibles. Eso no responde directamente a su pregunta, pero una respuesta más detallada requeriría expertos lingüísticos para cada uno de los idiomas que espera admitir.

ObscureRobot
fuente
1

El problema de comparar cadenas : dos cadenas con contenido equivalente para la mayoría de las aplicaciones pueden contener secuencias de caracteres diferentes.

Consulte la equivalencia canónica de Unicode : si el algoritmo de comparación es simple (o debe ser rápido), la equivalencia de Unicode no se realiza. Este problema ocurre, por ejemplo, en la comparación canónica XML, consulte http://www.w3.org/TR/xml-c14n

Para evitar este problema ... ¿Qué estándar usar? ¿"UTF8 expandido" o "UTF8 compacto"?
Utilice "ç" o "c + ◌̧".

W3C y otros (por ejemplo , nombres de archivo ) sugieren utilizar el "compuesto como canónico" (tenga en cuenta C de las cadenas más cortas "más compactas") ... Entonces,

El estándar es C ! en duda use NFC

Para la interoperabilidad, y para las opciones de "convención sobre configuración" , la recomendación es el uso de NFC , para "canonizar" cadenas externas. Para almacenar XML canónico, por ejemplo, guárdelo en "FORM_C". El Grupo de Trabajo CSV en la Web del W3C también recomienda NFC (sección 7.2).

PS: de "FORM_C" es el formulario predeterminado en la mayoría de las bibliotecas. Ex. en PHP normalizer.isnormalized () .


El término " forma de composición " ( FORM_C) se usa para ambos, para decir que "una cadena está en la forma C-canónica" (el resultado de una transformación NFC) y para decir que se usa un algoritmo de transformación ... Ver http: //www.macchiato.com/unicode/nfc-faq

(...) cada una de las siguientes secuencias (las dos primeras son secuencias de un solo carácter) representan el mismo carácter:

  1. U + 00C5 (Å) LETRA DE CAPITAL LATINA A CON ANILLO ARRIBA
  2. U + 212B (Å) SIGNO DE ANGSTROM
  3. U + 0041 (A) LETRA DE CAPITAL LATINO A + U + 030A (̊) ANILLO COMBINADO ARRIBA

Estas secuencias se denominan canónicamente equivalentes. El primero de estos formularios se llama NFC, para el formulario de normalización C, donde el C es para la composición . (...) Una función que transforma una cadena S en la forma NFC puede abreviarse como toNFC(S), mientras que una que prueba si S está en NFC se abrevia como isNFC(S).


Nota: para probar la normalización de cadenas pequeñas (referencias UTF-8 puras o entidades XML), puede usar este convertidor en línea de prueba / normalización .

Peter Krauss
fuente
Estoy confundido. Fui a esta página de prueba en línea y entré allí: "TÖST MÉ pleasé". e intente las 4 normalizaciones dadas: ninguna cambia mi texto de ninguna manera, bueno, excepto que cambia los códigos utilizados para presentar esos caracteres. ¿Estoy pensando erróneamente que "normalización" significa "eliminar todos los signos diacríticos y similares", y en realidad significa: simplemente cambiar la codificación utf a continuación?
userfuser
Hola @userfuser, quizás necesites un puesto sobre la aplicación: ¿es para comparar o estandarizar tu texto? Mi publicación aquí solo trata sobre "estandarizar" las aplicaciones. PD: cuando todo el mundo usa el estándar, el problema de comparación desaparece.
Peter Krauss el