html5-parser
A pure-Rust WHATWG HTML5 tokenizer and tree-construction implementation.
Scope
Deliberately staged, not built as a fully generic library from day one:
- Step 1: only what
html-conform(a sibling project) actually needs to replace its current HTML5 parsing dependency — a tokenizer and tree-construction implementation whose output can directly feedhtml-conform'ssrc/infoset.rs::normalize(), including per-node source positions (its current dependency has none). No generic public API commitment yet. - Step 2: once step 1 is proven against
html-conform's real usage, extract the generic,html-conform-agnostic part (a reusable WHATWG HTML5 tokenizer/tree-builder) as this crate's public API.
Architecture (working title)
HTML input (string) → tokenizer (WHATWG tokenizer state machine)
→ tree_builder (WHATWG tree-construction algorithm,
incl. foreign content / SVG / MathML)
→ document (element/text/comment tree with positions)
Normative basis
Implementation decisions are derived from the
WHATWG HTML parsing specification.
Other implementations (e.g. html5ever) are explanatory references only,
not a source to copy code from.
Status
The tokenizer (§13.2.5) and tree-construction algorithm (§13.2.6 — all insertion modes, adoption agency, foster parenting, foreign content) are implemented and wired end to end.
pub fn parse(input: &str) -> Document and the read-only tree types
(Document, NodeId, NodeKind, Node, Attribute, Position,
Children) are public — just enough to walk the resulting tree and read
each node's kind and source position, matching what html-conform's
src/infoset.rs::normalize() needs. Tokenizer/TreeBuilder and
everything else stay crate-internal (see Scope above — no commitment to
a generic public API yet, that's Step 2); not published to crates.io.
Known limitations
Three gaps were tracked here, evidence-based (per Step 1's scope, above)
rather than oversights, closed out one by one ahead of a first
crates.io publish (see plan/DECISIONS.md), independently of whether
html-conform itself ends up needing each one — all three are now
done:
Frameset-related insertion modes not implemented— done: "in frameset"/"after frameset"/"after after frameset" (§13.2.6.4.18-21) are implemented, including<frameset>correctly replacing (rather than nesting under)<body>per §13.2.6.4.7/.4.6's real rules. Seeplan/04-frameset.md.— done (the classic content model):<template>treated as an ordinary element<template>gets a real, separateNodeKind::DocumentFragment"template contents" (§13.2.6.4.4/.16, the stack of template insertion modes, the active-formatting-elements marker). Still not implemented within this: two much newer, still-evolving sub-features layered onto<template>in the current spec, neither exercised by the html5lib-tests corpus: declarative shadow DOM (shadowrootmodeand friends) and content patching (theforattribute) — both would require modeling shadow roots/custom element registries this crate has no other use for. Seeplan/05-template.md.— done, with a scope note: this isn't actually a tree-construction (§13.2.6) feature at all — it's the<selectedcontent>treated as an ordinary element<option>element's own HTML-parser-specific hook (form-elements.html §4.10.10/.17, "maybe clone an option into selectedcontent", run when anoptionis popped off the stack of open elements), simplified to this crate's parse-time-only, non-scripted needs (no live selectedness mutation, nomultiple<select>support, a practically- rather than fully-generally-scoped "list of options"/disabled-flag walk). Seeplan/06-selectedcontent.md.
Testing
Besides hand-written unit/end-to-end tests (src/tokenizer.rs,
src/tree_builder.rs, src/lib.rs, src/document.rs), cargo test
also runs tests/html5lib_conformance.rs: every applicable case
(full-document, non-fragment, non-scripting — see
tests/html5lib-tests/README.md) from the vendored
html5lib-tests
tree-construction corpus — currently all 1,726 applicable cases pass
(100%). tests/html5lib_known_failures.txt is currently empty (kept,
rather than deleted, as the harness's regression-tracking mechanism —
see that file's header — for whenever a future corpus refresh or code
change introduces a real one).
License
MIT — see LICENSE.