BarrierLab

html-conform

HTML5 conformance checking without a JVM, including the six parsers underneath.

HTML5 conformance checking as a Rust dependency, without a JVM. Checked differentially against the Nu Html Checker (vnu). It includes six standalone packages that read the syntax the check needs.

Status

0.2.1 on crates.io, used by auditmysite (src/html_conform/) and astro-post-audit (checks/html_validation.rs). main contains changes after 0.2.1: MSRV raised to 1.88, additional findings (img without alt, interactive content in a).

With the move, the = pins are gone. Previously html-conform depended on exact versions of four of its six dependencies, because every change needed a release cascade across seven repositories. Now they are workspace dependencies with path and version — one change, one release.

Structure

HTML source

html5-parser

infoset: normalized tree

Content model, attributes,

table integrity

relax-ng

(schema validation)

schematron-engine

(rules over XPath)

xpath-eval

Attribute values

csp-parse

media-query-parse

Findings (Severity::Error)

PackageRole
html5-parserWHATWG tokenizer and tree construction, reports parse errors with position
csp-parseContent Security Policy per the CSP3 grammar
media-query-parseMedia Queries Level 4
xpath-evalXPath evaluation
relax-ngRELAX NG, for the schema side of the check
schematron-engineSchematron rules over xpath-eval

Public surface

html_conform::check(html) returns the findings. The six packages underneath each have their own small API and can be used individually — xpath-eval and schematron-engine, for example, also outside of HTML.

CheckReport::to_report() converts the result into a11y-report’s Report, and CheckReport::skipped names unchecked classes present in the document (CSS in <style>, CSP hash sources); both appear there as DocumentIncomplete input notes. The crate’s own Finding types remain for now.

check_with_ranges additionally returns, per finding, the source range where the layer knows the end: the start tag of the element a schema, assertion, script, CSP or table finding is anchored at, always the start tag, also for a finding about the element’s content (html5-parser records it as Node::end_position). Parse errors have no end. column counts Unicode scalar values, byte_offset UTF-8 bytes; details in docs/html-conform/reference/api.md. html_conform::rules() is the machine-readable rule catalogue (id, layer, outcome, description, source), built from the parsed rules/*.sch files and the layer constants, and also feeds the SARIF tool.driver.rules; html-conform --list-rules prints it.

The rdfa-lite feature (off by default) adds SchemaPreset (#[non_exhaustive]: Default, RdfaLite) and check_with_preset(html, options, preset): vnu’s html5-rdfalite preset, which allows only the RDFa Lite 1.1 attributes. Without the feature the type and function do not exist, so there is no runtime “not run” path; check and check_with_options are unchanged. The second compiled schema is prebuilt/html5-rdfalite.rngc (about 246 KB); it is in the crate download either way (a feature cannot exclude files from a tarball) but only compiled in with the feature.

The cli feature (off by default, no extra dependency) adds an html-conform binary as a thin front end for files, directories and stdin; see docs/html-conform/guides/cli.md (encodings, rule selection, config file, prebuilt binaries). It lives in the crate rather than in a separate one because it takes data from the local filesystem and fetches nothing, and a separate crate would need its own release train.

Dependencies

Downward: the six above and a11y-report (for to_report). Upward: auditmysite, astro-post-audit, in future web-checks.

Limits

Not a complete vnu replacement. The check is calibrated differentially against vnu, not identical to it; which classes are missing is documented in docs/html-conform/. And it says nothing about accessibility — conformance is a different question from perceivability.

Checking goes up to a nesting depth of 512 elements. Deeper documents yield CheckError::DocumentTooDeep instead of a report — the check layers after the parser recurse over the tree and would otherwise blow the stack of a 2 MiB thread. The parser itself knows no limit, because WHATWG tree construction has none; Blink stops nesting at 512 and attaches deeper elements as siblings. html-conform deliberately does not adopt this flattening: findings would then apply to a tree that the specification does not build. The limit uses the latitude that the HTML standard leaves for implementation-specific limits on otherwise unbounded input, by rejecting the check rather than checking something different.

Edit this page on GitHub