¿Has oído hablar de la normalización Unicode? No estas solo Pero todos necesitan saber sobre esto. La normalización puede ahorrarle muchos problemas. Tarde o temprano, algo similar al que se muestra en la siguiente figura sucede con cualquier desarrollador.
Zoë no es ZoëY esto, por cierto, no es un ejemplo de otro JavaScript
extraño . El autor del material, cuya traducción publicamos hoy, dice que puede mostrar cómo se manifiesta el mismo problema al usar casi todos los lenguajes de programación existentes. En particular, estamos hablando de Python, Go e incluso scripts de shell. ¿Cómo lidiar con eso?
Antecedentes
Me encontré con el problema de Unicode hace muchos años cuando escribí una aplicación (en Objective-C) que importaba una lista de contactos de la libreta de direcciones del usuario y de sus redes sociales, después de lo cual eliminé los duplicados. En ciertas situaciones, resultó que algunas personas están en la lista dos veces. Esto sucedió debido al hecho de que sus nombres, según el programa, no eran la misma cadena.
Aunque en el ejemplo anterior las dos líneas se ven exactamente iguales, la forma en que se presentan en el sistema, los bytes en los que se almacenan en el disco difieren. En el primer nombre,
"Zoë"
el carácter ë (e con diéresis) representa un único punto de código Unicode. En el segundo caso, estamos tratando con la descomposición, con el enfoque de representar caracteres usando varios caracteres. Si, en su aplicación, trabaja con cadenas Unicode, debe tener en cuenta el hecho de que los mismos caracteres se pueden representar de diferentes maneras.
Cómo llegamos a los emoji: en pocas palabras sobre la codificación de caracteres
Las computadoras funcionan con bytes, que son solo números. Para poder procesar textos en computadoras, las personas acordaron la correspondencia de caracteres y números, y llegaron a acuerdos sobre cómo debería verse la representación visual de los caracteres.
El primer acuerdo de este tipo estuvo representado por la codificación ASCII (Código Estándar Americano para el Intercambio de Información). Esta codificación utilizaba 7 bits y podía representar 128 caracteres, que incluían el alfabeto latino (letras mayúsculas y minúsculas), números y signos de puntuación básicos. ASCII también incluyó muchos caracteres "no imprimibles", como un avance de línea, pestañas, retornos de carro y otros. Por ejemplo, en ASCII, la letra latina M (m mayúscula) está codificada como 77 (4D en notación hexadecimal).
El problema con ASCII es que, aunque 128 caracteres pueden ser suficientes para representar todos los caracteres que suelen usar las personas que usan textos en inglés, este número de caracteres no es suficiente para representar textos en otros idiomas y varios caracteres especiales como emojis.
La solución a este problema fue la adopción del estándar Unicode, que apuntaba a la posibilidad de representar cada carácter utilizado en todos los textos modernos y antiguos, incluidos caracteres como emojis. Por ejemplo, en el estándar Unicode 12.0 más reciente, hay más de 137,000 caracteres.
El estándar Unicode se puede implementar utilizando una variedad de métodos de codificación de caracteres. Los más comunes son UTF-8 y UTF-16. Cabe señalar que en el espacio web el más común es el estándar para codificar textos UTF-8.
El estándar UTF-8 usa de 1 a 4 bytes para representar caracteres. UTF-8 es un superconjunto de ASCII, por lo que sus primeros 128 caracteres coinciden con los caracteres representados en la tabla de códigos ASCII. El estándar UTF-16, por otro lado, usa de 2 a 4 bytes para representar 1 carácter.
¿Por qué hay ambos estándares? El hecho es que los textos en idiomas occidentales generalmente se codifican de manera más eficiente utilizando el estándar UTF-8 (ya que la mayoría de los caracteres en dichos textos se pueden representar como códigos de 1 byte). Si hablamos de idiomas orientales, entonces podemos decir que los archivos que almacenan textos escritos en estos idiomas generalmente obtienen menos cuando se usa UTF-16.
Puntos de código Unicode y codificación de caracteres
A cada carácter en el estándar Unicode se le asigna un número de identificación llamado punto de código. Por ejemplo, un emoji de punto de código

es
U + 1F436 .
Al codificar este icono, se puede representar como varias secuencias de bytes:
- UTF-8: 4 bytes,
0xF0 0x9F 0x90 0xB6
- UTF-16: 4 bytes,
0xD83D 0xDC36
En el siguiente código de JavaScript, los tres comandos imprimen el mismo carácter en la consola del navegador.
//
console.log('
') // => 
// Unicode (ES2015+)
console.log('\u{1F436}') // => 
// UTF-16
// ( 2 )
console.log('\uD83D\uDC36') // => 
Los mecanismos internos de la mayoría de los intérpretes de JavaScript (incluidos Node.js y los navegadores modernos) usan UTF-16. Esto significa que el icono de perro que estamos considerando se almacena utilizando dos unidades de código UTF-16 (16 bits cada una). Por lo tanto, lo que el siguiente código de salida no debería parecerle incomprensible:
console.log('
'.length) // => 2
Combinación de personajes
Ahora, de vuelta al punto de partida, es decir, hablemos sobre por qué los símbolos que se ven iguales para una persona tienen una representación interna diferente.
Algunos caracteres Unicode están diseñados para modificar otros caracteres. Se llaman personajes combinados. Se aplican a los caracteres base. Por ejemplo:
n + ˜ = ñ
u + ¨ = ü
e + ´ = é
Como puede ver en el ejemplo anterior, los caracteres combinables le permiten agregar signos diacríticos a los caracteres base. Pero las capacidades de transformación de caracteres de Unicode no se limitan a esto. Por ejemplo, algunas secuencias de caracteres se pueden representar como ligaduras (por lo que ae puede convertirse en æ).
El problema es que los caracteres especiales se pueden representar de varias maneras.
Por ejemplo, la letra é se puede representar de dos maneras:
- Usando un único punto de código U + 00E9 .
- Usando una combinación de la letra e y el signo agudo, es decir, usando dos puntos de código: U + 0065 y U + 0301 .
Los caracteres resultantes del uso de cualquiera de estas formas de representar la letra é se verán iguales, pero cuando se comparan, resulta que los caracteres son diferentes. Las líneas que los contienen tendrán diferentes longitudes. Puede verificar esto ejecutando el siguiente código en la consola del navegador.
console.log('\u00e9') // => é console.log('\u0065\u0301') // => é console.log('\u00e9' == '\u0065\u0301') // => false console.log('\u00e9'.length) // => 1 console.log('\u0065\u0301'.length) // => 2
Esto puede conducir a errores inesperados. Por ejemplo, se pueden expresar en el hecho de que el programa, por razones desconocidas, no puede encontrar algunas entradas en la base de datos, ya que el usuario, al ingresar la contraseña correcta, no puede iniciar sesión en el sistema.
Normalización de linea
Los problemas anteriores tienen una solución simple, que consiste en normalizar las cadenas, en llevarlas a la "representación canónica".
Hay cuatro formas estándar (algoritmos) de normalización:
- NFC: Normalización Forma Composición canónica.
- NFD: Normalización de la descomposición canónica.
- NFKC: Composición de compatibilidad de formularios de normalización.
- NFKD: Descomposición de compatibilidad de formularios de normalización.
La forma de normalización más utilizada es la NFC. Cuando se usa este algoritmo, todos los caracteres se descomponen primero, después de lo cual todas las secuencias combinadas se vuelven a componer en el orden definido por el estándar. Para uso práctico, puede elegir cualquier forma. Lo principal es aplicarlo consistentemente. Como resultado, la recepción de los mismos datos en la entrada del programa siempre conducirá al mismo resultado.
En JavaScript, comenzando con el estándar ES2015 (ES6), hay un método incorporado para normalizar cadenas:
String.prototype.normalize ([formulario]) . Puede usarlo en el entorno Node.js y en casi todos los navegadores modernos. El argumento de
form
de este método es el identificador de cadena del formulario de normalización. El valor predeterminado es el formulario NFC.
Volvemos al ejemplo considerado anteriormente, aplicando la normalización esta vez:
const str = '\u0065\u0301' console.log(str == '\u00e9') // => false const normalized = str.normalize('NFC') console.log(normalized == '\u00e9') // => true console.log(normalized.length) // => 1
Resumen
Si está desarrollando una aplicación web y está utilizando lo que el usuario ingresa en ella, siempre normalice los datos de texto recibidos. En JavaScript, puede usar el método de cadena estándar
normalize () para realizar la normalización.
Estimados lectores! ¿Ha encontrado problemas con las cadenas que se pueden resolver con la normalización?
