====== 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\\ **Quelle:** ''std/text.lyx'' Alle Beispiele und Angaben dieser Seite sind mit ''lyxc 1.0.21A'' übersetzt und ausgeführt; die gezeigten Ausgaben stammen aus dem tatsächlichen Lauf. ---- ===== 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; } Ausgabe: 7 5 1 Grö GRößE > **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 | import std.io; import std.text; fn Zeige(t: Text): void { var p: pchar := t.ToPchar() as pchar; PrintLn(p); } fn main(): int64 { var csv: Text := TextFromPchar("eins,zwei,drei"c); var sep: Text := TextFromPchar(","c); PrintLn("Teile: ", IntToStr(csv.SplitCount(sep))); Print("Teil 0: "); Zeige(csv.PartAt(sep, 0)); Print("Teil 1: "); Zeige(csv.PartAt(sep, 1)); Print("Teil 2: "); Zeige(csv.PartAt(sep, 2)); var alt: Text := TextFromPchar("zwei"c); var neu: Text := TextFromPchar("ZWEI"c); Print("Replace: "); Zeige(csv.Replace(alt, neu)); var h: Text := TextFromPchar("Grüße aus Köln"c); var n: Text := TextFromPchar("aus"c); PrintLn("Find (Byte-Offset) = ", IntToStr(h.Find(n))); PrintLn("FindCp (Codepoint-Pos) = ", IntToStr(h.FindCp(n))); PrintLn("ByteLength / Codepoints = ", IntToStr(h.ByteLength()), " / ", IntToStr(h.CodepointCount())); return 0; } Ausgabe: Teile: 3 Teil 0: eins Teil 1: zwei Teil 2: drei Replace: eins,ZWEI,drei Find (Byte-Offset) = 8 FindCp (Codepoint-Pos) = 6 ByteLength / Codepoints = 17 / 14 An „Grüße aus Köln" ist der Unterschied der beiden Suchfunktionen ablesbar: ''aus'' beginnt am **Byte** 8, aber am **Codepoint** 6 — dazwischen liegen die beiden Zweibyte-Zeichen ''ü'' und ''ß''. Für Ausschnitte mit ''SubstringCp'' braucht man den Codepoint-Wert, für Zeigerrechnung auf ''Data()'' den Byte-Wert. ==== Split in der Rohform ==== ''SplitCount'' + ''PartAt'' durchläuft den Text je Aufruf erneut. Wer alle Teile auf einmal braucht, nimmt ''Split'': es schreibt je Teil einen Block von ''TEXT_PART_STRIDE'' (24) Bytes nach ''out'' — Zeiger, Bytelänge, Gültigkeitsflag. import std.io; import std.text; import std.alloc; fn main(): int64 { var csv: Text := TextFromPchar("eins,zwei,drei,vier"c); var sep: Text := TextFromPchar(","c); // Platz fuer zwei Teile — absichtlich zu wenig var out: int64 := alloc(TEXT_PART_STRIDE * 2); var gesamt: int64 := csv.Split(sep, out, 2); PrintLn("Rueckgabe = ", IntToStr(gesamt), " (Gesamtzahl, nicht die geschriebenen)"); var i: int64 := 0; while (i < 2) { var d: int64 := peek64(out + i * TEXT_PART_STRIDE); // Zeiger auf Bytes var bl: int64 := peek64(out + i * TEXT_PART_STRIDE + 8); // Bytelaenge var vl: int64 := peek64(out + i * TEXT_PART_STRIDE + 16); // gueltiges UTF-8? Print(" Teil ", IntToStr(i), ": ", IntToStr(bl), " Bytes, valid=", IntToStr(vl), " -> '"); var k: int64 := 0; while (k < bl) { PrintChar(peek8(d + k)); k := k + 1; } PrintLn("'"); i := i + 1; } if (gesamt > 2) { PrintLn("Puffer war zu klein: ", IntToStr(gesamt - 2), " Teile fehlen"); } return 0; } Rueckgabe = 4 (Gesamtzahl, nicht die geschriebenen) Teil 0: 4 Bytes, valid=1 -> 'eins' Teil 1: 4 Bytes, valid=1 -> 'zwei' Puffer war zu klein: 2 Teile fehlen ''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. Die Blöcke enthalten Zeiger in den Originaltext, keine Kopien; sie bleiben nur gültig, solange dieser lebt. ==== 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 | ---- ===== Speicher und Referenzsemantik ===== ''Text'' ist eine Klasse — eine Zuweisung kopiert **nicht**, sie teilt dasselbe Objekt: var a: Text := TextFromPchar("Original"c); var b: Text := a; // dasselbe Objekt, nicht eine Kopie PrintLn(IntToStr((a.Data() == b.Data()) as int64)); // 1 a.Free(); PrintLn(IntToStr(b.ByteLength())); // 0 — b ist mit entwertet PrintLn(IntToStr(b.Data())); // 0 Nach ''a.Free()'' ist auch ''b'' leer: ''Free'' gibt den Bytepuffer frei und setzt die Felder des gemeinsamen Objekts zurück. Ein ''Text'' darf also nur **einmal** freigegeben werden, und niemand darf ihn danach noch benutzen. Alle ableitenden Methoden — ''Add'', ''SubstringCp'', ''Trim'', ''Replace'', ''AsciiUpper'', ''AsciiLower'', ''PartAt'' — legen dagegen einen **eigenen** Puffer an. Deren Ergebnisse sind unabhängig und müssen einzeln freigegeben werden, wenn sie nicht mehr gebraucht werden. ''ToPchar()'' erzeugt jedes Mal eine **frische, NUL-terminierte Kopie**. In einer Schleife aufgerufen wächst der Speicherverbrauch entsprechend — den Rückgabewert also einmal in einer Variablen halten statt mehrfach abzurufen. ---- ===== Grenzfälle ===== Keine Methode bricht bei Bereichsverletzung ab; jede hat einen definierten Rückgabewert. ^ Aufruf ^ Ergebnis ^ | ''CodepointAt(idx)'' außerhalb, auch negativ | ''-1'' | | ''ByteAt(i)'' außerhalb | ''0'' | | ''ByteOffsetOfCodepoint(idx)'' außerhalb | ''-1'' | | ''SubstringCp(1, 99)'' — zu viele Zeichen verlangt | schneidet bis zum Ende ab (''bc'') | | ''SubstringCp(5, 2)'' — Start hinter dem Ende | leeres ''Text'', ''ByteLength() == 0'' | | ''TextFromPchar("")'' | leeres ''Text'', ''IsValid() == 1'' | | ''Find'' ohne Treffer | ''-1'' | | ''TextFromUtf8'' mit ungültigen Bytes | ''Text'' entsteht, ''IsValid() == 0'', Bytes bleiben erhalten | Der letzte Punkt ist der wichtigste: **ungültiges UTF-8 führt nicht zum Fehlschlag.** Wer fremde Daten einliest, muss ''IsValid()'' selbst prüfen — sonst arbeiten ''CodepointCount'' und ''SubstringCp'' auf kaputten Sequenzen weiter. ---- ===== 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-13 — Beispiele gegen ''lyxc 1.0.17K'' verifiziert, Abschnitte zu Speicherverwaltung, Grenzfällen und der Split-Rohform ergänzt. Codebeispiele geprüft: gegen **lyxc 1.2.5C** übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die ''pub fn''-Signaturen in ''aurum/std'' gehalten, 0 Abweichungen).