std.html
Werkzeuge für den Umgang mit HTML als Text: Sonderzeichen maskieren, Tags erkennen und entfernen, Verschachtelung grob prüfen, Leerzeichen für Formularparameter kodieren. Die Unit ist kein Parser — sie arbeitet zeichenweise über den Eingabetext und hat kein Dokumentmodell.
→ std.xml · std.string · Standard Library
Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.
Einsatz: Template-Ausgabe, HTML-Erzeugung, Textextraktion aus Webseiten, SEO- und Feed-Auswertung.
Nicht für Sicherheitsentscheidungen verwenden. Frühere Fassungen dieser Seite nannten „HTML-Sanitisierung (XSS-Schutz)„ als Einsatzbereich — dafür taugt die Unit nicht:
*StripTagsentfernt die Tags, nicht deren Inhalt: aus<script>alert(1)</script>wirdalert(1).
*Unescapelöst derzeit fast keine Entity auf (siehe unten), eine darauf gestützte Prüfung sieht also etwas anderes als der Browser.
* Es gibt keine Attributprüfung:onclick,javascript:-URLs und Data-URLs bleiben unangetastet, solange sie in einem Tag stehen, das entfernt wird — der Text dahinter aber bleibt.
Zum Schutz vor XSS istEscapebeim Ausgeben das richtige Mittel, nicht das Filtern beim Einlesen.
Maskieren
| Signatur | Beschreibung |
|---|---|
Escape(input: pchar, output: pchar): int64 | Ersetzt &, <, >, “, ' und Byte 160 durch Entities. Rückgabe: Länge ohne Nullbyte. |
Unescape(input: pchar, output: pchar): int64 | Soll Entities zurückwandeln — funktioniert derzeit nur für ". |
NeedsEscape(s: pchar): bool | Meldet, ob überhaupt ein zu maskierendes Zeichen vorkommt. |
| Zeichen | Wird zu |
|---|---|
& | & |
< | < |
> | > |
„ | " |
' | ' (numerisch, nicht ') |
| Byte 160 | — problematisch, siehe Kasten |
Der Ausgabepuffer gehört dem Aufrufer. Sechs Byte je Eingabezeichen sind sicher (" und sind die längsten Ersetzungen), plus ein Byte für den Abschluss.
import std.html;
import std.alloc;
import std.string;
fn main(): int64 {
var o: pchar := alloc(512) as pchar;
Escape("<a href=\"x\">Tom & Jerry's</a>"c, o);
PrintLn(StrConcat("Escape: ", o));
var back: pchar := alloc(512) as pchar;
Unescape(o, back);
PrintLn(StrConcat("Rueckweg: ", back));
// Einzelne Entities durch den Dekoder
Unescape("& < > " ' ' A &unknown;"c, o);
PrintLn(StrConcat("Entities: ", o));
// UTF-8: 'à' ist C3 A0 - das zweite Byte ist 160
Escape("Caf\xc3\xa0 Grand-M\xc3\xa8re"c, o);
PrintLn(StrConcat("UTF-8: ", o));
return 0;
}
Escape: <a href="x">Tom & Jerry's</a>
Rueckweg: <a href="x">Tom & Jerry's</a>
Entities: & < > " ' ' A &unknown;
UTF-8: CafM-C Grand-MM-CM-(re
Die letzte Zeile ist durch cat -v sichtbar gemacht: M-C steht für das Byte 0xC3. Aus à (C3 A0) wurde C3 + — ein halbes UTF-8-Zeichen gefolgt von einer Entity. è (C3 A8) blieb dagegen unversehrt, weil nur 0xA0 geprüft wird.
Unescapebeherrscht seit 1.0.20F den vollen Umfang (#1418): benannte Entities und numerische Angaben. Gemessen wird<a> & A zu<a> & Asamt geschütztem Leerzeichen aufgelöst.
Escapelässt UTF-8 unangetastet (#1419 behoben, 1.0.21A). Die frühere Latin-1-Regel „Byte 160 → “ zerlegte Zeichen wieà(C3 A0); nachgemessen bleibtà Grüßejetzt unverändert.
Tags erkennen
| Signatur | Beschreibung |
|---|---|
GetTagName(html: pchar, output: pchar): int64 | Liest den Tag-Namen ab <; funktioniert auch für </tag> und <tag/>. 0, wenn das erste Zeichen kein < ist. |
IsClosingTag(html: pchar): bool | true bei </…>. |
IsSelfClosing(html: pchar): bool | true bei <…/> — nicht bei Void-Elementen wie <br>. |
HasTags(s: pchar): bool | Sucht ein < mit passendem > irgendwo im Text. |
ValidateBalance(html: pchar): int64 | Bilanz aus öffnenden und schließenden Tags: 0 = ausgeglichen, positiv = offen, negativ = zu viele schließende. |
import std.html;
import std.alloc;
import std.string;
fn J(b: bool): pchar { if (b) { return "ja"; } return "nein"; }
fn main(): int64 {
var o: pchar := alloc(512) as pchar;
// Tag-Analyse
GetTagName("<div class=\"x\">"c, o); PrintLn(StrConcat("GetTagName('<div class=..>'): ", o));
GetTagName("</span>"c, o); PrintLn(StrConcat("GetTagName('</span>'): ", o));
GetTagName("<br/>"c, o); PrintLn(StrConcat("GetTagName('<br/>'): ", o));
PrintLn(StrConcat("IsClosingTag('</p>'): ", J(IsClosingTag("</p>"c))));
PrintLn(StrConcat("IsSelfClosing('<br/>'):", J(IsSelfClosing("<br/>"c))));
PrintLn(StrConcat("IsSelfClosing('<br>'): ", J(IsSelfClosing("<br>"c))));
PrintLn(StrConcat("HasTags('a < b'): ", J(HasTags("a < b"c))));
PrintLn(StrConcat("NeedsEscape('a & b'): ", J(NeedsEscape("a & b"c))));
// Verschachtelung
PrintLn(StrConcat("Balance('<p><b>x</b></p>'): ", IntToStr(ValidateBalance("<p><b>x</b></p>"c))));
PrintLn(StrConcat("Balance('<p><b>x</p>'): ", IntToStr(ValidateBalance("<p><b>x</p>"c))));
PrintLn(StrConcat("Balance('<p>a<br>b</p>'): ", IntToStr(ValidateBalance("<p>a<br>b</p>"c))));
PrintLn(StrConcat("Balance('<img src=x/>'): ", IntToStr(ValidateBalance("<img src=x/>"c))));
PrintLn(StrConcat("Balance('5 < 7 und 9 > 2'): ", IntToStr(ValidateBalance("5 < 7 und 9 > 2"c))));
// StripTags
StripTags("<p>Hallo <b>Welt</b></p>"c, o); PrintLn(StrConcat("Strip normal: ", o));
StripTags("<script>alert(1)</script>"c, o); PrintLn(StrConcat("Strip script: ", o));
StripTags("5 < 7 und 9 > 2"c, o); PrintLn(StrConcat("Strip Text: ", o));
StripTags("<p onclick=\"evil()\">Text</p>"c, o); PrintLn(StrConcat("Strip Attribut:", o));
// URL-Leerzeichen
EncodeSpaces("a b c"c, o); PrintLn(StrConcat("EncodeSpaces('a b c'): ", o));
DecodeSpaces(o, o); PrintLn(StrConcat("DecodeSpaces zurueck: ", o));
return 0;
}
GetTagName('<div class=..>'): div
GetTagName('</span>'): span
GetTagName('<br/>'): br
IsClosingTag('</p>'): ja
IsSelfClosing('<br/>'):ja
IsSelfClosing('<br>'): nein
HasTags('a < b'): ja
NeedsEscape('a & b'): ja
Balance('<p><b>x</b></p>'): 0
Balance('<p><b>x</p>'): 1
Balance('<p>a<br>b</p>'): 1
Balance('<img src=x/>'): 1
Balance('5 < 7 und 9 > 2'): 1
Strip normal: Hallo Welt
Strip script: alert(1)
Strip Text: 5 2
Strip Attribut:Text
EncodeSpaces('a b c'): a+b+c
DecodeSpaces zurueck: a b c
ValidateBalancerechnet naiv. Jedes<zählt als geöffnet, jedes</als geschlossen — unabhängig davon, ob überhaupt ein Tag vorliegt:
* Void-Elemente (<br>,<img>,<meta>,<input>,<hr>) gelten als offen; gültiges HTML bekommt eine Bilanz ungleich null.
* Selbstschließende Tags (<img/>) ebenfalls, obwohlIsSelfClosingsie erkennen könnte.
* Ein<im Fließtext (5 < 7) zählt mit.
Brauchbar ist die Funktion damit nur für streng gepaartes, XML-artiges Markup ohne Void-Elemente (#1420).
Tags entfernen
StripTags kopiert alles, was außerhalb von <…> steht. Das Verfahren ist bewusst einfach — und hat zwei Folgen, die man kennen muss:
- Der Inhalt bleibt. Ein
<script>-Block verliert seine Tags, der Code darin steht anschließend im Text. - Ein
<im Text frisst mit. Aus5 < 7 und 9 > 2wird5 2— alles zwischen<und dem nächsten>gilt als Tag. Wer Text mit Vergleichszeichen verarbeitet, maskiert ihn vorher mitEscape.
Für die Textextraktion aus wohlgeformtem HTML — der eigentliche Zweck — arbeitet die Funktion zuverlässig.
Leerzeichen für Formularparameter
| Signatur | Beschreibung |
|---|---|
EncodeSpaces(input: pchar, output: pchar): int64 | Ersetzt Leerzeichen durch +. |
DecodeSpaces(input: pchar, output: pchar): int64 | Ersetzt + durch Leerzeichen. |
Das ist+, nicht%20. Frühere Fassungen dieser Seite sprachen von „%20für URLs„ — tatsächlich erzeugtEncodeSpacesdie Form ausapplication/x-www-form-urlencoded, wie sie in Formulardaten und Abfrageparametern üblich ist.DecodeSpaceskennt umgekehrt nur+und lässt%20unverändert stehen.
Beide Funktionen behandeln ausschließlich das Leerzeichen.&,=,?,#und Nicht-ASCII bleiben unangetastet — für vollständige URL-Kodierung iststd.net.httpzuständig.
Fallstricke
- Ausgabepuffer selbst anlegen und großzügig bemessen — keine Funktion prüft die Zielgröße. Für
Escapesind sechs Byte je Eingabezeichen sicher. Unescapelöst benannte und numerische Entities auf (#1418 behoben).Escapelässt UTF-8 unangetastet (#1419 behoben).- Doppelte Maskierung merkt niemand:
Escape(Escape(x))ergibt&lt;statt<. Nur einmal maskieren, unmittelbar vor der Ausgabe. - Kein Dokumentmodell.
GetTagNameund Verwandte erwarten einen Text, der am<beginnt; einen Tag-Iterator gibt es nicht. Wer Struktur braucht, nimmt std.xml. - Kommentare, CDATA und Skriptinhalte kennt die Unit nicht —
<!– a > b –>endet fürStripTagsbeim ersten>. - Die exportierten Entity-Konstanten sind unit-privat; von außen sichtbar sind nur die elf Funktionen.
Weiterführend: std.xml — Parser mit Dokumentmodell · std.string · std.net.http · Standard Library
Quelle: std/html.lyx (459 Zeilen) · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne
Letzte Aktualisierung: 2026-08-13 — Seite gegen std/html.lyx überarbeitet: leere Konstanten-Tabelle entfernt, zwei lauffähige Beispiele mit echter Ausgabe, Entity-Tabelle, Fallstricke. Die Nennung als Mittel zur „HTML-Sanitisierung (XSS-Schutz)“ ist gestrichen — StripTags entfernt Tags, nicht deren Inhalt. Belegt und als Issue erfasst: Unescape erkennt nur " (#1418), Escape zerstört UTF-8-Zeichen mit Folgebyte 0xA0 (#1419), ValidateBalance zählt Void-Elemente und Text-Kleinerzeichen mit (#1420). EncodeSpaces erzeugt +, nicht %20. Geprüft mit lyxc 1.0.21A.
