====== 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