Skip to content

About

A streaming HTML rewriter for Zig

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

zig-html-rewriter

A streaming HTML rewriter for Zig — a pure-Zig port of Cloudflare's lol-html, the engine behind the HTMLRewriter API in Cloudflare Workers and Bun. Built for cottontail, where it backs the HTMLRewriter API without a Rust toolchain or C bindings in the build.

Parse and transform HTML as it streams through, with CSS-selector-driven handlers and byte-preserving output: content you don't touch passes through byte-identical, at any chunk size, without buffering the document or building a DOM.

const html_rewriter = @import("html_rewriter");

fn onElement(_: ?*anyopaque, el: *html_rewriter.units.element.Element) anyerror!void {
    try el.setAttribute("data-seen", "1");
    try el.prepend("<!-- hi -->", .html);
}

const out = try html_rewriter.rewriteStr(allocator, "<div class=x>hello</div>", .{
    .element_content_handlers = &.{.{
        .selector = "div.x",
        .handlers = .{ .element = .{ .ctx = null, .func = &onElement } },
    }},
});
// streaming: html_rewriter.HtmlRewriter.init(...) + write(chunk) / end()

Compatibility

Behavior is ported and verified; internals are idiomatic Zig rather than transliterated Rust. Output parity with upstream is enforced by CI-run suites (all green at every chunk size, including 1-byte chunks):

  • html5lib tokenizer conformance — 7,028 cases
  • Parser feedback (foreign content / text-mode switching, derived from html5lib tree-construction, expected tokens computed by html5ever) — 1,811 cases, including strict-mode ambiguity bailouts
  • Selector parse accept/reject parity — exact match with upstream's supported selector grammar on all 346 fixture selectors
  • Upstream functional fixtures — selector_matching (784) and element_content_replacement (784)
  • Differential testing — 2,544 rewrite runs compared byte-for-byte against the original Rust implementation across an operation × selector × document matrix

Scope: UTF-8 only (upstream only supports ASCII-compatible encodings; this port drops the encoding layer entirely) and no C API — it's a Zig module. See COMPAT.md for the API surface and known gaps, and DESIGN.md for architecture and test methodology.

Build & test

Zig 0.16. In-tree development uses the sibling cottontail repo's vendored toolchain:

../cottontail/vendors/zig/zig build test                 # everything
../cottontail/vendors/zig/zig build test -Dchunk-size=1  # boundary stress

Test data

tests/data/ vendors the upstream test corpus (html5lib-tests, selector fixtures, regression inputs). tests/data/generated/ is produced by tools/oracle (Rust; build-time only) and committed, which keeps the Zig test suite free of any Rust dependency:

cd tools/oracle
cargo run --release -- ../../tests/data ../../tests/data/generated
cargo run --release --bin differential -- ../../tests/data ../../tests/data/generated

tests/upstream-reference/ keeps upstream's Rust harness sources for reference; they are not built.

License & attribution

BSD 3-Clause, same license as lol-html — see LICENSE.

This project is a port of lol-html (© 2019 Cloudflare, Inc.) and derives its design and observable behavior from it; it also redistributes test material from lol-html, html5lib-tests, and the W3C CSS3 Selectors test suite. See THIRD_PARTY_NOTICES.

About

A streaming HTML rewriter for Zig

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages