Unicode-normalisator

Unicode kent vier normalisatievormen en ze zijn niet uitwisselbaar. NFC en NFD herschikken hetzelfde teken tussen één codepunt en een basisletter plus een combinerend teken; NFKC en NFKD gaan verder en herschrijven tekens die alleen op elkaar lijken — de fi-ligatuur, het volbreedte A, de omcirkelde ①, het Romeinse cijfer Ⅻ. Deze pagina haalt dezelfde tekst door alle vier, toont een rij per teken met wat er veranderde en waarom, telt grafemen tegen codepunten en UTF-8-bytes, en vergelijkt twee strings onder de vorm die u kiest. Het tweede deel gaat over de tekens die helemaal niet zichtbaar worden: zero-width spaces, joiners, bidirectionele besturingstekens, variatieselectoren, C0- en C1-besturingstekens, codepunten voor privégebruik en kapotte surrogaathelften, elk met positie en naam, en de optie om ze uit een kopie van de tekst te verwijderen.
Alle verwerking gebeurt lokaal in uw browser — uw gegevens worden nooit naar een server geüpload.

Weet u niet wat normalisatie is? Unicode-normalisatie in gewone taal uitgelegd — waarom é één teken of twee kan zijn, en waarom dat een inlog kan breken.
Vorm om te rapporteren en mee te vergelijken:
Vergelijk de twee vakken na:
NFC (nog niets)
NFD (nog niets)
NFKC (nog niets)
NFKD (nog niets)
Nog niets vergeleken. Plak de twee strings en druk op De twee strings vergelijken.
eerste string, genormaliseerd (nog niets)
tweede string, genormaliseerd (nog niets)
Nog niets gescand. Druk op Scannen op onzichtbare en gevaarlijke tekens, en daarna op Verwijderen als u de tekst zonder die tekens wilt.
tekst zonder de geselecteerde klassen (nog niets)
Ctrl+Enter in het eerste vak voert de actieve modus uit.
Kies een modus en klik op de actieknop. Normaliseren schrijft alle vier vormen van uw tekst en een rij per teken; Vergelijken zegt of twee strings na normalisatie gelijk zijn; Scannen somt de tekens op die niet zichtbaar zijn en kan ze verwijderen. De knop Zelftest draait elk gedocumenteerd geval in het gegevensbestand van deze pagina opnieuw, elk teken dat de onzichtbare- en besturingstabellen noemen, en meldt het aantal hier.
Elk teken, één rij per teken:wat het teken is, wat de vier vormen ermee doen, en waarom. Een gearceerde rij is een teken dat ten minste één vorm verandert; de NFD-kolom toont waarin het teken ontbindt wanneer het dat doet.
Hoeveel tekens is dat?Dezelfde tekst op vier manieren geteld: grafeemclusters (wat een lezer telt), codepunten (wat Unicode toekent), UTF-16-eenheden (wat JavaScript indexeert) en UTF-8-bytes (wat reist). Normalisatie verplaatst de getallen tussen de tweede en de derde kolom zonder de eerste te veranderen.
Wat er is gevonden, in de volgorde waarin het voorkomt:de positie telt codepunten, de klasse is die waartoe het teken behoort, en de notitie zegt wat dat teken doet met alles wat de tekst daarna leest.
De klassen die deze pagina als verdacht behandelt:de tabel wordt uit de scanner zelf afgedrukt, zodat de woorden op deze pagina en het gedrag van de knoppen niet uit elkaar kunnen lopen. Ernst gaat over het effect op een vergelijking, niet over hoe waarschijnlijk het is dat het teken onschuldig is.
Tekens voor privégebruik staan om een andere reden op de lijst met verdachte tekens dan de rest: ze worden wel weergegeven, maar ze hebben geen afgesproken betekenis, dus hetzelfde codepunt is in elk lettertype dat het definieert een andere afbeelding.
Hoe groot een invoer deze pagina accepteert, en waarom:normalisatie is een herbouw van de hele string, en deze pagina doet er meerdere van plus een scan en een grafeemtelling, dus de kosten groeien recht evenredig met de lengte van de invoer. De limieten hieronder bestaan omdat de tabel per teken in het document wordt getekend, en een tabel met een miljoen rijen is wat een browser echt doet vastlopen.
Wat is Unicode-normalisatie?

Unicode geeft voor sommige tekens meer dan één manier om ze te schrijven. De letter é kan één codepunt zijn, U+00E9, of een gewone e gevolgd door een combinerend accent aigu, U+0065 U+0301. De twee zien er op het scherm hetzelfde uit, drukken hetzelfde af, en als strings zijn ze verschillend: ze hebben verschillende lengtes, verschillende UTF-8-bytes, en 'é' === 'é' is false.

Dat is geen curiositeit, het is een klasse van bugs. Of u de ene of de andere vorm krijgt, hangt af van de toetsenbordindeling, het besturingssysteem, de invoermethode en of de tekst ooit via een tekstverwerker is gekopieerd. Een wachtwoordcontrole, een unieke databaseindex, een blokkeerlijst of een cachesleutel die strings byte voor byte vergelijkt, behandelt de twee schrijfwijzen als twee verschillende waarden — zo kan een gebruiker een wachtwoord instellen op het ene apparaat en niet inloggen op het andere, en kan hetzelfde woord twee keer voorkomen in een lijst met "al bezette" namen.

De versie in 30 seconden: NFC en NFD zijn dezelfde tekst op twee manieren geschreven — NFC componeert (e + accent wordt één teken), NFD ontbindt (één teken wordt e + accent). Ze zijn bedoeld als verliesvrij en zijn de twee vormen om te vergelijken en op te slaan. NFKC en NFKD doen alles wat de eerste twee doen en vervangen daarna ook compatibiliteitstekens: de ligatuur fi wordt twee letters, ½ wordt 1⁄2, het Romeinse cijfer ⅲ wordt XII, een volbreedte A wordt een gewone A. Dat is precies wat een zoekindex of een trefwoordfilter wil, en precies wat u nooit op een wachtwoord mag toepassen.
Waarom vier vormen en niet één

De splitsing bestaat omdat twee verschillende doelen werden gediend. Samenstellen en ontbinden van tekst is een canonieke wijziging: het resultaat betekent hetzelfde en er gaat, bij de eerste twee vormen, niets verloren. Het wegvouwen van ligaturen, superscripts, omcirkelde letters en volbreedte-vormen is een compatibiliteitswijziging: die wordt gedaan voor het gemak van matchen, en ze vernietigt wel het onderscheid tussen twee strings die Unicode als verschillend beschouwt. De vier vormen zijn elke combinatie van die twee beslissingen.

De tabel hierboven is de caselijst uit het gegevensbestand van deze pagina, gecontroleerd tegen de eigen String.prototype.normalize van het platform, en de zelftest controleert elke rij ervan voor uw ogen opnieuw. Lees de eerste twee rijen samen: het is dezelfde tekst, twee keer geschreven, en na normalisatie zijn het dezelfde string.

Uitgewerkte voorbeelden

Dit zijn de strings die de knop Voorbeeld doorloopt, door de pagina live genormaliseerd in plaats van als antwoorden ingetypt. Elk is gekozen omdat een vorm er iets zichtbaars mee doet.

Compatibiliteitstekens: wat NFKC u kost

NFKC en NFKD zijn verliesgevend, en het verlies is bewust. Ze beantwoorden de vraag "is dit hetzelfde woord", niet de vraag "is dit dezelfde string". Drie gevolgen volgen daaruit, en alle drie zijn in productiecode makkelijk fout te doen:

  • Sla nooit een wachtwoord, token of handtekening op in een compatibiliteitsvorm. Twee verschillende invoeren zouden samenvallen tot dezelfde opgeslagen waarde, waardoor een lengte- of tekencontrole zwakker wordt. Normaliseer wachtwoorden met NFC (of helemaal niet) en vergelijk ze als bytes nadat u precies hetzelfde aan beide kanten hebt gedaan.
  • Een compatibiliteitsvouw kan één teken in meerdere veranderen. De ligatuur fi is één codepunt dat twee letters wordt, dus een veld dat vóór normalisatie op lengte is gevalideerd, kan daarna langer zijn. Normaliseer eerst, valideer daarna, en sla dan pas op.
  • NFKC verwijdert niet alles wat onzichtbaar is. Een variatieselector overleeft NFKC en NFKD, en een zero-width joiner ook. Daarom heeft deze pagina een scanmodus: normalisatie beantwoordt een vraag over de vorm en identiteit van een teken, en de scanner beantwoordt de vraag of er iets aanwezig is.
Grafemen, codepunten en bytes

Normalisatie verandert het tweede van deze tellingen en laat het eerste meestal ongemoeid. De drie getallen zijn alle "de lengte van de tekst" en allemaal verschillend, daarom kan een invoerveld met een limiet van 20 tekens horen bij een string die 20 codepunten, 30 UTF-16-eenheden en 47 bytes lang is.

Het getal in de grafeemkolom is wat een mens telt. Het verschil met het aantal codepunten is precies waar de scanner over gaat: een naam die uit 12 codepunten bestaat, kan worden weergegeven als 12 letters, als 3 letters met 9 onzichtbare tekens ertussen, of als één emoji.

De tekens die u niet kunt zien

Een zero-width space neemt geen breedte in beslag, verandert geen glyph, en is een echt teken met een echt codepunt. Twee identifiers die op het scherm identiek lijken, kunnen door één van die tekens verschillen, en zo kan een naam in een lijst een oplichter zijn die anders sorteert, zoekt en authenticeert dan de naam ernaast. Hetzelfde geldt voor de bidirectionele besturingstekens, die een string in een andere volgorde kunnen laten weergeven dan waarin hij is geschreven — een truc die is gebruikt om bestandsnamen te vermommen.

De scanner in de derde modus somt elk van hen op met zijn positie: zero-width- en joiner-tekens, bidirectionele besturingstekens, variatieselectoren, ongewone spaties, C0- en C1-besturingstekens, codepunten voor privégebruik, en een surrogaat dat niet de helft van een geldig paar is, en dus helemaal geen teken is. De klassetabel hierboven op deze pagina zegt wat elke klasse doet, en de verwijderknop verwijdert een kopie van die tekens zonder uw invoer aan te raken.

De fouten waarvoor deze pagina is gebouwd
  • Twee strings vergelijken zonder ze te normaliseren. 'é' === 'é' is onwaar. Normaliseer beide kanten met dezelfde vorm voordat u vergelijkt, en gebruik dezelfde vorm in de index waartegen u vergelijkt.
  • Maar aan één kant normaliseren. Een opzoeking die de zoekopdracht normaliseert en de opgeslagen waarde niet, vindt niets, en lijkt een gegevensprobleem in plaats van een normalisatieprobleem.
  • Een formulier met een compatibiliteitsvorm invullen en het NFC-veilig noemen. NFKC op een wachtwoord verkleint de ruimte van mogelijke wachtwoorden en voegt invoeren samen die verschillend bedoeld waren.
  • Aannemen dat een normalisatievorm een spoof oplost. Dat verwijdert geen zero-width joiner, geen variatieselector en geen homoglyph — een Cyrillische а en een Latijnse a zijn verschillende codepunten die NFC met rust laat.
  • Een tekenaantal vertrouwen. Het aantal dat een gebruiker ziet, het aantal dat een databasekolom afdwingt en het aantal dat een bytelimiet afdwingt, zijn drie verschillende getallen, en de rijen hierboven laten zien hoe ver die uit elkaar kunnen liggen.
  • Aannemen dat u NFC zult krijgen. Tekst die binnenkomt via een invoermethode, een bestand of een ander systeem heeft de vorm die dat systeem heeft gemaakt. Deze pagina begint op NFC omdat dat de vorm is die het web naar verwachting gebruikt, niet omdat dat uw invoer is.
Korte antwoorden

“Waarom zijn deze twee strings niet gelijk terwijl ze er hetzelfde uitzien?”

Omdat de ene geschreven is met een samengesteld teken en de andere met een basisletter plus een combinerend teken, of omdat een van beide een teken bevat dat helemaal niets tekent. Schakel naar de modus Vergelijken en zet één string in elk vak: de pagina meldt de eerste positie waarop de genormaliseerde strings verschillen en wat er op die positie in elk van beide staat.

“Welke vorm moet ik in een toepassing gebruiken?”

NFC voor alles wat een mens leest, weergeeft, opslaat en doorzoekt, de vorm die het W3C voor het web aanbeveelt. NFD is het juiste antwoord wanneer de code achter u basisletters en losse tekens wil, wat gebruikelijk is in tekstverwerking en in sommige bestandssystemen op macOS. Gebruik NFKC of NFKD alleen waar u vergelijkt in plaats van opslaat: zoekindexen, trefwoordfilters, dubbeldetectie.

“Blijft een al genormaliseerde string genormaliseerd nadat ik er iets aan toevoeg?”

Niet noodzakelijk. Normalisatie blijft niet behouden bij aaneenschakeling: twee NFC-strings samenvoegen kan een reeks opleveren die NFC anders zou hebben geschreven, omdat een combinerend teken aan het begin van de tweede string zich kan hechten aan het teken aan het eind van de eerste. Normaliseer na het samenstellen, niet alleen ervoor.

“Verwijdert normalisatie de onzichtbare tekens?”

Nee, met één gedeeltelijke uitzondering: NFKC en NFKD vervangen de no-break space wel door een gewone spatie, wat een zichtbaar resultaat is van een compatibiliteitsvouw. Al het overige — zero-width spaces, joiners, bidirectionele besturingstekens, variatieselectoren — gaat door alle vier vormen ongewijzigd heen. Daarvoor bestaat de derde modus.

“Waarom verandert het aantal bytes terwijl het aantal tekens dat niet doet?”

Omdat de twee vormen zijn opgebouwd uit verschillende codepunten die in UTF-8 een verschillend aantal bytes kosten. Een vooraf samengestelde é is twee bytes; het ontbonden paar is drie. De telling in de tabel hierboven drukt alle vier getallen naast elkaar af voor de invoer en voor elke vorm, zodat het verschil zichtbaar is in plaats van omschreven.

“Hoe weet ik of de resultaten op deze pagina kloppen?”

Druk op Zelftest. Die normaliseert elk geval in het gegevensbestand van deze pagina, controleert de vier vormen onderling op de gedocumenteerde relaties (NFC is idempotent, NFD van een NFC-string is hetzelfde als de NFD van het origineel, enzovoort), haalt elk teken dat in de onzichtbare- en besturingstabellen staat door de scanner, en telt de grafeemgevallen tegen Intl.Segmenter.

Standaarden en naamsvermelding

De vier vormen en hun definities komen uit de Unicode Standard, Annex 15 (Unicode Normalization Forms). De codepunten die als ontbindingsvoorbeelden dienen, zijn de codepunten die de standaard toekent; deze pagina implementeert de ontbindingstabellen zelf niet — ze roept de eigen String.prototype.normalize van de browser aan, dezelfde implementatie die het platform gebruikt voor tekstinvoer, sorteren en vergelijken, dus wat hier wordt gerapporteerd is wat de rest van het systeem met dezelfde string doet. Grafeemclusters komen van Intl.Segmenter waar de browser die heeft, en van een gedocumenteerde benadering waar dat niet zo is.

De paginatekst is voor deze site geschreven. Elk uitgewerkt voorbeeld op deze pagina is door de eigen code van de pagina geproduceerd en daarna gecontroleerd tegen de vastgelegde caselijst van het gegevensbestand, die op zijn beurt is gecontroleerd tegen de NormalizationTest-gegevens van Unicode voor dezelfde tekens. De tabellen met onzichtbare en gevaarlijke tekens zijn een samengestelde deelverzameling die bedoeld is voor voor mensen leesbare waarschuwingen, niet het volledige Unicode-beveiligingsprofiel (UTS #39), en de pagina zegt dat, in plaats van een volledigheid te suggereren die ze niet heeft.