In cosa consiste UTF-8 normalizzato?


129

Il progetto ICU (che ora ha anche una libreria PHP ) contiene le classi necessarie per aiutare a normalizzare le stringhe UTF-8 per facilitare il confronto dei valori durante la ricerca.

Tuttavia, sto cercando di capire cosa significhi per le applicazioni. Ad esempio, in quali casi desidero "Equivalenza canonica" anziché "Equivalenza di compatibilità" o viceversa?


230
Chi ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t orrori si trovano nel buio cuore di Unicode ͞
ObscureRobot

@ObscureRobot Voglio davvero sapere se quei simboli extra possono avere stati o no
eonil

1
@Eonil - Non sono sicuro di cosa significhi stato nel contesto di Unicode.
ObscureRobot,

@ObscureRobot Per esempio, un certo punto codice come questo: (begin curved line) (char1) (char2) … (charN) (end curved line)invece di questo: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). In altre parole, unità minima che può essere renderizzata?
eonil,

2
Sembra una buona domanda da sola.
ObscureRobot

Risposte:


181

Tutto ciò che non avresti mai voluto sapere sulla normalizzazione Unicode

Normalizzazione canonica

Unicode include diversi modi per codificare alcuni caratteri, in particolare i caratteri accentati. La normalizzazione canonica modifica i punti di codice in una forma di codifica canonica. I punti di codice risultanti dovrebbero apparire identici a quelli originali, escludendo eventuali bug nei caratteri o nel motore di rendering.

Quando usare

Poiché i risultati appaiono identici, è sempre sicuro applicare la normalizzazione canonica a una stringa prima di memorizzarla o visualizzarla, purché sia ​​possibile tollerare che il risultato non sia bit per bit identico all'input.

La normalizzazione canonica si presenta in 2 forme: NFD e NFC. I due sono equivalenti nel senso che si può convertire senza perdita tra queste due forme. Il confronto di due stringhe in NFC darà sempre lo stesso risultato del confronto in NFD.

NFD

NFD ha i personaggi completamente espansi. Questo è il modulo di normalizzazione più veloce da calcolare, ma i risultati in più punti di codice (cioè usano più spazio).

Se vuoi solo confrontare due stringhe che non sono già normalizzate, questa è la forma di normalizzazione preferita a meno che tu non sappia che hai bisogno di normalizzazione di compatibilità.

NFC

NFC ricombina i punti di codice quando possibile dopo aver eseguito l'algoritmo NFD. Questo richiede un po 'più di tempo, ma si traduce in stringhe più brevi.

Normalizzazione di compatibilità

Unicode include anche molti personaggi che in realtà non appartengono, ma sono stati utilizzati in set di caratteri legacy. Unicode li ha aggiunti per consentire l'elaborazione del testo in quei set di caratteri come Unicode e quindi la riconversione senza perdita.

La normalizzazione della compatibilità li converte nella corrispondente sequenza di caratteri "reali" e esegue anche la normalizzazione canonica. I risultati della normalizzazione della compatibilità potrebbero non apparire identici agli originali.

I caratteri che includono informazioni sulla formattazione vengono sostituiti con quelli che non lo fanno. Ad esempio, il personaggio viene convertito in 9. Altri non comportano differenze di formattazione. Ad esempio, il carattere numerico romano viene convertito in lettere regolari IX.

Ovviamente, una volta eseguita questa trasformazione, non è più possibile riconvertire senza perdita di dati al set di caratteri originale.

Quando usare

Il consorzio Unicode suggerisce di pensare alla normalizzazione della compatibilità come a una ToUpperCasetrasformazione. È qualcosa che può essere utile in alcune circostanze, ma non dovresti semplicemente applicarlo volenti o nolenti.

Un caso d'uso eccellente sarebbe un motore di ricerca poiché probabilmente vorrai che una ricerca 9corrispondesse .

Una cosa che probabilmente non dovresti fare è visualizzare il risultato dell'applicazione della normalizzazione della compatibilità all'utente.

NFKC / NFKD

Il modulo di normalizzazione della compatibilità è disponibile in due forme NFKD e NFKC. Hanno la stessa relazione tra NFD e C.

Qualsiasi stringa in NFKC è intrinsecamente anche in NFC, e lo stesso vale per NFKD e NFD. Quindi NFKD(x)=NFD(NFKC(x)), e NFKC(x)=NFC(NFKD(x)), ecc.

Conclusione

In caso di dubbi, seguire la normalizzazione canonica. Scegli NFC o NFD in base allo scambio spazio / velocità applicabile o in base a ciò che è richiesto da qualcosa con cui interagisci.


42
Un rapido riferimento per ricordare che cosa significano le abbreviazioni: NF = forma normalizzata D = decomponi (decomprimi) , C = componi (comprimi) K = compatibilità (da quando è stata presa la "C").
Mike Spross,

12
Si desidera sempre NFD tutte le stringhe sull'input come prima cosa e NFC tutte le stringhe emesse come l'ultima cosa. Questo è ben noto.
tchrist

3
@tchrist: Questo è generalmente un buon consiglio, tranne nei rari casi in cui si desidera che l'output sia byte per byte identico all'input quando non vengono apportate modifiche. Ci sono altri casi in cui si desidera NFC in memoria o NFD su disco, ma sono l'eccezione piuttosto che la regola.
Kevin Cathcart,

@Kevin: Sì, NFD in entrata e NFC in uscita distruggeranno i singoli. Non sono sicuro che a qualcuno importi di quelli, ma forse.
tchrist,

2
Potresti pensare che, ma dall'allegato: "Per trasformare una stringa Unicode in un dato Modulo di normalizzazione Unicode, il primo passo è decomporre completamente la stringa". Pertanto, anche se eseguiamo NFC, Q-Caron diventerebbe prima Q + Caron e non potrebbe ricomporre, poiché le regole di stabilità vietano l'aggiunta della nuova mappatura della composizione. NFC è effettivamente definito come NFC(x)=Recompose(NFD(x)).
Kevin Cathcart,

40

Alcuni caratteri, ad esempio una lettera con un accento (diciamo, é) possono essere rappresentati in due modi: un singolo punto di codice U+00E9o la lettera semplice seguita da un accento combinato U+0065 U+0301. La normalizzazione ordinaria sceglierà uno di questi per rappresentarlo sempre (il punto di codice singolo per NFC, il modulo di combinazione per NFD).

Per i personaggi che potrebbero essere rappresentati da più sequenze di caratteri di base e segni combinati (diciamo "s, punto sotto, punto sopra" vs mettendo punto sopra quindi punto sotto o usando un carattere base che ha già uno dei punti), NFD scegli anche uno di questi (di seguito va prima, come succede)

Le decomposizioni di compatibilità includono un numero di caratteri che "non dovrebbero realmente" essere personaggi, ma perché sono stati utilizzati nelle codifiche legacy. La normalizzazione ordinaria non li unificherà (per preservare l'integrità del round trip - questo non è un problema per le forme combinate perché nessuna codifica legacy [tranne una manciata di codifiche vietnamite] ha usato entrambi), ma la normalizzazione della compatibilità lo farà. Pensa come il segno del chilogrammo "kg" che appare in alcune codifiche dell'Asia orientale (o il katakana e l'alfabeto a mezza larghezza / fullwidth) o la legatura "fi" in MacRoman.

Vedi http://unicode.org/reports/tr15/ per maggiori dettagli.


1
Questa è davvero la risposta corretta. Se si utilizza solo la normalizzazione canonica sul testo originato in un set di caratteri legacy, il risultato può essere riconvertito in quel set di caratteri senza perdita. Se si utilizza la decomposizione della compatibilità, si finisce senza caratteri di compatibilità, ma non è più possibile riconvertire al set di caratteri originale senza perdita.
Kevin Cathcart,

13

Le forme normali (di Unicode, non i database) riguardano principalmente (esclusivamente?) I caratteri che hanno segni diacritici. Unicode fornisce ad alcuni personaggi segni diacritici "incorporati", come U + 00C0, "Capitale latina A con tomba". Lo stesso carattere può essere creato da una "Capitale latina A" (U + 0041) con un "Combining Grave Accent" (U + 0300). Ciò significa che anche se le due sequenze producono lo stesso carattere risultante, un byte per byte il confronto li mostrerà come completamente diversi.

La normalizzazione è un tentativo di affrontarlo. La normalizzazione assicura (o almeno prova a) che tutti i caratteri siano codificati allo stesso modo - o tutti usano un segno diacritico combinato separato dove necessario, o tutti usano un singolo punto di codice dove possibile. Da un punto di vista del confronto, non importa molto quale scegliate: praticamente qualsiasi stringa normalizzata verrà confrontata correttamente con un'altra stringa normalizzata.

In questo caso, "compatibilità" significa compatibilità con il codice che presuppone che un punto di codice sia uguale a un carattere. Se si dispone di un codice simile, probabilmente si desidera utilizzare il modulo normale di compatibilità. Anche se non l'ho mai visto dichiarare direttamente, i nomi delle forme normali implicano che il consorzio Unicode considera preferibile usare segni diacritici combinati separati. Ciò richiede più intelligenza per contare i caratteri effettivi in ​​una stringa (così come cose come spezzare una stringa in modo intelligente), ma è più versatile.

Se stai sfruttando appieno l'ICU, è probabile che tu voglia utilizzare la forma normale canonica. Se stai provando a scrivere codice da solo che (ad esempio) assume che un punto di codice sia uguale a un carattere, probabilmente vuoi la forma normale di compatibilità che lo rende vero il più spesso possibile.


Quindi questa è la parte in cui le funzioni Grapheme entrano allora. Non solo il carattere è più byte di ASCII, ma più sequenze possono essere un singolo carattere giusto? (A differenza delle funzioni di stringa MB .)
Xeoncross,

4
No, "un punto di codice è un carattere" corrisponde approssimativamente a NFC (quello con i segni di combinazione è NFD e nessuno dei due è "compatibilità") - Le normalizzazioni di compatibilità NFKC / NFKD sono un problema diverso; compatibilità (o mancanza di ciò) per codifiche legacy che per esempio avevano caratteri separati per il greco mu e 'micro' (è divertente da evidenziare perché la versione di "compatibilità" è quella che si trova nel blocco latino 1)
Casuale 832

@ Random832: Oops, giusto. Dovrei sapere meglio che passare dalla memoria quando non ci ho lavorato negli ultimi due anni.
Jerry Coffin,

@ Random832 Questo non è vero. Il tuo "approssimativamente" è troppo là fuori. Considera i due grafemi, ō̲̃ e ȭ̲. Esistono molti modi per scrivere ognuno di questi, di cui esattamente uno è NFC e uno NFD, ma ne esistono anche altri. Non è un caso che solo un punto di codice. NFD per il primo è "o\x{332}\x{303}\x{304}", e NFC lo è "\x{22D}\x{332}". Per il secondo NFD è "o\x{332}\x{304}\x{303}"e NFC è "\x{14D}\x{332}\x{303}". Tuttavia, esistono molte possibilità non canoniche che sono canonicamente equivalenti a queste. La normalizzazione consente il confronto binario di grafemi equivalenti canonicamente.
tchrist

5

Se due stringhe unicode sono canonicamente equivalenti, le stringhe sono davvero le stesse, usando solo sequenze unicode diverse. Ad esempio Ä può essere rappresentato utilizzando il carattere Ä o una combinazione di A e ◌̈.

Se le stringhe sono solo equivalenti alla compatibilità, le stringhe non sono necessariamente le stesse, ma possono essere le stesse in alcuni contesti. Ad esempio ff potrebbe essere considerato uguale a ff.

Pertanto, se si confrontano le stringhe, è necessario utilizzare l'equivalenza canonica, poiché l'equivalenza di compatibilità non è reale equivalenza.

Ma se vuoi ordinare una serie di stringhe, potrebbe avere senso usare l'equivalenza di compatibilità poiché sono quasi identiche.


5

Questo è in realtà abbastanza semplice. UTF-8 in realtà ha diverse rappresentazioni dello stesso "carattere". (Uso il carattere tra virgolette poiché i byte sono diversi, ma praticamente sono uguali). Un esempio è riportato nel documento collegato.

Il carattere "Ç" può essere rappresentato come sequenza di byte 0xc387. Ma può anche essere rappresentato da un C(0x43) seguito dalla sequenza di byte 0xcca7. Quindi puoi dire che 0xc387 e 0x43cca7 sono lo stesso personaggio. Il motivo che funziona è che 0xcca7 è un segno combinato; vale a dire che prende il personaggio prima di esso (un Cqui) e lo modifica.

Ora, per quanto riguarda la differenza tra equivalenza canonica e equivalenza di compatibilità, dobbiamo esaminare i caratteri in generale.

Esistono 2 tipi di caratteri, quelli che trasmettono significato attraverso il valore e quelli che prendono un altro personaggio e lo modificano. 9 è un personaggio significativo. Un super-script ⁹ prende quel significato e lo altera per presentazione. Quindi canonicamente hanno significati diversi, ma rappresentano ancora il personaggio base.

L'equivalenza canonica è dove la sequenza di byte rende lo stesso carattere con lo stesso significato. L'equivalenza della compatibilità è quando la sequenza di byte sta visualizzando un carattere diverso con lo stesso significato di base (anche se può essere modificato). 9 e ⁹ sono equivalenti alla compatibilità poiché entrambi significano "9", ma non sono canonicamente equivalenti poiché non hanno la stessa rappresentazione.


@tchrist: leggi di nuovo la risposta. Non ho mai nemmeno menzionato i diversi modi di rappresentare lo stesso punto di codice. Ho detto che ci sono diversi modi per rappresentare lo stesso personaggio stampato (tramite combinatori e più personaggi). Ciò vale sia per UTF-8 che per Unicode. Quindi il tuo voto negativo e il tuo commento non si applicano affatto a quello che ho detto. In realtà, in sostanza, stavo facendo lo stesso punto che il poster in alto ha fatto qui (anche se non altrettanto) ...
Ircmaxell,

4

Se l'equivalenza canonica o l'equivalenza di compatibilità è più rilevante per te dipende dalla tua applicazione. Il modo di pensare ASCII ai confronti delle stringhe è approssimativamente associato all'equivalenza canonica, ma Unicode rappresenta molte lingue. Non credo sia sicuro supporre che Unicode codifichi tutte le lingue in un modo che ti consenta di trattarle proprio come ASCII dell'Europa occidentale.

Le figure 1 e 2 forniscono buoni esempi dei due tipi di equivalenza. Sotto l'equivalenza della compatibilità, sembra che lo stesso numero in forma di sotto-e super-script comparerebbe uguale. Ma non sono sicuro che risolva lo stesso problema della forma araba corsiva o dei caratteri ruotati.

La vera verità dell'elaborazione del testo Unicode è che devi riflettere profondamente sui requisiti di elaborazione del testo della tua applicazione e quindi affrontarli nel miglior modo possibile con gli strumenti disponibili. Ciò non affronta direttamente la tua domanda, ma una risposta più dettagliata richiederebbe esperti linguistici per ciascuna delle lingue che prevedi di supportare.


1

Il problema delle stringhe di confronto : due stringhe con contenuto equivalente ai fini della maggior parte delle applicazioni possono contenere sequenze di caratteri diverse.

Vedi l'equivalenza canonica di Unicode : se l'algoritmo di confronto è semplice (o deve essere veloce), l' equivalenza Unicode non viene eseguita. Questo problema si verifica, ad esempio, nel confronto canonico XML, vedere http://www.w3.org/TR/xml-c14n

Per evitare questo problema ... Quale standard usare? "UTF8 espanso" o "UTF8 compatto"?
Usa "ç" o "c + ◌̧."?

W3C e altri (es. Nomi di file ) suggeriscono di usare il "composto come canonico" (tenere presente C delle stringhe più "compatte") ... Quindi,

Lo standard è C ! in dubbio usa NFC

Per l'interoperabilità e per le scelte di "convenzione sulla configurazione" , la raccomandazione è l'uso di NFC per "canonizzare" le stringhe esterne. Per memorizzare XML canonico, ad esempio, memorizzarlo in "FORM_C". Anche il CSV del W3C sul Web Working Group raccomanda NFC (sezione 7.2).

PS: de "FORM_C" è il modulo predefinito nella maggior parte delle librerie. Ex. nel normalizer.isnormalized di PHP () .


Il termine " forma di composizione " ( FORM_C) viene utilizzato per entrambi, per dire che "una stringa è nella forma canonica C" (il risultato di una trasformazione NFC) e per dire che viene utilizzato un algoritmo di trasformazione ... Vedi http: //www.macchiato.com/unicode/nfc-faq

(...) ognuna delle seguenti sequenze (le prime due sono sequenze a carattere singolo) rappresentano lo stesso carattere:

  1. U + 00C5 (Å) LETTERA MAIUSCOLA LETTERA A CON ANELLO SOPRA
  2. U + 212B (Å) SEGNO ANGSTROM
  3. U + 0041 (A) LETTERA MAIUSCOLA LATINA A + U + 030A (̊) ANELLO COMBINANTE SOPRA

Queste sequenze sono chiamate canonicamente equivalenti. La prima di queste forme è chiamata NFC - per la forma di normalizzazione C, dove la C è per la composizione . (...) Una funzione che trasforma una stringa S in forma NFC può essere abbreviata come toNFC(S), mentre una che verifica se S è in NFC è abbreviata come isNFC(S).


Nota: per testare la normalizzazione di stringhe piccole (riferimenti UTF-8 o entità XML puri), è possibile utilizzare questo convertitore online di test / normalizzazione .


Non ho capito bene. Sono andato a questa pagina di tester online e vi ho inserito: "TÖST MÉ pleasé". e prova tutte e 4 le normali normalizzazioni - nessuna modifica il mio testo in alcun modo, beh, tranne per il fatto che cambia i codici usati per presentare quei caratteri. Sto erroneamente pensando che "normalizzazione" significhi "rimuovere tutti i segni diacritici e simili", e in realtà significa: basta cambiare il codice utf sottostante?
userfuser

Ciao @utentefuser forse hai bisogno di una posizione, sull'applicazione: è di confrontare o standardizzare il tuo testo? Il mio post qui riguarda solo "standardizzare" le applicazioni. PS: quando tutto il mondo usa lo standard, il problema del confronto svanisce.
Peter Krauss,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.