====== std.text ====== ''Text'' ist die **UTF-8-Ebene** über den rohen Bytes. Sie validiert die Kodierung bei der Erzeugung und arbeitet auf **Codepoints** statt auf Bytes: ''CodepointCount'', ''CodepointAt'', ''SubstringCp'' zählen und schneiden dort, wo ein Zeichen anfängt — nicht mitten in einer Mehrbyte-Sequenz. Die Unit braucht **keine Unicode-Tabellen**: UTF-8 zu prüfen und zu dekodieren ist reine Bytemuster-Arbeit. Alles, was Tabellen braucht — Normalisierung, volle Groß-/Kleinschreibung, Graphem-Segmentierung — liegt in eigenen Units, die man dazuholt: ^ Aufgabe ^ Unit ^ | Bytes, Besitz, Länge | ''[[lyx_-_programmiersprache:units:strtype|std.strtype]]'' | | UTF-8, Codepoints, Suchen/Schneiden/Splitten | **std.text** | | Normalisierung NFC/NFD/NFKC/NFKD, Whitespace | ''[[lyx_-_programmiersprache:units:unicode|std.unicode]]'' | | Volle Groß-/Kleinschreibung, Caseless-Vergleich | ''[[lyx_-_programmiersprache:units:unicode_case|std.unicode_case]]'' | | Graphem-Cluster („was der Nutzer als ein Zeichen sieht") | ''[[lyx_-_programmiersprache:units:grapheme|std.grapheme]]'' | Intern speichert Lyx **immer UTF-8**. UTF-16 gibt es nur als Grenzformat (Windows-API, UTF-16-Dateien) — dafür die Konverter am Ende dieser Seite. **Autor:** Andreas Röne\\ **Copyright:** 2024-2026 Andreas Röne ---- ===== Erzeugen und ausgeben ===== ''Text'' ist eine **Klasse** mit Referenzsemantik. Die freien ''Text*''-Funktionen sind dünne Hüllen um die gleichnamigen Methoden — beide Schreibweisen sind gleichwertig. import std.io; import std.text; fn Zeige(t: Text): void { var p: pchar := t.ToPchar() as pchar; PrintLn(p); } fn main(): int64 { var t: Text := TextFromPchar("Größe"c); PrintLn(IntToStr(t.ByteLength())); // 7 — Bytes PrintLn(IntToStr(t.CodepointCount())); // 5 — Zeichen PrintLn(IntToStr(t.IsValid())); // 1 Zeige(t.SubstringCp(0, 3)); // "Grö" — schneidet nach Codepoints Zeige(t.AsciiUpper()); // "GRößE" — nur ASCII wird gewandelt return 0; } > **Die Hilfsfunktion ''Zeige'' ist kein Zufall.** ''ToPchar()'' hat den Rückgabetyp ''int64''; ein ''as pchar'' direkt im Argument von ''PrintLn'' wird nicht übernommen und würde die Adresse als Zahl ausgeben. Der Wert muss erst in eine ''pchar''-Variable. ^ Funktion ^ Beschreibung ^ | ''TextFromPchar(p: int64): Text'' | Aus NUL-terminiertem UTF-8 | | ''TextFromUtf8(ptr: int64, n: int64): Text'' | Aus ''n'' rohen Bytes | Ungültige Bytes führen **nicht** zum Fehlschlag: Die Bytes werden gespeichert und ''IsValid()'' meldet ''0''. Geprüft wird auf korrekte Start-/Folgebytes, Overlong-Kodierungen, Surrogate und den Bereich bis U+10FFFF. ---- ===== Methoden ===== ==== Abfragen ==== ^ Signatur ^ Beschreibung ^ | ''ByteLength(): int64'' | Länge in Bytes | | ''CodepointCount(): int64'' | Anzahl der Codepoints | | ''IsValid(): int64'' | ''1'', wenn der Inhalt gültiges UTF-8 ist | | ''Data(): int64'' | Zeiger auf die Rohbytes (nicht NUL-terminiert) | | ''ByteAt(i: int64): int64'' | Byte an Byte-Position ''i'' | | ''CodepointAt(idx: int64): int64'' | Codepoint an **Codepoint**-Position ''idx'' | | ''ByteOffsetOfCodepoint(idx: int64): int64'' | Byte-Offset, an dem Codepoint ''idx'' beginnt | ==== Ableiten und Vergleichen ==== ^ Signatur ^ Beschreibung ^ | ''Add(other: Text): Text'' / ''Concat'' | Verkettung als neues ''Text'' | | ''SubstringCp(startCp, countCp): Text'' | Ausschnitt nach Codepoint-Positionen | | ''Equals(other: Text): int64'' | Byte-Inhaltsvergleich | | ''EqualsPchar(p: int64): int64'' | Vergleich gegen ''pchar'' | | ''StartsWith(prefix: Text): int64'' | Präfixtest | | ''Compare(other: Text): int64'' | Lexikografisch: ''-1'', ''0'', ''1'' | | ''ToPchar(): int64'' | Frische NUL-terminierte Kopie | | ''Free()'' | Puffer freigeben | > ''Equals'' vergleicht **Bytes**. „é" als ein Codepoint (U+00E9) und „é" als ''e'' + Combining Acute (U+0301) sind damit ungleich, obwohl sie gleich aussehen. Wer das gleichsetzen will, normalisiert vorher — ''TextEqualsNormalized'' in ''[[lyx_-_programmiersprache:units:unicode|std.unicode]]''. ==== Suchen, Trimmen, Ersetzen, Splitten ==== ^ Signatur ^ Beschreibung ^ | ''Find(needle: Text): int64'' | **Byte**-Offset des ersten Treffers, ''-1'' wenn nicht gefunden | | ''FindCp(needle: Text): int64'' | **Codepoint**-Position des ersten Treffers | | ''Contains(needle: Text): int64'' | Enthaltenstest | | ''Trim(): Text'' | ASCII-Whitespace an beiden Enden entfernen | | ''Replace(old: Text, repl: Text): Text'' | Alle Vorkommen ersetzen | | ''SplitCount(sep: Text): int64'' | Anzahl der Teile | | ''PartAt(sep: Text, idx: int64): Text'' | Einzelnes Teil — der bequeme Weg | | ''Split(sep, out: int64, maxParts: int64): int64'' | Rohform: schreibt bis zu ''maxParts'' Feldblöcke à ''TEXT_PART_STRIDE'' Bytes nach ''out'' und liefert die **Gesamtzahl** der Teile zurück | | ''AsciiUpper(): Text'' / ''AsciiLower(): Text'' | Nur ASCII wandeln — Umlaute und Griechisch bleiben unangetastet | var csv: Text := TextFromPchar("a,b,c"c); var sep: Text := TextFromPchar(","c); PrintLn(IntToStr(csv.SplitCount(sep))); // 3 Zeige(csv.PartAt(sep, 1)); // "b" var alt: Text := TextFromPchar("b"c); var neu: Text := TextFromPchar("X"c); Zeige(csv.Replace(alt, neu)); // "a,X,c" > ''Split'' liefert die Gesamtzahl der Teile, auch wenn nur ''maxParts'' davon geschrieben wurden — der Rückgabewert taugt also zur Prüfung, ob der Puffer gereicht hat. Für den Normalfall ist ''SplitCount'' + ''PartAt'' der einfachere Weg. ==== Codepoint-Hilfsfunktionen ==== ^ Signatur ^ Beschreibung ^ | ''CodepointUtf8Len(cp: int64): int64'' | Bytes, die ''cp'' in UTF-8 belegt (1–4; ''0'' wenn ungültig) | | ''CodepointToUtf8(cp: int64, dest: int64): int64'' | Codepoint nach ''dest'' kodieren; liefert die Bytezahl | ---- ===== Operatoren ===== ^ Operator ^ Methode ^ Ergebnis ^ | ''a + b'' | ''Add'' | neues ''Text'' | | ''a == b'', ''a != b'' | ''Eq''/''Ne'' | Byte-Inhaltsgleichheit | | ''a < b'', ''a <= b'', ''a > b'', ''a >= b'' | aus ''Compare'' | lexikografisch | | ''a[i]'' | ''Get'' | **Codepoint** an Position ''i'' (nicht Byte!) | ---- ===== UTF-16 — nur an der Grenze ===== Für UTF-16-Dateien und die Windows-API. Innerhalb von Lyx wird nichts als UTF-16 gehalten. ^ Signatur ^ Beschreibung ^ | ''TextFromUtf16(ptr, byteLen, endian): Text'' | UTF-16 → ''Text'' mit vorgegebener Byte-Reihenfolge | | ''TextFromUtf16Bom(ptr, byteLen): Text'' | Byte-Reihenfolge aus einem führenden BOM ableiten und ihn entfernen | | ''TextUtf16Length(t: Text): int64'' | Benötigte Bytes ohne BOM (für einen BOM 2 dazu) | | ''TextToUtf16(t, dest, endian): int64'' | Nach ''dest'' kodieren, ohne BOM | | ''TextToUtf16Bom(t, dest, endian): int64'' | Mit vorangestelltem U+FEFF | > **Zwei Fallen bei der UTF-16-Umwandlung:** > > * **Ohne BOM liest ''TextFromUtf16Bom'' Big Endian** — so schreibt es RFC 2781 für schlichtes „UTF-16" vor. Von Windows erzeugte Dateien ohne BOM sind aber in aller Regel Little Endian. Wenn die Byte-Reihenfolge bekannt ist, ''TextFromUtf16'' mit ausdrücklicher Angabe verwenden. > * **Fehlerhafte Eingabe schlägt nicht fehl, sie wird ersetzt.** Ein unpaariges High- oder Low-Surrogate, ein abgeschnittenes Paar am Pufferende und ein einzelnes überzähliges Byte werden je zu **U+FFFD**. Das hält das Ergebnis als gültiges UTF-8 — wer schlechte Eingaben ablehnen statt ersetzen will, prüft das Ergebnis auf U+FFFD. ---- Letzte Aktualisierung: 2026-08-11