feat: add htmlToHast for parsing HTML into HAST (#60)#140
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
c4a17f4 to
04ea080
Compare
04ea080 to
3831715
Compare
|
No need to keep this updated, I'll be taking it over very soon. I have some changes for it in local. It overalls looks good! |
Thanks for letting me know! I was a little surprised, since I didn’t realize you already had related changes in progress. When you say you'll be "taking it over," could you clarify what you mean? Should I pause work on this PR, or are you planning to incorporate parts of it into your own changes? I just want to make sure I understand. |
|
I'll just push a few things to your branch, I'm currently travelling so I'm a bit offline. I currently have some changes committed in local that I just haven't pushed yet. Of course, your commits are still part of it and you'll get the appropriate credits both in the changelog and in the commit log. |
|
@Princesseuh — nice work on the The boolean coercion diverges from if ((info.boolean || info.overloadedBoolean) &&
(value === '' || normalize(value) === normalize(name))) return true
return valueOur current arms are: PropKind::Boolean => (PROP_BOOL_TRUE, StringRef::empty()),
PropKind::OverloadedBoolean if value.is_empty() => (PROP_BOOL_TRUE, StringRef::empty()),So two cases diverge from
The XHTML-style Suggested fix: pass the resolved property name into (Verified empirically against |
90df983 to
35c1ca3
Compare
Adds an html5ever-backed parser that turns an HTML string into a HAST tree (elements, text, comments, doctype), mirroring hast-util-from-html in document mode. Exposed as `htmlToHast` from the npm package and `create_hast_handle_from_html` at the NAPI boundary. The Rust parser lives behind an opt-in `from-html` feature on satteri-ast; the NAPI binding enables it by default (like `mdx`) so the full build ships it and lite (--no-default-features) builds drop it. Refs bruits#60 Co-authored-by: Copilot App <[email protected]>
Co-authored-by: Copilot App <[email protected]> Copilot-Session: 11c16117-e051-412e-be36-084082adeb2c
…owed ones Stitch markers now carry a per-reparse random nonce and are claimed by the tree sink as they parse, so document content can neither forge nor collide with them. Markers the parser swallows as text (unclosed raw-text element, split tag, unterminated comment) are detected and scrubbed from the output instead of leaking. Also emits arena subtrees in a single pass.
…onversion The reparse moves from the hast-handle constructor into ConvertOptions and runs as the final conversion step, so the markdownToHtml fast path, the plugin pipelines, and the *ToHast entry points all reparse identically and hast plugins always see reparsed elements.
…in rawHtml Four conformance fixes surfaced by a differential audit against remark-rehype + rehype-raw, all now locked in by conformance cases: - boolean attributes coerce to true only when the value is empty or repeats the attribute name case-insensitively (disabled="false" stays a string, download="download" becomes true) - numeric coercion follows JavaScript Number() acceptance: hex/octal/ binary literals and exactly-spelled Infinity coerce; inf/NaN/units stay strings - comma-separated lists keep interior empty items and drop only a trailing one - coords items materialize as numbers via a new PROP_COMMA_SEP_NUM wire kind (coords was also misclassed as a plain number in the tables) - raw fragments parse in a template context, the most permissive mode, so table parts and other context-sensitive elements survive outside their usual parents
cc1266a to
6ee0462
Compare
Summary
Prototype for #60 — a built-in HTML → HAST parser, the Sätteri equivalent of
hast-util-from-html/rehype-raw's parsing step.Adds
htmlToHast(html: string): HastNodeto the npm package (andcreate_hast_handle_from_htmlat the NAPI boundary). It parses an HTML string into a materialized HAST tree — elements, text, comments, and doctype — usinghtml5ever's spec-compliant tree builder, in document mode (result is arootwrapping the implied<html>subtree, matchinghast-util-from-html's default).Approach
Follows the parser recommendation from my benchmark writeup on the issue: #60 (comment) —
html5everdriving a custom, arena-friendlyTreeSinkrather than pulling inrcdom.crates/satteri-ast/src/hast/from_html.rs: an index-addressedTreeSink(Handle = usizeinto aVec<Node>) that mirrors rcdom's tree-mutation semantics (append + text coalescing, foster parenting, adoption-agency reparenting,add_attrs_if_missing, template contents). After parsing, the flat node list is walked once into anArenaBuilder<Hast>, reusing the existing HAST codec so serialize/materialize/render all work unchanged.class/href/etc. round-trip.Feature gating
from-htmlis opt-in onsatteri-ast(not in its default features) so size-conscious consumers can drophtml5everentirely.mdx): the full native build ships it, and lite--no-default-featuresbuilds drop it.Happy to flip this either way — e.g. make it fully opt-in on the binding too — depending on how you'd like to weigh the binary-size cost.
Tests
#[test]s infrom_html.rscovering document wrapping, attributes, text/comment/doctype emission, character references, raw-text elements, foster parenting, and adoption-agency reparenting.packages/satteri/test/from-html.test.ts— asserts the materialized tree shape (root → html/head/body, tag names, text/comment/doctype, verbatim properties) and a full round-trip through theunified+rehype-stringifyecosystem, which is the actual interop story from Add a built-in way to get HAST from HTML #60.Known follow-ups (deliberately out of scope for this prototype)
class, notclassName: [...]; no boolean/number coercion). Fullproperty-informationhandling (space/comma-separated lists, booleans, SVG casing) would be the natural next step for exacthast-util-from-htmlparity.disabledcurrently renders asdisabled=""; SVG/xmlns prefixes aren't specialized yet.hast-util-from-html'sfragment: true) could be added later.A
minorSampo changeset is included.Refs #60