Normalizzatore Unicode

Unicode ha quattro forme di normalizzazione e non sono intercambiabili. NFC e NFD riorganizzano lo stesso carattere tra un unico punto di codice e una base più un segno combinante; NFKC e NFKD vanno oltre e riscrivono caratteri che si somigliano soltanto — la legatura fi, la A a larghezza piena A, il cerchiato ①, il numero romano Ⅻ. Questa pagina fa passare lo stesso testo attraverso tutte e quattro, stampa una riga per carattere che dice che cosa è cambiato e perché, conta i grafemi rispetto ai punti di codice e ai byte UTF-8, e confronta due stringhe nella forma che sceglie. La seconda parte riguarda i caratteri che non compaiono mai: spazi di larghezza zero, giunzioni, controlli bidirezionali, selettori di variazione, caratteri di controllo C0 e C1, punti di codice di uso privato e metà di surrogati non validi, ciascuno con la sua posizione e il suo nome, e l'opzione per eliminarli da una copia del testo.
Tutta l'elaborazione avviene localmente nel suo browser — i suoi dati non vengono mai caricati su alcun server.

Non sa che cosa sia la normalizzazione? La normalizzazione Unicode spiegata in parole semplici — perché é può essere uno o due caratteri, e perché questo rompe un login.
Forma da riportare e con cui confrontare:
Confronta le due caselle dopo:
NFC (ancora niente)
NFD (ancora niente)
NFKC (ancora niente)
NFKD (ancora niente)
Nessun confronto ancora. Incolli le due stringhe e prema Confronta le due stringhe.
prima stringa, normalizzata (ancora niente)
seconda stringa, normalizzata (ancora niente)
Nessuna scansione ancora. Prema Cerca caratteri invisibili e pericolosi, poi Elimina se vuole il testo senza quei caratteri.
testo senza le classi selezionate (ancora niente)
Ctrl+Invio nella prima casella esegue la modalità attiva.
Scelga una modalità e prema il suo pulsante di azione. Normalizza scrive tutte e quattro le forme del testo e una riga per carattere; Confronta dice se due stringhe sono uguali dopo la normalizzazione; Scansione elenca i caratteri che non si vedono e può eliminarli. Il pulsante Autoverifica riesegue ogni caso documentato nel file dati di questa pagina, ogni carattere che le tabelle degli invisibili e dei controlli nominano, e riporta qui il conteggio.
Ogni carattere, una riga ciascuno:che cos'è il carattere, che cosa fanno le quattro forme e perché. Una riga ombreggiata è un carattere che almeno una forma modifica; la colonna NFD mostra in che cosa si scompone il carattere, quando si scompone.
Quanti caratteri sono?Lo stesso testo contato in quattro modi: cluster di grafemi (ciò che conta un lettore), punti di codice (ciò che Unicode assegna), unità UTF-16 (ciò che JavaScript indicizza) e byte UTF-8 (ciò che viaggia). La normalizzazione sposta i numeri tra la seconda e la terza colonna senza cambiare la prima.
Che cosa è stato trovato, nell'ordine in cui compare:la posizione conta i punti di codice, la classe è quella a cui il carattere appartiene, e la nota dice che cosa quel carattere fa a chiunque legga il testo dopo.
Le classi che questa pagina considera sospette:la tabella è stampata dallo scanner stesso, così le parole di questa pagina e il comportamento dei pulsanti non possono divergere. La gravità riguarda l'effetto su un confronto, non quanto è probabile che il carattere sia innocuo.
I caratteri di uso privato sono elencati come sospetti per un motivo diverso dagli altri: vengono visualizzati, semplicemente non hanno un significato condiviso, quindi lo stesso punto di codice è un'immagine diversa in ogni font che lo definisce.
Quanto grande un input accetta questa pagina, e perché:la normalizzazione ricostruisce l'intera stringa, e questa pagina ne esegue diverse insieme a una scansione e a un conteggio dei grafemi, quindi il costo cresce in linea retta con la lunghezza dell'input. I limiti qui sotto esistono perché la tabella per carattere viene disegnata nel documento, e una tabella con un milione di righe è ciò che davvero blocca un browser.
Che cos'è la normalizzazione Unicode?

Unicode offre più di un modo di scrivere alcuni caratteri. La lettera é può essere un unico punto di codice, U+00E9, oppure una normale e seguita da un accento acuto combinante, U+0065 U+0301. I due appaiono uguali sullo schermo, si stampano allo stesso modo e, come stringhe, sono diversi: hanno lunghezze diverse, byte UTF-8 diversi, e 'é' === 'e\u0301' è false.

Non è una curiosità, è una classe di bug. Ottenere l'una o l'altra forma dipende dal layout della tastiera, dal sistema operativo, dal metodo di input e dal fatto che il testo sia mai stato copiato attraverso un programma di videoscrittura. Un controllo della password, un indice univoco di database, una lista di blocco o una chiave di cache che confronta le stringhe byte per byte tratteranno le due grafie come due valori diversi — così un utente può impostare una password su un dispositivo e non riuscire ad accedere da un altro, e la stessa parola può comparire due volte in un elenco di nomi «già presi».

La versione in 30 secondi: NFC e NFD sono lo stesso testo scritto in due modi — NFC compone (e + accento diventa un carattere), NFD scompone (un carattere diventa e + accento). Sono pensate per non perdere nulla e sono le due forme da usare per confronto e memorizzazione. NFKC e NFKD fanno tutto ciò che fanno le prime due e poi sostituiscono anche i caratteri di compatibilità: la legatura fi diventa due lettere, ½ diventa 1⁄2, il numero romano ⅲ diventa XII, una A a larghezza piena diventa una normale A. È esattamente ciò che vuole un indice di ricerca o un filtro per parole chiave, ed è esattamente ciò che non si deve mai applicare a una password.
Perché quattro forme e non una

La divisione esiste perché servivano due obiettivi diversi. Comporre e scomporre il testo è un cambiamento canonico: il risultato significa la stessa cosa e, per le prime due forme, non si perde nulla. Eliminare legature, apici, lettere cerchiate e forme a larghezza piena è un cambiamento di compatibilità: si fa per comodità di confronto, e distrugge la distinzione tra due stringhe che Unicode considera diverse. Le quattro forme sono tutte le combinazioni delle due decisioni.

La tabella qui sopra è l'elenco dei casi del file dati di questa pagina, verificato con String.prototype.normalize della piattaforma, e l'Autoverifica ricontrolla ogni sua riga davanti a lei. Legga insieme le prime due righe: sono lo stesso testo scritto in due modi, e dopo la normalizzazione sono la stessa stringa.

Esempi svolti

Queste sono le stringhe che il pulsante Esempio percorre, normalizzate dal vivo dalla pagina invece di essere digitate come risposte. Ciascuna è stata scelta perché una forma le fa qualcosa di visibile.

Caratteri di compatibilità: che cosa costa NFKC

NFKC e NFKD sono con perdita, e la perdita è voluta. Rispondono alla domanda «è la stessa parola?», non alla domanda «è la stessa stringa?». Ne seguono tre conseguenze, tutte e tre facili da sbagliare in un codice di produzione:

  • Non memorizzi mai una password, un token o una firma in una forma di compatibilità. Due input diversi collasserebbero nello stesso valore memorizzato, e un controllo di lunghezza o di caratteri diventerebbe più debole. Normalizzi le password con NFC (o non le normalizzi affatto) e le confronti byte per byte dopo aver fatto esattamente la stessa cosa su entrambi i lati.
  • Una piegatura di compatibilità può trasformare un carattere in più caratteri. La legatura fi è un punto di codice che diventa due lettere, quindi un campo validato per lunghezza prima della normalizzazione può risultare più lungo dopo. Prima normalizzi, poi valide, poi memorizzi.
  • NFKC non rimuove tutto ciò che è invisibile. Un selettore di variazione sopravvive a NFKC e NFKD, e così una giunzione di larghezza zero. Per questo la pagina ha una modalità di scansione: la normalizzazione risponde a una domanda sulla forma e l'identità di un carattere, lo scanner risponde a una domanda sul fatto che qualcosa ci sia o no.
Grafemi, punti di codice e byte

La normalizzazione cambia il secondo di questi conteggi e di solito lascia in pace il primo. I tre numeri sono tutti «la lunghezza del testo» e sono tutti diversi, ed è per questo che un campo di modulo con un limite di 20 caratteri può appartenere a una stringa lunga 20 punti di codice, 30 unità UTF-16 e 47 byte.

Il numero nella colonna dei grafemi è ciò che conta una persona. La differenza rispetto al conteggio dei punti di codice è esattamente ciò di cui si occupa lo scanner: un nome fatto di 12 punti di codice può apparire come 12 lettere, come 3 lettere con 9 segni invisibili tra l'una e l'altra, oppure come una sola emoji.

I caratteri che non si vedono

Uno spazio di larghezza zero non occupa spazio, non cambia alcun glifo, ed è un carattere reale con un punto di codice reale. Due identificatori che sullo schermo sembrano identici possono differire per uno di questi caratteri, ed è così che un nome in un elenco può essere un impostore che si ordina, si cerca e si autentica in modo diverso dal nome accanto. Lo stesso vale per i controlli bidirezionali, che possono far visualizzare una stringa in un ordine diverso da quello in cui è stata scritta — un trucco usato per camuffare i nomi dei file.

Lo scanner della terza modalità li elenca tutti con la loro posizione: caratteri di larghezza zero e giunzione, controlli bidirezionali, selettori di variazione, spazi insoliti, caratteri di controllo C0 e C1, punti di codice di uso privato, e un surrogato che non è metà di una coppia valida, il che non è affatto un carattere. La tabella delle classi più in alto spiega che cosa fa ciascuna classe, e il pulsante di eliminazione rimuove una copia di quei caratteri senza toccare il suo input.

Gli errori per cui questa pagina è costruita
  • Confrontare due stringhe senza normalizzarle. 'é' === 'e\u0301' è falso. Normalizzi entrambi i lati con la stessa forma prima di confrontarli, e usi la stessa forma nell'indice con cui confronta.
  • Normalizzare da un solo lato. Una ricerca che normalizza la query ma non il valore memorizzato non trova nulla, e sembra un problema di dati invece che di normalizzazione.
  • Usare una forma di compatibilità per un modulo e definirlo sicuro con NFC. Applicare NFKC a una password riduce lo spazio delle password possibili e unisce input che dovevano restare distinti.
  • Credere che una forma di normalizzazione risolva un inganno. Non elimina una giunzione di larghezza zero, un selettore di variazione o un omoglifo — una а cirillica e una a latina sono punti di codice diversi che NFC lascia intatti.
  • Fidarsi di un conteggio dei caratteri. Il conteggio che vede l'utente, quello che impone una colonna del database e quello che impone un limite di byte sono tre numeri diversi, e le righe qui sopra mostrano quanto possono distare.
  • Credere che otterrà NFC. Il testo che arriva da un metodo di input, da un file o da un altro sistema è nella forma che quel sistema ha prodotto. Questa pagina parte da NFC perché è la forma che il web dovrebbe usare, non perché sia quella del suo input.
Risposte rapide

«Perché due stringhe non sono uguali se sembrano uguali?»

Perché una delle due è scritta con un carattere composto e l'altra con una lettera di base più un segno combinante, oppure una delle due contiene un carattere che non disegna nulla. Passi alla modalità Confronta e metta una stringa in ciascuna casella: la pagina riporta la prima posizione in cui le stringhe normalizzate differiscono e che cosa c'è in quella posizione in ciascuna.

«Quale forma devo usare in un'applicazione?»

NFC per tutto ciò che una persona leggerà, mostrerà, memorizzerà e cercherà, che è la forma raccomandata dal W3C per il web. NFD è la risposta giusta quando il codice dietro di lei vuole lettere di base e segni separati, il che è comune nell'elaborazione del testo e in alcuni file system di macOS. Usi NFKC o NFKD solo dove sta confrontando invece di memorizzare: indici di ricerca, filtri per parole chiave, rilevamento dei duplicati.

«Una stringa già normalizzata resta normalizzata dopo che vi concateno qualcosa?»

Non necessariamente. La concatenazione non conserva la normalizzazione: unire due stringhe NFC può produrre una sequenza che NFC avrebbe scritto diversamente, perché un segno combinante all'inizio della seconda stringa può attaccarsi al carattere alla fine della prima. Normalizzi dopo l'assemblaggio, non solo prima.

«La normalizzazione elimina i caratteri invisibili?»

No, con una sola eccezione parziale: NFKC e NFKD sostituiscono lo spazio senza interruzione con un normale spazio, che è un risultato visibile di una piegatura di compatibilità. Tutto il resto — spazi di larghezza zero, giunzioni, controlli bidirezionali, selettori di variazione — passa attraverso tutte e quattro le forme senza essere toccato. È a questo che serve la terza modalità.

«Perché il conteggio dei byte cambia quando non cambia il conteggio dei caratteri?»

Perché le due forme sono costruite con punti di codice diversi che in UTF-8 costano un numero diverso di byte. Una é precomposta sono due byte; la coppia scomposta ne sono tre. La tabella dei conteggi qui sopra mostra tutti e quattro i numeri affiancati per l'input e per ciascuna forma, così la differenza è visibile invece che descritta.

«Come faccio a sapere che i risultati di questa pagina sono corretti?»

Prema Autoverifica. Normalizza ogni caso nel file dati di questa pagina, riverifica le quattro forme l'una contro l'altra per le relazioni documentate (NFC è idempotente, NFD di una stringa NFC è uguale alla NFD dell'originale, e così via), fa passare allo scanner ogni carattere citato nelle tabelle degli invisibili e dei controlli, e confronta i casi di grafemi con Intl.Segmenter.

Standard e crediti

Le quattro forme e le loro definizioni provengono dal Unicode Standard, Annex 15 (Unicode Normalization Forms). I punti di codice usati come esempi di decomposizione sono quelli assegnati dallo standard; questa pagina non implementa da sé le tabelle di decomposizione — chiama String.prototype.normalize del browser, cioè la stessa implementazione che la piattaforma usa per l'inserimento di testo, l'ordinamento e il confronto, quindi ciò che qui viene riportato è ciò che il resto del sistema fa con la stessa stringa. I cluster di grafemi provengono da Intl.Segmenter dove il browser lo offre, e da un'approssimazione documentata dove non lo offre.

Il testo della pagina è stato scritto per questo sito. Ogni esempio svolto è stato prodotto dal codice della pagina e poi verificato con l'elenco dei casi registrato nel file dati, a sua volta verificato con i dati NormalizationTest di Unicode per gli stessi caratteri. Le tabelle dei caratteri invisibili e pericolosi sono un sottoinsieme curato, scritto per avvertimenti leggibili da persone, non il profilo di sicurezza completo di Unicode (UTS #39), e la pagina lo dice invece di lasciar intendere una completezza che non ha.