formats: std/html documents (epic) #79

Open
opened 2026-10-05 21:07:17 +00:00 by orion · 6 comments
Collaborator

Context

std/html as a document format: the WHATWG parsing algorithm reads any HTML, old or broken, into a tree, and the tree can be written back. This is an epic: the standard's tokenizer alone has 80 states and a 2231-name table, tree construction adds 23 insertion modes, the adoption agency algorithm, foster parenting, foreign content and templates. That is more than one PR. Steps become issues just in time, one at a time. The builder side of the package (Html<Message>, Markup<Message>, escaping) is #93, not this epic.

Source

  • ROADMAP.md, format versions: HTML is the parsing algorithm of the WHATWG standard, which also reads older documents; no separate HTML 4 or XHTML parser (XHTML is XML, std/xml).
  • ENCODING.md section 9: a page is not data binding; it gets "a tree type as a value in its own package and ordinary functions over it". So there is no HtmlEncode/HtmlDecode and no mapping DSL. This corrects the old acceptance line "the package on Encode and Decode", which came from the planning list and contradicts the design record.
  • WEB.md section 4.7 (Html.sanitized(markup, policy) "parses and keeps an allowlist") and section 4.3 (the checker tokenizes static parts with std/html's tokenizer): the two consumers of this epic.
  • std/xml and std/toml are the layout models (format value or entry type, tree, parser, writer, vendored suite, docs page).

Design record point

ROADMAP "Which versions a format supports" (HTML row); ENCODING.md section 9; WEB.md decision D13 (three packages, std/html has no HTTP in it and is useful alone). Decided by the loop:

  • One package std/html, shared with #93. The document side uses HtmlToken, HtmlTokenizer, HtmlParseError, HtmlNode, HtmlDocument, HtmlFragment; no type named Html is introduced here, because Html<Message> is #93's (WEB.md section 4). Name clash avoided on purpose: the entry point is HtmlDocument.parse(text), not Html().parse(text) as Xml().parse(text) would suggest.
  • Reading HTML never fails (the standard defines a result for every input): HtmlDocument.parse answers the tree with the list of parse errors beside it, not a Result.
  • Input is a String; detecting the encoding of bytes is not in this epic.
  • Not supported (ROADMAP): a separate HTML 4 or XHTML parser. Not in this epic: scripting (scripting flag stays off, so <noscript> content is parsed as markup), CSS or DOM queries beyond walking the tree, Html.sanitized itself (#93, after step 4 here).
  • Touches the seed? No for the whole epic: the compiler does not import std/html and no std name is looked up by string. A later step of another epic that makes the checker import the tokenizer (#108) carries the two-commit rule itself.

Steps

  • 1. Tokenizer and character references: the package std/html, HtmlToken, HtmlTokenizer, HtmlParseError, the 2231-name table, html5lib-tests' tokenizer suite, docs page #439
  • 2. Tree construction for documents: HtmlNode, HtmlDocument.parse(text), all insertion modes for HTML elements including the adoption agency algorithm, foster parenting, quirks mode and implied elements; the html5lib-tests tree-construction cases that need no foreign content, template or fragment
  • 3. Foreign content (SVG and MathML integration points), <template>, fragment parsing HtmlFragment.parse(context, text); the rest of the tree-construction suite passes
  • 4. Serializer: the standard's serialization algorithm as HtmlNode.toHtml(), text and attribute escaping, void elements; parse(toHtml(parse(text))) equals parse(text) over the suite; small tree helpers (children, attribute lookup, text content); docs page complete

Dependencies

  • Needs: nothing unmerged. std/encoding is not used (no data binding).
  • Shares the package directory with #93 (std/html markup, WEB slice 1): independent code, lib.trb and the docs page are the only shared files; whichever lands second adds its lines. Neither blocks the other.
  • Unblocks: Html.sanitized(markup, policy) of #93 (needs steps 2 to 4), the checker's tokenizer use in #108 (needs step 1).
  • Independent of #78 (std/xml, its own grammar and tree) and #80.

Moved from the planning lists of the docs; the design record stays the specification.

## Context `std/html` as a document format: the WHATWG parsing algorithm reads any HTML, old or broken, into a tree, and the tree can be written back. This is an epic: the standard's tokenizer alone has 80 states and a 2231-name table, tree construction adds 23 insertion modes, the adoption agency algorithm, foster parenting, foreign content and templates. That is more than one PR. Steps become issues just in time, one at a time. The builder side of the package (`Html<Message>`, `Markup<Message>`, escaping) is #93, not this epic. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L224): HTML is the parsing algorithm of the WHATWG standard, which also reads older documents; no separate HTML 4 or XHTML parser (XHTML is XML, `std/xml`). - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709): a page is not data binding; it gets "a tree type as a value in its own package and ordinary functions over it". So there is no `HtmlEncode`/`HtmlDecode` and no mapping DSL. This corrects the old acceptance line "the package on `Encode` and `Decode`", which came from the planning list and contradicts the design record. - [WEB.md section 4.7](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/WEB.md) (`Html.sanitized(markup, policy)` "parses and keeps an allowlist") and section 4.3 (the checker tokenizes static parts with `std/html`'s tokenizer): the two consumers of this epic. - `std/xml` and `std/toml` are the layout models (format value or entry type, tree, parser, writer, vendored suite, docs page). ## Design record point ROADMAP "Which versions a format supports" (HTML row); ENCODING.md section 9; WEB.md decision D13 (three packages, `std/html` has no HTTP in it and is useful alone). Decided by the loop: - One package `std/html`, shared with #93. The document side uses `HtmlToken`, `HtmlTokenizer`, `HtmlParseError`, `HtmlNode`, `HtmlDocument`, `HtmlFragment`; no type named `Html` is introduced here, because `Html<Message>` is #93's (WEB.md section 4). Name clash avoided on purpose: the entry point is `HtmlDocument.parse(text)`, not `Html().parse(text)` as `Xml().parse(text)` would suggest. - Reading HTML never fails (the standard defines a result for every input): `HtmlDocument.parse` answers the tree with the list of parse errors beside it, not a `Result`. - Input is a `String`; detecting the encoding of bytes is not in this epic. - Not supported (ROADMAP): a separate HTML 4 or XHTML parser. Not in this epic: scripting (`scripting` flag stays off, so `<noscript>` content is parsed as markup), CSS or DOM queries beyond walking the tree, `Html.sanitized` itself (#93, after step 4 here). - Touches the seed? No for the whole epic: the compiler does not import `std/html` and no std name is looked up by string. A later step of another epic that makes the checker import the tokenizer (#108) carries the two-commit rule itself. ## Steps - [x] 1. Tokenizer and character references: the package `std/html`, `HtmlToken`, `HtmlTokenizer`, `HtmlParseError`, the 2231-name table, html5lib-tests' tokenizer suite, docs page #439 - [ ] 2. Tree construction for documents: `HtmlNode`, `HtmlDocument.parse(text)`, all insertion modes for HTML elements including the adoption agency algorithm, foster parenting, quirks mode and implied elements; the html5lib-tests tree-construction cases that need no foreign content, template or fragment - [ ] 3. Foreign content (SVG and MathML integration points), `<template>`, fragment parsing `HtmlFragment.parse(context, text)`; the rest of the tree-construction suite passes - [ ] 4. Serializer: the standard's serialization algorithm as `HtmlNode.toHtml()`, text and attribute escaping, void elements; `parse(toHtml(parse(text)))` equals `parse(text)` over the suite; small tree helpers (children, attribute lookup, text content); docs page complete ## Dependencies - Needs: nothing unmerged. `std/encoding` is not used (no data binding). - Shares the package directory with #93 (`std/html` markup, WEB slice 1): independent code, `lib.trb` and the docs page are the only shared files; whichever lands second adds its lines. Neither blocks the other. - Unblocks: `Html.sanitized(markup, policy)` of #93 (needs steps 2 to 4), the checker's tokenizer use in #108 (needs step 1). - Independent of #78 (`std/xml`, its own grammar and tree) and #80. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), sources ROADMAP.md format versions and ENCODING.md section 9, not a duplicate, no collision with a design record. Shares the package with WEB.md slice 1; refine checks that overlap. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), sources ROADMAP.md format versions and ENCODING.md section 9, not a duplicate, no collision with a design record. Shares the package with WEB.md slice 1; refine checks that overlap. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T18:08Z

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T18:08Z
orion changed title from formats: std/html documents to formats: std/html documents (epic) 2026-10-23 16:10:37 +00:00
Author
Collaborator

[loop:torbscript] decided by the loop: #79 is too big for one PR, so it is an epic and stays without ready. The WHATWG tokenizer (80 states, 2231 named references), tree construction (23 insertion modes, adoption agency, foster parenting), foreign content with templates and fragments, and the serializer are four reviewable pieces, in the order each needs the previous. Step 1 is filed as #439 (keyword search of open issues for html, tokenizer, entity, whatwg, sanitiz found only #93 and #108 as neighbours, none a duplicate) and is ready; steps 2 to 4 are filed just in time.

Decisions:

  • Overlap with #93: both use the package std/html, but #93 is the builder (Html<Message>, Markup<Message>, escaping) and this is the reader (tokens, HtmlNode, HtmlDocument). No code is shared, so neither blocks the other; to avoid a name clash this epic introduces no type called Html and the entry point is HtmlDocument.parse, not Html().parse. The second to land adds its lines to lib.trb and the docs page. Html.sanitized of #93 needs steps 2 to 4 here.
  • The old acceptance line "the package on Encode and Decode" is dropped: ENCODING.md section 9 says a page is not data binding and gets a tree type with ordinary functions (as std/xml has no XmlEncode).
  • Reading never fails (the standard defines a result for every input), so parse answers the tree plus parse errors, not a Result.
  • No seed touch: the compiler does not import std/html. #108 (checker uses the tokenizer) carries the two-commit rule itself.
  • Public suite: html5lib-tests (MIT), vendored with a notice; hand-written cases if the licence check at vendoring time says otherwise, as for std/xml.
[loop:torbscript] decided by the loop: #79 is too big for one PR, so it is an epic and stays without `ready`. The WHATWG tokenizer (80 states, 2231 named references), tree construction (23 insertion modes, adoption agency, foster parenting), foreign content with templates and fragments, and the serializer are four reviewable pieces, in the order each needs the previous. Step 1 is filed as #439 (keyword search of open issues for html, tokenizer, entity, whatwg, sanitiz found only #93 and #108 as neighbours, none a duplicate) and is `ready`; steps 2 to 4 are filed just in time. Decisions: - Overlap with #93: both use the package `std/html`, but #93 is the builder (`Html<Message>`, `Markup<Message>`, escaping) and this is the reader (tokens, `HtmlNode`, `HtmlDocument`). No code is shared, so neither blocks the other; to avoid a name clash this epic introduces no type called `Html` and the entry point is `HtmlDocument.parse`, not `Html().parse`. The second to land adds its lines to `lib.trb` and the docs page. `Html.sanitized` of #93 needs steps 2 to 4 here. - The old acceptance line "the package on `Encode` and `Decode`" is dropped: ENCODING.md section 9 says a page is not data binding and gets a tree type with ordinary functions (as `std/xml` has no `XmlEncode`). - Reading never fails (the standard defines a result for every input), so parse answers the tree plus parse errors, not a `Result`. - No seed touch: the compiler does not import `std/html`. #108 (checker uses the tokenizer) carries the two-commit rule itself. - Public suite: html5lib-tests (MIT), vendored with a notice; hand-written cases if the licence check at vendoring time says otherwise, as for `std/xml`.
Author
Collaborator

[loop:torbscript] refine: released the claim of session:loop-torbscript-20261023. #79 is an epic (checklist of 4 steps in the body), step 1 filed as #439 with label ready. Nothing was written to the repo. Worker usage: about 90k tokens (sonnet).
next: waiting (epic waits on its steps)

[loop:torbscript] refine: released the claim of session:loop-torbscript-20261023. #79 is an epic (checklist of 4 steps in the body), step 1 filed as #439 with label `ready`. Nothing was written to the repo. Worker usage: about 90k tokens (sonnet). next: waiting (epic waits on its steps)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:theorben until 2026-10-30T12:02Z

[loop:torbscript] claim refine by session:loop-torbscript-20261030 @anthropic:theorben until 2026-10-30T12:02Z
Author
Collaborator

[loop:torbscript] refine: step 1 (#439, PR #443) is on main (std/html tokenizer and html5lib tokenizer suite present), so it is ticked in the checklist. Decided by the loop: step 2 (23 insertion modes, adoption agency, foster parenting, quirks, a new vendored suite) is more than one PR, so only its first slice is filed: #688 (HtmlNode, HtmlDocument.parse, document skeleton and plain content modes, vendored tree-construction suite with a named skip list). Keyword search of open and closed issues for html, tree construction found no duplicate (#650 is the book page, #93 and #108 are neighbours). The rest of step 2 (formatting elements and adoption agency, tables and foster parenting) is filed just in time after #688 lands. No seed touch.
released the claim of session:loop-torbscript-20261030
next: waiting (epic waits on its steps)

[loop:torbscript] refine: step 1 (#439, PR #443) is on `main` (std/html tokenizer and html5lib tokenizer suite present), so it is ticked in the checklist. Decided by the loop: step 2 (23 insertion modes, adoption agency, foster parenting, quirks, a new vendored suite) is more than one PR, so only its first slice is filed: https://git.torb.dev/torbscript/language/issues/688 (`HtmlNode`, `HtmlDocument.parse`, document skeleton and plain content modes, vendored tree-construction suite with a named skip list). Keyword search of open and closed issues for html, tree construction found no duplicate (#650 is the book page, #93 and #108 are neighbours). The rest of step 2 (formatting elements and adoption agency, tables and foster parenting) is filed just in time after #688 lands. No seed touch. released the claim of session:loop-torbscript-20261030 next: waiting (epic waits on its steps)
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#79
No description provided.