O que é o UTF-8 normalizado?

129

O projeto ICU (que agora também possui uma biblioteca PHP ) contém as classes necessárias para ajudar a normalizar cadeias UTF-8 para facilitar a comparação de valores durante a pesquisa.

No entanto, estou tentando descobrir o que isso significa para aplicativos. Por exemplo, em quais casos eu quero "Equivalência canônica" em vez de "Equivalência de compatibilidade" ou vice-versa?

Xeoncross
fonte
230
Quem ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t horrores se encontram no coração escuro do Unicode ͞
ObscureRobot
@ObscureRobot eu realmente quero saber se esses símbolos extras pode ter estados ou não
eonil
1
@Eonil - Não sei ao certo o que significa estado no contexto do unicode.
ObscureRobot
@ObscureRobot Por exemplo, algum ponto código como este: (begin curved line) (char1) (char2) … (charN) (end curved line)em vez do que isso: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). Em outras palavras, unidade mínima que pode ser renderizada?
eonil
2
Isso soa como uma boa pergunta por si só.
precisa saber é o seguinte

Respostas:

181

Tudo o que você nunca quis saber sobre a normalização Unicode

Normalização canônica

O Unicode inclui várias maneiras de codificar alguns caracteres, principalmente caracteres acentuados. A normalização canônica altera os pontos de código em uma forma de codificação canônica. Os pontos de código resultantes devem parecer idênticos aos originais, com exceção de erros nas fontes ou no mecanismo de renderização.

Quando usar

Como os resultados parecem idênticos, é sempre seguro aplicar a normalização canônica a uma cadeia de caracteres antes de armazená-la ou exibi-la, desde que você possa tolerar que o resultado não seja bit por bit idêntico à entrada.

A normalização canônica vem em 2 formas: NFD e NFC. Os dois são equivalentes no sentido de que se pode converter entre essas duas formas sem perda. Comparar duas seqüências de caracteres em NFC sempre dará o mesmo resultado que compará-las em NFD.

NFD

NFD tem os caracteres totalmente expandidos. Este é o formulário de normalização mais rápido para calcular, mas resulta em mais pontos de código (isto é, usa mais espaço).

Se você deseja comparar apenas duas cadeias que ainda não estão normalizadas, este é o formulário de normalização preferido, a menos que você saiba que precisa da normalização de compatibilidade.

NFC

A NFC recombina pontos de código quando possível depois de executar o algoritmo NFD. Isso leva um pouco mais de tempo, mas resulta em seqüências mais curtas.

Normalização de compatibilidade

O Unicode também inclui muitos caracteres que realmente não pertencem, mas foram usados ​​em conjuntos de caracteres herdados. O Unicode os adicionou para permitir que o texto nesses conjuntos de caracteres seja processado como Unicode e depois convertido novamente sem perdas.

A normalização de compatibilidade os converte na sequência correspondente de caracteres "reais" e também executa a normalização canônica. Os resultados da normalização de compatibilidade podem não parecer idênticos aos originais.

Caracteres que incluem informações de formatação são substituídos por caracteres que não o fazem. Por exemplo, o caractere é convertido em 9. Outros não envolvem diferenças de formatação. Por exemplo, o caractere do número romano é convertido em letras regulares IX.

Obviamente, uma vez que essa transformação tenha sido executada, não é mais possível converter sem perdas no conjunto de caracteres original.

Quando usar

O Unicode Consortium sugere pensar na normalização da compatibilidade como uma ToUpperCasetransformação. É algo que pode ser útil em algumas circunstâncias, mas você não deve apenas aplicá-lo à vontade.

Um excelente caso de uso seria um mecanismo de pesquisa, pois você provavelmente desejaria que uma pesquisa 9correspondesse .

Uma coisa que você provavelmente não deve fazer é exibir o resultado da aplicação da normalização de compatibilidade ao usuário.

NFKC / NFKD

O formulário de normalização de compatibilidade vem em dois formulários NFKD e NFKC. Eles têm a mesma relação entre NFD e C.

Qualquer cadeia de caracteres no NFKC também é inerentemente no NFC e o mesmo para o NFKD e o NFD. Assim NFKD(x)=NFD(NFKC(x)), e NFKC(x)=NFC(NFKD(x)), etc.

Conclusão

Em caso de dúvida, siga a normalização canônica. Escolha NFC ou NFD com base na troca de espaço / velocidade aplicável ou com base no que é exigido por algo com o qual você está interagindo.

Kevin Cathcart
fonte
42
Uma referência rápida para lembrar o que significam as abreviaturas: NF = forma normalizada D = decompor (descomprimir) , C = compor (comprimir) K = compatibilidade (desde que "C" foi utilizado).
quer
12
Você sempre deseja NFD todas as seqüências de caracteres de entrada como a primeira coisa e NFC todas as seqüências de caracteres de saída como a última coisa. Isso é bem conhecido.
tchrist
3
@ tchrist: Esse geralmente é um bom conselho, exceto nos raros casos em que você deseja que a saída seja byte por byte idêntico à entrada quando nenhuma alteração for feita. Existem outros casos em que você deseja NFC na memória ou NFD no disco, mas eles são a exceção e não a regra.
Kevin Cathcart
@ Kevin: Sim, o NFD in e NFC out destruirá os singletons. Não tenho certeza de que alguém se importe com isso, mas possivelmente.
Tbrist
2
Você pode pensar isso, mas a partir do anexo: "Para transformar uma string Unicode em um determinado Formulário de Normalização Unicode, a primeira etapa é decompor completamente a string". Assim, mesmo executando NFC, o Q-Caron se tornaria Q + Caron e não poderia se recompor, pois as regras de estabilidade proíbem a adição do novo mapeamento da composição. NFC é efetivamente definido como NFC(x)=Recompose(NFD(x)).
Kevin Cathcart
40

Alguns caracteres, por exemplo, uma letra com acento (digamos é) podem ser representados de duas maneiras - um único ponto de código U+00E9ou a letra simples seguida de um acento combinado U+0065 U+0301. A normalização comum escolherá um deles para representá-lo sempre (o ponto de código único para NFC, o formulário de combinação para NFD).

Para caracteres que podem ser representados por várias seqüências de caracteres base e marcas combinadas (digamos "s, ponto abaixo, ponto acima" versus colocar ponto acima e depois ponto abaixo ou usar um caractere base que já possua um dos pontos), o NFD irá também escolha uma delas (abaixo, primeiro, por acaso)

As decomposições de compatibilidade incluem vários caracteres que "realmente não deveriam" ser caracteres, mas são porque foram usados ​​em codificações herdadas. A normalização comum não irá unificá-las (para preservar a integridade de ida e volta - isso não é um problema para os formulários combinados porque nenhuma codificação legada [exceto um punhado de codificações vietnamitas] usou ambas), mas a normalização de compatibilidade o fará. Pense como o sinal de quilograma "kg" que aparece em algumas codificações do Leste Asiático (ou os katakana e alfabeto de meia largura / largura), ou a ligadura "fi" em MacRoman.

Veja http://unicode.org/reports/tr15/ para mais detalhes.

Random832
fonte
1
Esta é realmente a resposta correta. Se você usar apenas a normalização canônica no texto que se originou em algum conjunto de caracteres herdado, o resultado poderá ser convertido novamente nesse conjunto de caracteres sem perda. Se você usar a decomposição de compatibilidade, acabará sem caracteres de compatibilidade, mas não será mais possível converter novamente no conjunto de caracteres original sem perda.
Kevin Cathcart
13

Formulários normais (de Unicode, não de bancos de dados) lidam principalmente (exclusivamente?) Com caracteres que possuem marcas diacríticas. O Unicode fornece alguns caracteres com marcas diacríticas "incorporadas", como U + 00C0, "Latin Capital A with Grave". O mesmo caractere pode ser criado a partir de um `Latin Capital A" (U + 0041) com um "Combining Grave Accent" (U + 0300). Isso significa que, embora as duas sequências produzam o mesmo caractere resultante, um byte a byte A comparação mostrará que eles são completamente diferentes.

Normalização é uma tentativa de lidar com isso. A normalização garante (ou pelo menos tenta) que todos os caracteres sejam codificados da mesma maneira - todos usando uma marca diacrítica combinada separada, quando necessário, ou todos usando um único ponto de código sempre que possível. Do ponto de vista da comparação, não importa muito o que você escolher - praticamente qualquer string normalizada será comparada adequadamente com outra string normalizada.

Nesse caso, "compatibilidade" significa compatibilidade com o código que assume que um ponto de código é igual a um caractere. Se você tiver um código como esse, provavelmente desejará usar o formulário normal de compatibilidade. Embora eu nunca tenha visto isso diretamente, os nomes das formas normais implicam que o consórcio Unicode considera preferível usar marcas diacríticas combinadas separadas. Isso requer mais inteligência para contar os caracteres reais de uma string (assim como quebrar inteligentemente uma string), mas é mais versátil.

Se você estiver fazendo pleno uso da UTI, é provável que deseje usar a forma normal canônica. Se você está tentando escrever código por conta própria que (por exemplo) assume que um ponto de código é igual a um caractere, provavelmente você deseja o formulário normal de compatibilidade que torna isso verdadeiro o mais rápido possível.

Jerry Coffin
fonte
Portanto, esta é a parte em que as funções Grapheme entram então. O caractere não é apenas mais bytes que ASCII - mas várias seqüências podem ser um único caractere, certo? (Ao contrário das MB cordas funções.)
Xeoncross
4
Não, o 'um ponto de código é um caractere' corresponde aproximadamente a NFC (aquele com as marcas combinadas é NFD e nenhum deles é "compatibilidade") - As normalizações de compatibilidade NFKC / NFKD são uma questão diferente; compatibilidade (ou falta dela) para codificações legadas que, por exemplo, tinham caracteres separados para o grego mu e 'micro' (isso é divertido de se
mencionar
@ Random832: Opa, tudo bem. Eu deveria saber que não devo me esquecer da memória quando não trabalho com ela há um ou dois anos.
Jerry Coffin
@ Random832 Isso não é verdade. Seu "grosso modo" está muito por aí. Considere os dois grafemas, ̲̃ e ȭ̲. Existem muitas maneiras de escrever cada uma delas, das quais exatamente uma é NFC e uma NFD, mas outras também existem. Não é caso que apenas um ponto de código. NFD para o primeiro é "o\x{332}\x{303}\x{304}", e NFC é "\x{22D}\x{332}". Para o segundo NFD é "o\x{332}\x{304}\x{303}"e NFC é "\x{14D}\x{332}\x{303}". No entanto, existem muitas possibilidades não canônicas que são canonicamente equivalentes a essas. A normalização permite a comparação binária de grafemas canonicamente equivalentes.
Tchrist
5

Se duas cadeias unicode são canonicamente equivalentes, as cadeias são realmente iguais, usando apenas sequências unicode diferentes. Por exemplo, Ä pode ser representado usando o caractere Ä ou uma combinação de A e ◌̈.

Se as strings são apenas equivalentes à compatibilidade, as strings não são necessariamente as mesmas, mas podem ser as mesmas em alguns contextos. Por exemplo, ff pode ser considerado o mesmo que ff.

Portanto, se você estiver comparando cadeias, use equivalência canônica, porque a equivalência de compatibilidade não é uma equivalência real.

Mas se você deseja classificar um conjunto de cadeias, pode fazer sentido usar a equivalência de compatibilidade, pois elas são quase idênticas.

NikiC
fonte
5

Isso é realmente bastante simples. UTF-8 na verdade tem várias representações diferentes do mesmo "caractere". (Uso caracteres entre aspas, pois, em termos de bytes, são diferentes, mas praticamente são iguais). Um exemplo é dado no documento vinculado.

O caractere "Ç" pode ser representado como a sequência de bytes 0xc387. Mas também pode ser representado por um C(0x43) seguido pela sequência de bytes 0xcca7. Então, você pode dizer que 0xc387 e 0x43cca7 são o mesmo caractere. A razão que funciona é que 0xcca7 é uma marca combinada; isto é, leva o personagem antes dele (a Caqui) e o modifica.

Agora, quanto à diferença entre equivalência canônica e equivalência de compatibilidade, precisamos observar os caracteres em geral.

Existem 2 tipos de caracteres, aqueles que transmitem significado através do valor e aqueles que recebem outro caractere e o alteram. 9 é um personagem significativo. Um super script ⁹ pega esse significado e o altera pela apresentação. Então canonicamente eles têm significados diferentes, mas ainda representam o personagem base.

A equivalência canônica é onde a sequência de bytes está processando o mesmo caractere com o mesmo significado. A equivalência de compatibilidade ocorre quando a sequência de bytes está renderizando um caractere diferente com o mesmo significado básico (mesmo que possa ser alterado). O 9 e ⁹ são equivalentes à compatibilidade, pois ambos significam "9", mas não são canonicamente equivalentes, pois não têm a mesma representação.

ircmaxell
fonte
@ tchrist: Leia a resposta novamente. Eu nunca mencionei as diferentes maneiras de representar o mesmo ponto de código. Eu disse que existem várias maneiras de representar o mesmo caractere impresso (por meio de combinadores e vários caracteres). O que se aplica a UTF-8 e Unicode. Portanto, seu voto negativo e seu comentário realmente não se aplicam ao que eu disse. Na verdade, eu basicamente estava fazendo o mesmo ponto que o cartaz superior feita aqui (embora não tão bem) ...
ircmaxell
4

Se a equivalência canônica ou a compatibilidade é mais relevante para você, depende do seu aplicativo. A maneira ASCII de pensar em comparações de strings é aproximadamente mapeada para equivalência canônica, mas o Unicode representa muitas linguagens. Não acho seguro supor que o Unicode codifique todos os idiomas de uma maneira que permita tratá-los como o ASCII da Europa Ocidental.

As figuras 1 e 2 fornecem bons exemplos dos dois tipos de equivalência. Sob equivalência de compatibilidade, parece que o mesmo número na forma de sub e super script seria comparável. Mas não tenho certeza de que resolva o mesmo problema que a forma árabe cursiva ou os caracteres rotacionados.

A verdadeira verdade do processamento de texto Unicode é que você precisa pensar profundamente nos requisitos de processamento de texto do aplicativo e, em seguida, abordá-los da melhor maneira possível com as ferramentas disponíveis. Isso não responde diretamente à sua pergunta, mas uma resposta mais detalhada exigiria especialistas em idiomas para cada um dos idiomas que você espera apoiar.

ObscureRobot
fonte
1

O problema das cadeias de comparação : duas cadeias com conteúdo equivalente para os propósitos da maioria dos aplicativos podem conter seqüências de caracteres diferentes.

Consulte a equivalência canônica do Unicode : se o algoritmo de comparação for simples (ou precisar ser rápido), a equivalência Unicode não será executada. Esse problema ocorre, por exemplo, na comparação canônica XML, consulte http://www.w3.org/TR/xml-c14n

Para evitar esse problema ... Qual padrão usar? "UTF8 expandido" ou "UTF8 compacto"?
Use "ç" ou "c + ◌̧."?

O W3C e outros (por exemplo, nomes de arquivos ) sugerem o uso de "composto como canônico" (lembre-se de C das seqüências mais curtas "mais compactas") ...

O padrão é C ! em dúvida use NFC

Para interoperabilidade e opções de "convenção sobre configuração" , a recomendação é o uso de NFC , para "canonizar" cadeias externas. Para armazenar XML canônico, por exemplo, armazene-o no "FORM_C". O grupo de trabalho CSV na Web do W3C também recomenda NFC (seção 7.2).

PS: de "FORM_C" é o formulário padrão na maioria das bibliotecas. Ex. no normalizer.isnormalized () do PHP .


O termo " forma de composição " ( FORM_C) é usado para ambos, para dizer que "uma string está na forma C-canônica" (o resultado de uma transformação NFC) e para dizer que um algoritmo de transformação é usado ... Veja http: //www.macchiato.com/unicode/nfc-faq

(...) cada uma das seguintes seqüências (as duas primeiras são sequências de um caractere) representam o mesmo caractere:

  1. U + 00C5 (Å) LETRA EM CAPITAL LATINA A COM ANEL ACIMA
  2. SINAL ANGSTROM U + 212B (Å)
  3. U + 0041 (A) LETRA DE CAPITAL LATINA A + U + 030A (̊) ANEL DE COMBINAÇÃO ACIMA

Essas sequências são chamadas canonicamente equivalentes. O primeiro desses formulários é chamado NFC - para o Formulário de Normalização C, onde o C é para composição . (...) Uma função que transforma uma string S no formato NFC pode ser abreviada como toNFC(S), enquanto uma função que testa se S está na NFC é abreviada como isNFC(S).


Nota: para testar a normalização de pequenas seqüências de caracteres (referências de entidade UTF-8 ou XML pura), você pode usar este conversor online de teste / normalização .

Peter Krauss
fonte
Estou confuso. Eu fui a esta página de testadores on-line e entrei lá: "TÖST MÉ pleasé". e tente todas as quatro normalizações dadas - nenhuma altera meu texto de forma alguma, exceto que altera os códigos usados ​​para apresentar esses caracteres. Estou pensando erroneamente que "normalização" significa "remover todos os sinais diacríticos e similares", e na verdade significa - basta alterar a codificação utf abaixo?
Userfuser
Olá @userfuser talvez você precise de uma posição, sobre aplicação: é comparar ou padronizar seu texto? Meu post aqui é apenas sobre "padronizar" aplicativos. PS: quando todo o mundo usa o padrão, o problema de comparação desaparece.
Peter Krauss