An HTML parser in Rust that reads pages the way a browser does, finds what you're after with CSS selectors, and lets you change the tree and write it back out.
If you use Lexbor today, directly or through selectolax, PHP's Dom\HTMLDocument,
nokolexbor or lazy_html, Parsec is being built for you. Lexbor is fast and
follows the spec. What we want to add is everything people keep asking it
for: positions on nodes, a worst case that stays linear, documents you can
share across threads, XPath, selectors that match everything a static page
can decide, and no use-after-free bugs, because the parser is memory-safe
Rust.
Parsec isn't released yet. This page says what works today and what's on the way, and the line between the two is kept sharp.
use parsec::css::SelectorList;
let doc = parsec::parse("<ul><li class=hit>one<li>two</ul>", false);
let hits = SelectorList::parse("li.hit").unwrap().select(&doc, doc.root());
assert_eq!(doc.text(hits[0]), "one");- The HTML Standard's tokenizer and tree builder. It passes all 7,045
html5lib tokenizer tests and every web-platform-tests tree-construction
case that doesn't need JavaScript, run with scripting on and with it off.
cargo testruns both suites; the WPT files are pinned incrates/parsec/tests/tree-construction. - Fragments parsed in the context of any element, the way
innerHTMLis, SVG and MathML included. - CSS selectors, Level 4:
:has(),:is(),:where(),:not()with lists,:nth-child(… of S),:scope, attribute flags, and the form states (:checked,:disabled,:requiredand friends). - A tree you can edit: create, insert, move and remove nodes, set and
rename attributes, then serialize as
innerHTML,outerHTMLor text. - Limits you choose: a time budget per parse and a cap on input size.
The slow-input cases in
examples/hostile.rs, like thousands of nested formatting elements (the pattern that runs Lexbor out of memory), take time in proportion to their length. - One dependency (
memchr) and oneunsafeblock, with its reasoning written beside it.
Speed: Parsec's parser is the one inside Netweir, whose published benchmark pulls every price, title and link from a generated shop page in 3.0 ms where selectolax (Lexbor) takes 5.7 ms, and 31 ms against 64 ms at 10 MB. That run includes Python and the queries, so it isn't a parser-only number. A head-to-head harness against Lexbor's own C, in one process, on the same corpus, is the first thing on the roadmap, and its numbers will replace this paragraph.
The roadmap has the order. In short, Parsec is set to cover every module Lexbor has, and then go past it:
| Lexbor | Parsec | |
|---|---|---|
| HTML parsing, fragments | yes | yes |
| Bytes in, encoding sniffed | yes | planned |
| Chunked input | yes | planned |
| Parse errors with line and column | pointers only | planned |
| Source positions on nodes and attributes | no | planned |
| Linear worst case | no (#340) | planned |
| Thread-safe documents | no | planned, checked at compile time |
:scope, :lang(), :dir(), :valid and other static pseudo-classes |
parsed, never match | :scope yes, the rest planned |
| XPath | no | planned |
| WHATWG Encoding, 40 encodings | yes | planned |
| WHATWG URL | yes | planned, with WPT pass counts published |
| CSS parser and CSSOM | yes | planned, with var(), media queries and computed values |
| C API | yes | planned, with prebuilt libraries for Linux, macOS and Windows |
| Python, WebAssembly | through bindings | planned, built here |
Every "yes" in the Parsec column is something cargo test checks. A
"planned" becomes a "yes" only when it ships.
Parsec isn't on crates.io yet. To try it from git:
[dependencies]
parsec-html = { git = "https://github.com/parsec-html/parsec" }The package is parsec-html and the crate you use is parsec.
The wiki is where the documentation lives: how parsing works, every option, the selector list, and a guide for moving from Lexbor.
git clone --recurse-submodules https://github.com/parsec-html/parsec
cd parsec
cargo testThe parser core is shared with Netweir, so changes to crates/parsec start
there. CONTRIBUTING.md explains how that works and how
to get a change in. Questions go to
Discussions, bugs to
Issues.