Wenn "Zoë"! == "Zoë" oder warum Sie Unicode-Strings normalisieren müssen

Schon mal was von Unicode-Normalisierung gehört? Du bist nicht allein. Aber jeder muss darüber Bescheid wissen. Die Normalisierung kann Ihnen viele Probleme ersparen. Früher oder später passiert bei jedem Entwickler etwas Ähnliches wie in der folgenden Abbildung.
Zoë ist nicht Zoë

Und dies ist übrigens kein Beispiel für ein anderes seltsames JavaScript. Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, sagt, dass er zeigen kann, wie sich das gleiche Problem manifestiert, wenn fast jede vorhandene Programmiersprache verwendet wird. Insbesondere sprechen wir über Python, Go und sogar Shell-Skripte. Wie gehe ich damit um?

Hintergrund


Das Unicode-Problem trat vor vielen Jahren zum ersten Mal auf, als ich eine Anwendung (in Objective-C) schrieb, die eine Kontaktliste aus dem Adressbuch des Benutzers und aus seinen sozialen Netzwerken importierte. Danach entfernte ich Duplikate. In bestimmten Situationen stellte sich heraus, dass einige Personen zweimal auf der Liste stehen. Dies geschah aufgrund der Tatsache, dass ihre Namen laut Programm nicht dieselbe Zeichenfolge waren.

Obwohl im obigen Beispiel die beiden Zeilen genau gleich aussehen, unterscheiden sich die Bytes, in denen sie auf der Festplatte gespeichert sind, in der Art und Weise, wie sie im System dargestellt werden. Im Vornamen "Zoë" das Zeichen ë (e mit Umlaut) für einen einzelnen Unicode-Codepunkt. Im zweiten Fall beschäftigen wir uns mit der Zerlegung, mit dem Ansatz, Zeichen mit mehreren Zeichen darzustellen. Wenn Sie in Ihrer Anwendung mit Unicode-Zeichenfolgen arbeiten, müssen Sie berücksichtigen, dass dieselben Zeichen auf unterschiedliche Weise dargestellt werden können.

Wie wir zu Emoji kamen: Kurz gesagt zur Zeichenkodierung


Computer arbeiten mit Bytes, die nur Zahlen sind. Um Texte auf Computern verarbeiten zu können, einigten sich die Menschen auf die Entsprechung von Zeichen und Zahlen und einigten sich darauf, wie die visuelle Darstellung von Zeichen aussehen sollte.

Die erste derartige Vereinbarung wurde durch ASCII-Codierung (American Standard Code for Information Interchange) dargestellt. Diese Codierung verwendete 7 Bit und konnte 128 Zeichen darstellen, einschließlich des lateinischen Alphabets (Groß- und Kleinbuchstaben), Zahlen und grundlegenden Satzzeichen. ASCII enthielt auch viele "nicht druckbare" Zeichen, wie z. B. einen Zeilenvorschub, Registerkarten, Zeilenumbrüche und andere. Beispielsweise wird in ASCII der lateinische Buchstabe M (Großbuchstabe m) als 77 (4D in hexadezimaler Notation) codiert.

Das Problem mit ASCII ist, dass 128 Zeichen zwar ausreichen können, um alle Zeichen darzustellen, die normalerweise mit englischen Texten verwendet werden, diese Anzahl von Zeichen jedoch nicht ausreicht, um Texte in anderen Sprachen und verschiedene Sonderzeichen wie Emojis darzustellen.

Die Lösung für dieses Problem war die Übernahme des Unicode-Standards, der darauf abzielte, jedes in allen modernen und alten Texten verwendete Zeichen darzustellen, einschließlich Zeichen wie Emojis. Im neuesten Unicode 12.0-Standard sind beispielsweise mehr als 137.000 Zeichen enthalten.

Der Unicode-Standard kann unter Verwendung einer Vielzahl von Zeichenkodierungsmethoden implementiert werden. Am häufigsten sind UTF-8 und UTF-16. Es ist zu beachten, dass im Webspace der Standard für die Codierung von Texten UTF-8 am häufigsten verwendet wird.

Der UTF-8-Standard verwendet 1 bis 4 Bytes zur Darstellung von Zeichen. UTF-8 ist eine Obermenge von ASCII, sodass die ersten 128 Zeichen mit den in der ASCII-Codetabelle dargestellten Zeichen übereinstimmen. Der UTF-16-Standard verwendet dagegen 2 bis 4 Bytes, um 1 Zeichen darzustellen.

Warum gibt es beide Standards? Tatsache ist, dass Texte in westlichen Sprachen normalerweise am effizientesten mit dem UTF-8-Standard codiert werden (da die meisten Zeichen in solchen Texten als Codes mit einer Größe von 1 Byte dargestellt werden können). Wenn wir über orientalische Sprachen sprechen, können wir sagen, dass Dateien, in denen in diesen Sprachen geschriebene Texte gespeichert sind, bei Verwendung von UTF-16 normalerweise weniger werden.

Unicode-Codepunkte und Zeichenkodierung


Jedem Zeichen im Unicode-Standard wird eine Identifikationsnummer zugewiesen, die als Codepunkt bezeichnet wird. Zum Beispiel ein Codepunkt Emoji ist U + 1F436 .

Bei der Codierung dieses Symbols kann es als verschiedene Folge von Bytes dargestellt werden:

  • UTF-8: 4 Bytes, 0xF0 0x9F 0x90 0xB6
  • UTF-16: 4 Bytes, 0xD83D 0xDC36

Im folgenden JavaScript-Code drucken alle drei Befehle dasselbe Zeichen an die Browserkonsole.

//
console.log(' ') // =>
// Unicode (ES2015+)
console.log('\u{1F436}') // =>
// UTF-16
// ( 2 )
console.log('\uD83D\uDC36') // =>


Die internen Mechanismen der meisten JavaScript-Interpreter (einschließlich Node.js und moderner Browser) verwenden UTF-16. Dies bedeutet, dass das von uns betrachtete Hundesymbol mit zwei UTF-16-Codeeinheiten (jeweils 16 Bit) gespeichert wird. Daher sollten Ihnen die folgenden Code-Ausgaben nicht unverständlich erscheinen:

console.log(' '.length) // => 2

Zeichenkombination


Zurück zu unserem Ausgangspunkt, lassen Sie uns darüber sprechen, warum die Symbole, die für eine Person gleich aussehen, eine andere interne Darstellung haben.

Einige Unicode-Zeichen dienen zum Ändern anderer Zeichen. Sie werden als Kombinieren von Zeichen bezeichnet. Sie gelten für Basiszeichen. Zum Beispiel:

  • n + ˜ = ñ
  • u + ¨ = ü
  • e + ´ = é

Wie Sie aus dem vorherigen Beispiel sehen können, können Sie mit kombinierbaren Zeichen den Basiszeichen diakritische Zeichen hinzufügen. Die Zeichenumwandlungsfunktionen von Unicode sind jedoch nicht darauf beschränkt. Zum Beispiel können einige Zeichenfolgen als Ligaturen dargestellt werden (so kann ae zu æ werden).

Das Problem ist, dass Sonderzeichen auf verschiedene Arten dargestellt werden können.

Beispielsweise kann der Buchstabe é auf zwei Arten dargestellt werden:

  • Verwenden eines einzelnen Codepunkts U + 00E9 .
  • Verwenden Sie eine Kombination aus dem Buchstaben e und dem Akutzeichen, dh zwei Codepunkte - U + 0065 und U + 0301 .

Die Zeichen, die sich aus einer dieser Darstellungsweisen für den Buchstaben é ergeben, sehen gleich aus, aber im Vergleich stellt sich heraus, dass die Zeichen unterschiedlich sind. Die Linien, die sie enthalten, haben unterschiedliche Längen. Sie können dies überprüfen, indem Sie den folgenden Code in der Browserkonsole ausführen.

 console.log('\u00e9') // => é console.log('\u0065\u0301') // => é console.log('\u00e9' == '\u0065\u0301') // => false console.log('\u00e9'.length) // => 1 console.log('\u0065\u0301'.length) // => 2 

Dies kann zu unerwarteten Fehlern führen. Sie können beispielsweise dadurch ausgedrückt werden, dass das Programm aus unbekannten Gründen einige Einträge in der Datenbank nicht finden kann, indem sich der Benutzer, der das richtige Kennwort eingibt, nicht beim System anmelden kann.

Leitungsnormalisierung


Die obigen Probleme haben eine einfache Lösung, die darin besteht, die Zeichenfolgen zu normalisieren und sie zur "kanonischen Darstellung" zu bringen.

Es gibt vier Standardformen (Algorithmen) der Normalisierung:

  • NFC: Normalisierungsform Canonical Composition.
  • NFD: Normalisierungsform Kanonische Zerlegung.
  • NFKC: Kompatibilitätszusammensetzung für Normalisierungsformulare.
  • NFKD: Kompatibilitätszerlegung für Normalisierungsformulare.

Die am häufigsten verwendete Form der Normalisierung ist NFC. Bei Verwendung dieses Algorithmus werden zuerst alle Zeichen zerlegt, wonach alle Kombinationssequenzen in der vom Standard festgelegten Reihenfolge neu zusammengesetzt werden. Für den praktischen Gebrauch können Sie ein beliebiges Formular auswählen. Die Hauptsache ist, es konsequent anzuwenden. Infolgedessen führt der Empfang derselben Daten am Programmeingang immer zu demselben Ergebnis.

In JavaScript gibt es ab dem Standard ES2015 (ES6) eine integrierte Methode zum Normalisieren von Zeichenfolgen - String.prototype.normalize ([form]) . Sie können es in der Node.js-Umgebung und in fast allen modernen Browsern verwenden. Das form dieser Methode ist die Zeichenfolgenkennung des Normalisierungsformulars. Der Standardwert ist das NFC-Formular.

Wir kehren zum zuvor betrachteten Beispiel zurück und wenden diesmal die Normalisierung an:

 const str = '\u0065\u0301' console.log(str == '\u00e9') // => false const normalized = str.normalize('NFC') console.log(normalized == '\u00e9') // => true console.log(normalized.length) // => 1 

Zusammenfassung


Wenn Sie eine Webanwendung entwickeln und das verwenden, was der Benutzer eingibt, normalisieren Sie immer die empfangenen Textdaten. In JavaScript können Sie die Standardzeichenfolgenmethode normalize () verwenden , um die Normalisierung durchzuführen.

Liebe Leser! Haben Sie Probleme mit Zeichenfolgen festgestellt, die mit der Normalisierung gelöst werden können?

Source: https://habr.com/ru/post/de445274/


All Articles