formats: std/xml tree, reader and writer (step 1 of #78) #429

Closed
opened 2026-10-23 13:28:34 +00:00 by orion · 3 comments
Collaborator

Context

Step 1 of #78 (std/xml). The package's tree, its reader and its writer, with no data binding yet: a document nobody has a type for (ENCODING.md section 8, step 6: Xml().parse(text) answers an XmlNode). torb/s3 needs it for listings and errors (FIRST-PARTY.md gap 24), and the WebDAV driver (#67) needs it for multistatus replies.

Source

  • ROADMAP.md, format versions: XML 1.0 (fifth edition) with namespaces; XML 1.1 not supported.
  • ENCODING.md section 9 and slice 8 (section 14): XmlNode is the tree, no XmlEncode/XmlDecode is ever written.
  • std/toml (std/toml/src/toml.trb, value.trb, parser.trb, writer.trb) is the model for the layout: a format value with options, a tree type, a parser, a writer, a vendored public suite, a docs page.

Design record point

ENCODING.md slice 8 and section 9 ("document formats ... the tree (XmlNode) for documents"); ROADMAP "Which versions a format supports" (XML row). Decided by the loop:

  • Package std/xml (std/xml/project.trb, src/lib.trb re-exporting from xml.trb, node.trb, scanner.trb/parser.trb, writer.trb, error.trb), written in TorbScript, no native.
  • public type Xml { ... } is the format value with options, as Toml is; step 1 has no option the reader needs, so it is Xml() with indent: Int = 0 for the writer only.
  • XmlNode is an ADT with cases for the document, an element (qualified name = namespace URI + local name + prefix, attributes in document order, children), text, CDATA (kept distinct), comment and processing instruction, and the XML declaration's version/encoding/standalone. Namespace prefixes are resolved at read time and kept for writing back.
  • Xml.parse(text): Result<XmlNode, XmlError> and Xml.write(tree): String. The reader decodes the five predefined entities and numeric character references, normalises line ends and attribute values per XML 1.0 section 2.11 and 3.3.3, enforces well-formedness (names, one root, matching tags, unique attributes, namespace constraints, the Char production) and reports line and column in XmlError.
  • A DOCTYPE without an internal subset is read and kept (its external identifier is never fetched). A DOCTYPE with an internal subset is refused with an XmlError that says so, until step 2 reads the entities; nothing is ever silently misread.
  • A read of UTF-8 text only (a String); the writer escapes &, < and (in attributes) ", and round-trips every tree the reader produces to an equal tree.

Acceptance criteria

  • use Xml, XmlNode, XmlError from "std/xml" works; Xml().parse(text) reads XML 1.0 (fifth edition) with namespaces into an XmlNode, and Xml().write(tree) writes it back; for every document the reader accepts, parse(write(parse(text))) equals parse(text).
  • Syntax errors answer an XmlError with line and column (cases: unclosed and mismatched tags, a second root, a duplicate attribute, an undeclared prefix, an illegal character, a bare &, -- in a comment).
  • A DOCTYPE with an internal subset is a clear XmlError; one without is accepted; no external entity or DTD is ever fetched or opened.
  • A vendored public suite passes in std/xml/tests with its notice: the well-formedness and namespace cases of the W3C XML Conformance Test Suite that carry no internal subset (the notice and licence recorded in the test package; if the licence does not allow vendoring, hand-written cases per production of XML 1.0 and Namespaces in XML 1.0 instead, and the PR says so).
  • A page docs/standard-library/xml.md (front matter as toml.md, a trb check example that parses a small S3 ListBucketResult and reads the keys from the tree), listed in docs/standard-library/index.md; torb docs check, docs skill --check and doc --check --no-run std are green; ENCODING.md slice 8 is marked in progress with a short "what the implementation decided" entry for the tree and the DOCTYPE rule.
  • The CONCEPT.md sentence "Document formats (XML, HTML) get their own traits in addition" (around line 2911) is reconciled with ENCODING.md ("No format gets a trait of its own", CONCEPT line 1768) in the same PR, since the design record says the opposite.
  • Gates: tier A green in forge CI (sh tools/gates.sh a); every public declaration has its doc comment; touched files pass torb format --check.

Touches the seed?

No. std/xml is a new package that the compiler does not import and no std name is looked up by string, so no two-commit rule applies. (If a later step makes the compiler import it, that step carries the seed rule; none is planned.) One or two Conventional Commits: feat(std/xml): ... and, if the diff is large, docs(std/xml): ....

Dependencies

  • None blocking. The typed Encode/Decode side (std/encoding) is step 3 of #78, not this step.
  • Unblocks the rest of #78 and, through it, #20 (S3 listings) and #67 (WebDAV).
  • Independent of #79 (std/html): HTML has its own parsing algorithm and tree (WEB.md slice 1), no shared code is planned.
## Context Step 1 of #78 (`std/xml`). The package's tree, its reader and its writer, with no data binding yet: a document nobody has a type for (ENCODING.md section 8, step 6: `Xml().parse(text)` answers an `XmlNode`). `torb/s3` needs it for listings and errors (FIRST-PARTY.md gap 24), and the WebDAV driver (#67) needs it for multistatus replies. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L185): XML 1.0 (fifth edition) with namespaces; XML 1.1 not supported. - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709) and slice 8 (section 14): `XmlNode` is the tree, no `XmlEncode`/`XmlDecode` is ever written. - `std/toml` (`std/toml/src/toml.trb`, `value.trb`, `parser.trb`, `writer.trb`) is the model for the layout: a format value with options, a tree type, a parser, a writer, a vendored public suite, a docs page. ## Design record point ENCODING.md slice 8 and section 9 ("document formats ... the tree (`XmlNode`) for documents"); ROADMAP "Which versions a format supports" (XML row). Decided by the loop: - Package `std/xml` (`std/xml/project.trb`, `src/lib.trb` re-exporting from `xml.trb`, `node.trb`, `scanner.trb`/`parser.trb`, `writer.trb`, `error.trb`), written in TorbScript, no `native`. - `public type Xml { ... }` is the format value with options, as `Toml` is; step 1 has no option the reader needs, so it is `Xml()` with `indent: Int = 0` for the writer only. - `XmlNode` is an ADT with cases for the document, an element (qualified name = namespace URI + local name + prefix, attributes in document order, children), text, CDATA (kept distinct), comment and processing instruction, and the XML declaration's `version`/`encoding`/`standalone`. Namespace prefixes are resolved at read time and kept for writing back. - `Xml.parse(text): Result<XmlNode, XmlError>` and `Xml.write(tree): String`. The reader decodes the five predefined entities and numeric character references, normalises line ends and attribute values per XML 1.0 section 2.11 and 3.3.3, enforces well-formedness (names, one root, matching tags, unique attributes, namespace constraints, the `Char` production) and reports line and column in `XmlError`. - A `DOCTYPE` without an internal subset is read and kept (its external identifier is never fetched). A `DOCTYPE` with an internal subset is refused with an `XmlError` that says so, until step 2 reads the entities; nothing is ever silently misread. - A read of UTF-8 text only (a `String`); the writer escapes `&`, `<` and (in attributes) `"`, and round-trips every tree the reader produces to an equal tree. ## Acceptance criteria - [ ] `use Xml, XmlNode, XmlError from "std/xml"` works; `Xml().parse(text)` reads XML 1.0 (fifth edition) with namespaces into an `XmlNode`, and `Xml().write(tree)` writes it back; for every document the reader accepts, `parse(write(parse(text)))` equals `parse(text)`. - [ ] Syntax errors answer an `XmlError` with line and column (cases: unclosed and mismatched tags, a second root, a duplicate attribute, an undeclared prefix, an illegal character, a bare `&`, `--` in a comment). - [ ] A `DOCTYPE` with an internal subset is a clear `XmlError`; one without is accepted; no external entity or DTD is ever fetched or opened. - [ ] A vendored public suite passes in `std/xml/tests` with its notice: the well-formedness and namespace cases of the W3C XML Conformance Test Suite that carry no internal subset (the notice and licence recorded in the test package; if the licence does not allow vendoring, hand-written cases per production of XML 1.0 and Namespaces in XML 1.0 instead, and the PR says so). - [ ] A page `docs/standard-library/xml.md` (front matter as `toml.md`, a `trb check` example that parses a small S3 `ListBucketResult` and reads the keys from the tree), listed in `docs/standard-library/index.md`; `torb docs check`, `docs skill --check` and `doc --check --no-run std` are green; ENCODING.md slice 8 is marked in progress with a short "what the implementation decided" entry for the tree and the DOCTYPE rule. - [ ] The CONCEPT.md sentence "Document formats (XML, HTML) get their own traits in addition" (around line 2911) is reconciled with ENCODING.md ("No format gets a trait of its own", CONCEPT line 1768) in the same PR, since the design record says the opposite. - [ ] Gates: tier A green in forge CI (`sh tools/gates.sh a`); every public declaration has its doc comment; touched files pass `torb format --check`. ## Touches the seed? No. `std/xml` is a new package that the compiler does not import and no std name is looked up by string, so no two-commit rule applies. (If a later step makes the compiler import it, that step carries the seed rule; none is planned.) One or two Conventional Commits: `feat(std/xml): ...` and, if the diff is large, `docs(std/xml): ...`. ## Dependencies - None blocking. The typed `Encode`/`Decode` side (`std/encoding`) is step 3 of #78, not this step. - Unblocks the rest of #78 and, through it, #20 (S3 listings) and #67 (WebDAV). - Independent of #79 (`std/html`): HTML has its own parsing algorithm and tree (WEB.md slice 1), no shared code is planned.
Author
Collaborator

[loop:torbscript] decided by the loop: first step of epic #78, sized as one PR (tree, reader, writer, docs page, vendored suite; about the size of std/toml). Naming and layout follow std/toml and ENCODING.md slice 8; no seed touch because the compiler does not import std/xml. Set ready.

[loop:torbscript] decided by the loop: first step of epic #78, sized as one PR (tree, reader, writer, docs page, vendored suite; about the size of std/toml). Naming and layout follow std/toml and ENCODING.md slice 8; no seed touch because the compiler does not import std/xml. Set ready.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T19:38Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T19:38Z
Author
Collaborator

[loop:torbscript] implement: std/xml tree, reader and writer (Xml.parse/write, XmlError with line and column, DOCTYPE rule with nothing external opened, 331 hand-written conformance and unit cases because the W3C suite cannot be redistributed, docs page, index, ENCODING slice 8, CONCEPT sentence reconciled), PR #431, CI green (round 2 after a pdisc sidecar fix; tier A, tier B, gates, pdisc all success), next: review. Tokens: roughly 1 context window plus continuation, not tracked more precisely.

[loop:torbscript] implement: std/xml tree, reader and writer (Xml.parse/write, XmlError with line and column, DOCTYPE rule with nothing external opened, 331 hand-written conformance and unit cases because the W3C suite cannot be redistributed, docs page, index, ENCODING slice 8, CONCEPT sentence reconciled), PR https://git.torb.dev/torbscript/language/pulls/431, CI green (round 2 after a pdisc sidecar fix; tier A, tier B, gates, pdisc all success), next: review. Tokens: roughly 1 context window plus continuation, not tracked more precisely.
argus closed this issue 2026-10-23 14:51:35 +00:00
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#429
No description provided.