std.unicode

Unicode-Korrektheit kostet Platz: Die Tabellen aus UnicodeData.txt sind groß. Deshalb steckt sie nicht in std.text, sondern in dieser Unit, die man bewusst dazuholt. Wer nur UTF-8 zählen und schneiden will, zahlt nichts dafür.

std.unicode liefert:

  • Normalisierung NFD, NFC, NFKD, NFKC — vollständige kanonische und Kompatibilitäts-Dekomposition, kanonische Sortierung der Kombinationszeichen, Komposition mit korrektem Blocking, Hangul algorithmisch
  • Normalisierendes Vergleichen: TextEqualsNormalized, TextCompareNormalized, TextStartsWithNormalized
  • Groß-/Kleinschreibung auf Codepoint- und Text-Ebene — regelbasiert für ASCII und Latin-1
  • Klassifizierung: Unicode-Whitespace, ASCII-Ziffer/-Buchstabe, dazu Unicode-bewusstes Trimmen

Die Tabellen liegen in der generierten Unit std.unicode_data (aus UnicodeData.txt), die dieser Unit gehört und nicht direkt benutzt wird.

Autor: Andreas Röne
Copyright: 2024-2026 Andreas Röne

std.text · std.unicode_case · std.grapheme · Standard Library


Normalisierung

Dasselbe Schriftbild kann verschieden kodiert sein: „é„ als ein Codepoint U+00E9 (vorkomponiert) oder als e + Combining Acute U+0301 (zerlegt). Ein Bytevergleich hält beide für verschieden. Normalisierung bringt sie auf eine gemeinsame Form.

Form Bedeutung
NFD Kanonische Dekomposition — alles zerlegt
NFC Kanonische Dekomposition, danach Komposition — die kompakte Standardform
NFKD Wie NFD, zusätzlich Kompatibilitäts-Zerlegung (fi, ²2)
NFKC Wie NFKD, danach Komposition

import std.io;
import std.text;
import std.unicode;

// "e" + U+0301 (Combining Acute) aus Codepoints gebaut — als Literal
// geschrieben würde sich die zerlegte Form beim Kopieren still in die
// vorkomponierte verwandeln, und das Beispiel zeigte nichts mehr.
fn Zerlegt(): Text {
    var buf: int64 := alloc(8);
    var n: int64 := CodepointToUtf8(101, buf);        // 'e'
    n := n + CodepointToUtf8(769, buf + n);           // U+0301
    return TextFromUtf8(buf, n);
}

fn main(): int64 {
    var a: Text := TextFromPchar("é"c);      // U+00E9, vorkomponiert
    var b: Text := Zerlegt();

    PrintLn(IntToStr(a.ByteLength()));            // 2
    PrintLn(IntToStr(b.ByteLength()));            // 3
    PrintLn(IntToStr(a.Equals(b)));               // 0 — Bytevergleich
    PrintLn(IntToStr(TextEqualsNormalized(a, b))); // 1 — normalisiert gleich

    var n: Text := TextToNFC(b);
    PrintLn(IntToStr(n.ByteLength()));            // 2
    var d: Text := TextToNFD(a);
    PrintLn(IntToStr(d.ByteLength()));            // 3
    return 0;
}

Signatur Beschreibung
TextToNFD(t: Text): Text Kanonische Dekomposition + kanonische Sortierung
TextToNFC(t: Text): Text NFD, danach kanonische Komposition
TextToNFKD(t: Text): Text Kompatibilitäts-Dekomposition
TextToNFKC(t: Text): Text NFKD, danach Komposition
TextEqualsNormalized(a, b): int64 Vergleich nach Normalisierung
TextCompareNormalized(a, b): int64 Lexikografisch nach Normalisierung: -1/0/1
TextStartsWithNormalized(t, prefix): int64 Präfixtest nach Normalisierung

Die 11.172 modernen Hangul-Silben (U+AC00–U+D7A3) werden algorithmisch zerlegt und zusammengesetzt (Unicode §3.12) — dafür braucht es keine Tabelle.

NFC vergleicht, NFKC vereinheitlicht. NFKC wirft Formatierungsunterschiede weg ( und fi werden gleich) und ist deshalb für Suchindizes und Bezeichner-Vergleiche gedacht, nicht zum Speichern von Nutzertext — die Umwandlung ist nicht umkehrbar.

Groß-/Kleinschreibung

Signatur Beschreibung
UnicodeToUpper(cp: int64): int64 Codepoint zu Groß — ASCII und Latin-1 Supplement
UnicodeToLower(cp: int64): int64 Codepoint zu Klein — ASCII und Latin-1 Supplement
TextToUpper(t: Text): Text Dekodieren → wandeln → neu kodieren
TextToLower(t: Text): Text dito
 
Diese Umwandlung ist regelbasiert und reicht nur bis Latin-1. Griechisch, Kyrillisch und Latin Extended bleiben unverändert: TextToUpper("Straße Über") liefert STRAßE ÜBER — das ß bleibt stehen, weil es keine 1:1-Großform hat.

Für die vollständige Abdeckung std.unicode_case verwenden (TextToUpperFull/TextToLowerFull). Die beiden Units sind bewusst getrennt: Beide bringen große generierte Tabellen mit, und zusammen übersetzt überschreitet das die Größengrenze des Compilers pro Übersetzungseinheit.

Klassifizierung und Trimmen

Signatur Beschreibung
UnicodeIsWhitespace(cp: int64): int64 Unicode-Eigenschaft White_Space — nicht nur ASCII
UnicodeIsAsciiDigit(cp: int64): int64 09
UnicodeIsAsciiLetter(cp: int64): int64 AZ, az
TextTrimUnicode(t: Text): Text Unicode-Whitespace an beiden Enden entfernen
TextTrimStartUnicode(t: Text): Text nur vorn
TextTrimEndUnicode(t: Text): Text nur hinten

Der Unterschied zu Text.Trim(): Jenes kennt nur ASCII-Whitespace. Diese Funktionen entfernen auch geschütztes Leerzeichen, Halbgeviert-Abstand und die übrigen Zeichen mit der Eigenschaft White_Space — genau das, was aus kopiertem Text von Webseiten hereinkommt.


Letzte Aktualisierung: 2026-08-11