Normalizator Unicode

Unicode are patru forme de normalizare și nu sunt interschimbabile. NFC și NFD rearanjează același caracter între un singur punct de cod și o bază plus un semn combinat; NFKC și NFKD merg mai departe și rescriu caractere care doar seamănă între ele — ligatura fi, litera fullwidth A, cercul ①, cifra romană Ⅻ. Pagina trece același text prin toate patru, afișează câte un rând pentru fiecare caracter care spune ce s-a schimbat și de ce, numără grafemele față de punctele de cod și octeții UTF-8 și compară două șiruri sub forma aleasă. A doua jumătate se referă la caracterele care nu apar deloc: spații de lățime zero, conectori, controale bidirecționale, selectori de variantă, caractere de control C0 și C1, puncte de cod de uz privat și jumătăți de surogate rupte, fiecare cu poziția și numele său, plus opțiunea de a le elimina dintr-o copie a textului.
Toate prelucrările au loc local, în browser — datele nu se încarcă pe niciun server.

Ce este normalizarea? Normalizarea Unicode explicată în termeni simpli — de ce é poate fi un caracter sau două și de ce asta strică o autentificare.
Forma de raportat și cu care se compară:
Se compară cele două casete după:
NFC (nimic încă)
NFD (nimic încă)
NFKC (nimic încă)
NFKD (nimic încă)
Nimic comparat încă. Se lipesc cele două șiruri și se apasă Compară cele două șiruri.
primul șir, normalizat (nimic încă)
al doilea șir, normalizat (nimic încă)
Nimic scanat încă. Se apasă Scanează caracterele invizibile și periculoase, apoi Elimină dacă se dorește textul fără acele caractere.
textul cu clasele selectate eliminate (nimic încă)
Ctrl+Enter în prima casetă rulează modul activ.
Se alege un mod și se face clic pe butonul de acțiune. Normalizează scrie toate cele patru forme ale textului și câte un rând pentru fiecare caracter; Compară spune dacă două șiruri sunt identice după normalizare; Scanează listează caracterele care nu se văd și le poate elimina. Butonul Autotest reia fiecare caz documentat din fișierul de date al paginii, fiecare caracter din tabelele invizibile și de control, și raportează aici numărul.
Fiecare caracter, câte un rând:ce este caracterul, ce fac cele patru forme cu el și de ce. Un rând umbrit este un caracter pe care cel puțin o formă îl modifică; coloana NFD arată în ce se descompune caracterul, atunci când se descompune.
Câte caractere sunt acestea?Același text numărat în patru moduri: clustere de grafeme (ce numără un cititor), puncte de cod (ce atribuie Unicode), unități UTF-16 (ce indexează JavaScript) și octeți UTF-8 (ce circulă). Normalizarea mută numerele între a doua și a treia coloană fără să schimbe prima.
Ce s-a găsit, în ordinea în care apare:poziția numără punctele de cod, clasa este cea căreia îi aparține caracterul, iar nota spune ce face acel caracter cu orice citește textul următor.
Clasele pe care această pagină le tratează ca suspecte:tabelul este tipărit direct din scaner, deci cuvintele de pe pagină și comportamentul butoanelor nu pot ieși din sincron. Gravitatea privește efectul asupra unei comparații, nu cât de probabil este ca acel caracter să fie nevinovat.
Caracterele de uz privat sunt listate ca suspecte dintr-un alt motiv decât restul: ele se afișează, doar că nu au un sens convenit, deci același punct de cod este o imagine diferită în fiecare font care îl definește.
Cât de mare intrare acceptă această pagină și de ce:normalizarea reconstruiește întregul șir, iar această pagină face mai multe astfel de operații plus o scanare și o numărare a grafemelor, deci costul crește liniar cu lungimea intrării. Limitele de mai jos există pentru că tabelul pe caractere este desenat în document, iar un tabel cu un milion de rânduri este exact ceea ce oprește de fapt un browser.
Ce este normalizarea Unicode?

Unicode oferă mai mult de un mod de a scrie unele caractere. Litera é poate fi un singur punct de cod, U+00E9, sau un simplu e urmat de un accent acut combinat, U+0065 U+0301. Cele două arată la fel pe ecran, se tipăresc la fel și, ca șiruri, sunt diferite: au lungimi diferite, octeți UTF-8 diferiți, iar 'é' === 'é' este false.

Nu este o curiozitate, ci o clasă de erori. Obținerea uneia sau a celeilalte forme depinde de aspectul tastaturii, de sistemul de operare, de metoda de introducere și de faptul că textul a fost sau nu copiat vreodată printr-un procesor de text. O verificare a parolei, un index unic în baza de date, o listă de blocare sau o cheie de cache care compară șiruri octet cu octet vor trata cele două ortografii ca două valori diferite — astfel încât un utilizator poate seta o parolă pe un dispozitiv și să nu se poată conecta de pe altul, iar același cuvânt poate apărea de două ori într-o listă de nume „deja luate”.

Versiunea de 30 de secunde: NFC și NFD sunt același text scris în două feluri — NFC compune (e + accent devine un singur caracter), NFD descompune (un caracter devine e + accent). Sunt concepute să nu piardă informație și sunt cele două forme de folosit la comparare și stocare. NFKC și NFKD fac tot ce fac primele două și apoi înlocuiesc și caracterele de compatibilitate: ligatura fi devine două litere, ½ devine 1⁄2, cifra romană ⅲ devine XII, un A de lățime întreagă devine un A obișnuit. Exact asta vrea un index de căutare sau un filtru de cuvinte cheie și exact ce nu trebuie aplicat niciodată unei parole.
De ce patru forme și nu una

Diviziunea există pentru că urmăreau două scopuri diferite. Compunerea și descompunerea textului este o modificare canonică: rezultatul înseamnă același lucru și, pentru primele două forme, nu se pierde nimic. Eliminarea ligaturilor, a exponenților, a literelor încercuite și a formelor fullwidth este o modificare de compatibilitate: se face pentru comoditatea potrivirii și distruge într-adevăr distincția dintre două șiruri pe care Unicode le spune diferite. Cele patru forme sunt toate combinațiile celor două decizii.

Tabelul de mai sus este lista de cazuri din fișierul de date al paginii, verificată față de propria funcție String.prototype.normalize a platformei, iar Autotestul reverifică fiecare rând sub ochii utilizatorului. Primele două rânduri trebuie citite împreună: sunt același text scris în două feluri și, după normalizare, același șir.

Exemple lucrate

Acestea sunt șirurile prin care trece butonul Exemplu, normalizate live de pagină, nu introduse ca răspunsuri. Fiecare a fost ales pentru că o formă face ceva vizibil pentru el.

Caractere de compatibilitate: ce costă NFKC

NFKC și NFKD sunt cu pierdere de informație, iar pierderea este intenționată. Ele răspund la întrebarea „este acesta același cuvânt”, nu la întrebarea „este acesta același șir”. Urmează trei consecințe, toate ușor de greșit în codul de producție:

  • Nu se stochează niciodată o parolă, un token sau o semnătură într-o formă de compatibilitate. Două intrări diferite s-ar reduce la aceeași valoare stocată, ceea ce transformă o verificare de lungime sau de caractere într-una mai slabă. Parolele se normalizează cu NFC (sau deloc) și se compară octet cu octet, după ce se face exact același lucru pe ambele părți.
  • O echivalență de compatibilitate poate transforma un caracter în mai multe. Ligatura fi este un punct de cod care devine două litere, deci un câmp validat pentru lungime înainte de normalizare poate ajunge mai lung după aceea. Se normalizează întâi, apoi se validează, apoi se stochează.
  • NFKC nu elimină tot ce este invizibil. Un selector de variantă supraviețuiește NFKC și NFKD, și la fel un conector de lățime zero. De aceea această pagină are un mod de scanare: normalizarea răspunde la o întrebare despre forma și identitatea unui caracter, iar scanerul răspunde la întrebarea dacă ceva este prezent deloc.
Grafeme, puncte de cod și octeți

Normalizarea schimbă al doilea dintre aceste numere și de obicei lasă primul neatins. Cele trei numere sunt toate „lungimea textului” și sunt toate diferite, motiv pentru care un câmp de formular cu o limită de 20 de caractere poate conține un șir de 20 de puncte de cod, 30 de unități UTF-16 și 47 de octeți.

Numărul din coloana grafemelor este cel pe care îl numără o persoană. Diferența dintre el și numărul de puncte de cod este exact ce urmărește scanerul: un nume format din 12 puncte de cod poate apărea ca 12 litere, ca 3 litere cu 9 marcaje invizibile între ele sau ca un singur emoji.

Caracterele pe care nu le vezi

Un spațiu de lățime zero nu ocupă loc, nu schimbă niciun glif și este un caracter real cu punct de cod real. Doi identificatori care arată identic pe ecran pot diferi printr-unul dintre ei, iar astfel un nume dintr-o listă poate fi un impostor care se sortează, se caută și se autentifică altfel decât numele de lângă el. Același lucru este valabil și pentru controalele bidirecționale, care pot face un șir să fie afișat într-o ordine diferită de cea în care a fost scris — un truc folosit pentru a ascunde nume de fișiere.

Scanerul din al treilea mod listează fiecare dintre ele cu poziția sa: caractere de lățime zero și conectori, controale bidirecționale, selectori de variantă, spații neobișnuite, caractere de control C0 și C1, puncte de cod de uz privat și un surogat care nu este jumătatea unei perechi valide, care nu este deloc un caracter. Tabelul claselor de mai sus spune ce face fiecare clasă, iar butonul de eliminare înlătură din copie acele caractere fără să atingă intrarea.

Greșelile pentru care este construită această pagină
  • Compararea a două șiruri fără normalizare. 'é' === 'é' este fals. Se normalizează ambele părți cu aceeași formă înainte de comparare și se folosește aceeași formă în indexul cu care se compară.
  • Normalizarea doar pe o parte. O căutare care normalizează interogarea, dar nu și valoarea stocată, nu găsește nimic și pare o problemă de date, nu una de normalizare.
  • Stilizarea unui formular cu o formă de compatibilitate și considerarea lui ca fiind sigur pentru NFC. NFKC aplicat unei parole reduce spațiul posibilelor parole și unește intrări care trebuiau să fie distincte.
  • Presupunerea că o formă de normalizare repară o falsificare. Nu elimină un conector de lățime zero, un selector de variantă sau un omoglif — un а chirilic și un a latin sunt puncte de cod diferite pe care NFC le lasă neatinse.
  • Încrederea într-un număr de caractere. Numărul pe care îl vede utilizatorul, cel pe care îl impune coloana din baza de date și cel pe care îl impune o limită de octeți sunt trei numere diferite, iar rândurile de mai sus arată cât de departe pot fi unele de altele.
  • Presupunerea că NFC este ce se va primi. Textul care sosește de la o metodă de introducere, dintr-un fișier sau de la alt sistem este în forma produsă de acel sistem. Pagina pornește cu NFC pentru că aceasta este forma pe care se așteaptă ca web-ul să o folosească, nu pentru că aceasta este forma intrării.
Răspunsuri rapide

„De ce cele două șiruri nu sunt egale când arată la fel?”

Pentru că unul dintre ele este scris cu un caracter compus, iar celălalt cu o literă de bază plus un semn combinat, sau pentru că unul conține un caracter care nu desenează nimic. Se trece în modul Compară și se pune câte un șir în fiecare casetă: pagina raportează prima poziție unde șirurile normalizate diferă și ce se află în acea poziție în fiecare.

„Ce formă să folosesc într-o aplicație?”

NFC pentru orice va citi, afișa, stoca și căuta un om, care este forma recomandată de W3C pentru web. NFD este răspunsul potrivit atunci când codul din spate vrea litere de bază și semne separate, ceea ce este frecvent în prelucrarea textului și în unele sisteme de fișiere de pe macOS. NFKC sau NFKD se folosesc doar acolo unde se potrivește, nu se stochează: indexuri de căutare, filtre de cuvinte cheie, detectarea duplicatelor.

„Un șir deja normalizat rămâne normalizat după ce i se concatenează altceva?”

Nu neapărat. Normalizarea nu se păstrează prin concatenare: unirea a două șiruri NFC poate produce o secvență pe care NFC ar fi scris-o altfel, deoarece un semn combinat de la începutul celui de-al doilea șir se poate atașa de caracterul de la sfârșitul primului. Se normalizează după asamblare, nu doar înainte.

„Normalizarea elimină caracterele invizibile?”

Nu, cu o excepție parțială: NFKC și NFKD înlocuiesc totuși spațiul fără întrerupere cu un spațiu obișnuit, ceea ce este un rezultat vizibil al unei echivalențe de compatibilitate. Restul — spațiile de lățime zero, conectorii, controalele bidirecționale, selectorii de variantă — trec prin toate cele patru forme neatinse. Pentru asta există al treilea mod.

„De ce se schimbă numărul de octeți când numărul de caractere nu se schimbă?”

Pentru că cele două forme sunt construite din puncte de cod diferite, care costă un număr diferit de octeți în UTF-8. Un é precompus are doi octeți; perechea descompusă are trei. Tabelul de numărători de mai sus afișează cele patru numere alăturat pentru intrare și pentru fiecare formă, astfel încât diferența este vizibilă, nu doar descrisă.

„De unde știu că rezultatele din această pagină sunt corecte?”

Se apasă Autotest. Acesta normalizează fiecare caz din fișierul de date al paginii, verifică din nou cele patru forme una față de alta pentru relațiile documentate (NFC este idempotent, NFD al unui șir NFC este același cu NFD-ul originalului etc.), trece prin scaner fiecare caracter menționat în tabelele invizibile și de control și numără cazurile de grafeme față de Intl.Segmenter.

Standarde și creditare

Cele patru forme și definițiile lor provin din Unicode Standard, Anexa 15 (Unicode Normalization Forms). Punctele de cod folosite ca exemple de descompunere sunt cele atribuite de standard; pagina nu implementează ea însăși tabelele de descompunere — apelează String.prototype.normalize al browserului, aceeași implementare pe care o folosește platforma pentru introducerea textului, sortare și comparare, deci ce se raportează aici este ce face restul sistemului cu același șir. Clusterele de grafeme provin din Intl.Segmenter acolo unde browserul îl are, iar dintr-o aproximare documentată acolo unde nu îl are.

Textul paginii a fost scris pentru acest site. Fiecare exemplu lucrat de pe pagină a fost produs de codul paginii și apoi verificat față de lista de cazuri înregistrată în fișierul de date, la rândul ei verificată față de datele NormalizationTest Unicode pentru aceleași caractere. Tabelele de caractere invizibile și periculoase sunt un subset selectat, scris pentru avertismente citibile de oameni, nu profilul complet de securitate Unicode (UTS #39), iar pagina spune asta în loc să sugereze o completitudine pe care nu o are.