Já ouviu falar da normalização Unicode? Você não está sozinho. Mas todo mundo precisa saber sobre isso. A normalização pode poupar muitos problemas. Mais cedo ou mais tarde, algo semelhante ao mostrado na figura a seguir acontece com qualquer desenvolvedor.
Zoë não é ZoëE isso, a propósito, não é um exemplo de outro JavaScript
estranho . O autor do material, cuja tradução publicamos hoje, diz que pode mostrar como o mesmo problema se manifesta ao usar quase todas as linguagens de programação existentes. Em particular, estamos falando sobre Python, Go e até scripts de shell. Como lidar com isso?
Antecedentes
Encontrei o problema Unicode pela primeira vez há muitos anos, quando escrevi um aplicativo (no Objective-C) que importava uma lista de contatos do catálogo de endereços do usuário e de suas redes sociais, após o que eliminei duplicatas. Em certas situações, algumas pessoas estão na lista duas vezes. Isso aconteceu devido ao fato de seus nomes, de acordo com o programa, não serem da mesma string.
Embora no exemplo acima as duas linhas pareçam exatamente iguais, da maneira como são apresentadas no sistema, os bytes em que são armazenados no disco diferem. No primeiro nome,
"Zoë"
o caractere ë (e com trema) representa um único ponto de código Unicode. No segundo caso, estamos lidando com a decomposição, com a abordagem de representar caracteres usando vários caracteres. Se você, no seu aplicativo, trabalha com seqüências de caracteres Unicode, precisa considerar o fato de que os mesmos caracteres podem ser representados de maneiras diferentes.
Como chegamos ao emoji: em poucas palavras sobre a codificação de caracteres
Os computadores trabalham com bytes, que são apenas números. Para poder processar textos em computadores, as pessoas concordaram com a correspondência de caracteres e números e chegaram a acordos sobre a aparência da representação visual dos caracteres.
O primeiro desses acordos foi representado pela codificação ASCII (American Standard Code for Information Interchange). Essa codificação usava 7 bits e poderia representar 128 caracteres, incluindo o alfabeto latino (letras maiúsculas e minúsculas), números e sinais de pontuação básicos. O ASCII também incluiu muitos caracteres "não imprimíveis", como alimentação de linha, guias, retornos de carro e outros. Por exemplo, em ASCII, a letra latina M (maiúscula m) é codificada como 77 (4D em notação hexadecimal).
O problema com o ASCII é que, embora 128 caracteres possam ser suficientes para representar todos os caracteres que as pessoas que trabalham com textos em inglês costumam usar, esse número de caracteres não é suficiente para representar textos em outros idiomas e vários caracteres especiais, como emojis.
A solução para esse problema foi a adoção do padrão Unicode, que visava a possibilidade de representar cada caractere usado em todos os textos modernos e antigos, incluindo caracteres como emojis. Por exemplo, no padrão Unicode 12.0 mais recente, existem mais de 137.000 caracteres.
O padrão Unicode pode ser implementado usando uma variedade de métodos de codificação de caracteres. Os mais comuns são UTF-8 e UTF-16. Note-se que no espaço da web o mais comum é o padrão para codificação de textos UTF-8.
O padrão UTF-8 usa 1 a 4 bytes para representar caracteres. UTF-8 é um superconjunto de ASCII, portanto, seus primeiros 128 caracteres correspondem aos caracteres representados na tabela de códigos ASCII. O padrão UTF-16, por outro lado, usa 2 a 4 bytes para representar 1 caractere.
Por que existem dois padrões? O fato é que textos em idiomas ocidentais geralmente são codificados com mais eficiência usando o padrão UTF-8 (já que a maioria dos caracteres desses textos pode ser representada como códigos de tamanho de 1 byte). Se falamos sobre idiomas orientais, podemos dizer que os arquivos que armazenam textos escritos nesses idiomas geralmente ficam menos quando se usa o UTF-16.
Pontos de código Unicode e codificação de caracteres
Cada caractere no padrão Unicode recebe um número de identificação chamado de ponto de código. Por exemplo, um emoji de ponto de código

é
U + 1F436 .
Ao codificar este ícone, ele pode ser representado como várias seqüências de bytes:
- UTF-8: 4 bytes,
0xF0 0x9F 0x90 0xB6
- UTF-16: 4 bytes,
0xD83D 0xDC36
No código JavaScript abaixo, todos os três comandos imprimem o mesmo caractere no console do navegador.
//
console.log('
') // => 
// Unicode (ES2015+)
console.log('\u{1F436}') // => 
// UTF-16
// ( 2 )
console.log('\uD83D\uDC36') // => 
Os mecanismos internos da maioria dos intérpretes JavaScript (incluindo Node.js e navegadores modernos) usam UTF-16. Isso significa que o ícone do cão que estamos considerando é armazenado usando duas unidades de código UTF-16 (16 bits cada). Portanto, o que o seguinte código gera não deve parecer incompreensível para você:
console.log('
'.length) // => 2
Combinação de caracteres
Agora, voltemos ao ponto em que começamos, a saber, por que os símbolos que parecem iguais para uma pessoa têm uma representação interna diferente.
Alguns caracteres Unicode foram projetados para modificar outros caracteres. Eles são chamados de caracteres combinados. Eles se aplicam aos caracteres base, por exemplo:
n + ˜ = ñ
u + ¨ = ü
e + ´ = é
Como você pode ver no exemplo anterior, caracteres combináveis permitem adicionar diacríticos aos caracteres base. Mas os recursos de transformação de caracteres do Unicode não se limitam a isso. Por exemplo, algumas seqüências de caracteres podem ser representadas como ligaduras (para que ae possa se transformar em æ).
O problema é que caracteres especiais podem ser representados de várias maneiras.
Por exemplo, a letra é pode ser representada de duas maneiras:
- Usando um único ponto de código U + 00E9 .
- Usando uma combinação da letra e com o sinal agudo, ou seja, usando dois pontos de código - U + 0065 e U + 0301 .
Os caracteres resultantes do uso de qualquer uma dessas maneiras de representar a letra é terão a mesma aparência, mas, quando comparados, acontece que os caracteres são diferentes. As linhas que os contêm terão comprimentos diferentes. Você pode verificar isso executando o seguinte código no console do 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
Isso pode levar a erros inesperados. Por exemplo, eles podem ser expressos no fato de que o programa, por razões desconhecidas, não é capaz de encontrar algumas entradas no banco de dados, em que o usuário, digitando a senha correta, não pode efetuar login no sistema.
Normalização de linha
Os problemas acima têm uma solução simples, que consiste em normalizar as strings, trazendo-as para a "representação canônica".
Existem quatro formas padrão (algoritmos) de normalização:
- NFC: Normalização da composição canônica.
- NFD: Normalização do formulário Decomposição canônica.
- NFKC: composição de compatibilidade do formulário de normalização.
- NFKD: decomposição de compatibilidade do formulário de normalização.
A forma de normalização mais usada é a NFC. Ao usar esse algoritmo, todos os caracteres são decompostos primeiro, após o que todas as seqüências combinadas são recompostas na ordem definida pelo padrão. Para uso prático, você pode escolher qualquer forma. O principal é aplicá-lo de forma consistente. Como resultado, o recebimento dos mesmos dados na entrada do programa sempre levará ao mesmo resultado.
No JavaScript, começando com o padrão ES2015 (ES6), existe um método
interno para normalizar seqüências de caracteres -
String.prototype.normalize ([form]) . Você pode usá-lo no ambiente Node.js. e em quase todos os navegadores modernos. O argumento do
form
desse método é o identificador de seqüência do formulário de normalização. O padrão é o formulário NFC.
Voltamos ao exemplo considerado anteriormente, aplicando a normalização desta 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
Sumário
Se você estiver desenvolvendo um aplicativo Web e usando o que o usuário digita nele, sempre normalize os dados de texto recebidos. Em JavaScript, você pode usar o método de sequência padrão
normalize () para executar a normalização.
Caros leitores! Você encontrou problemas com cadeias que podem ser resolvidas com a normalização?
