Skip to content

feat(html): key-value form regions (docling field_region/field_item) - #22

Merged
artiz merged 5 commits into
masterfrom
claude/focused-curie-zkkv2p
Jul 2, 2026
Merged

feat(html): key-value form regions (docling field_region/field_item)#22
artiz merged 5 commits into
masterfrom
claude/focused-curie-zkkv2p

Conversation

@artiz

@artiz artiz commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

First slice of the HTML browser-render subsystem (MIGRATION §5): key-value form regions, detected statically — no headless browser. An element classed form_region whose descendants carry docling's keyN / keyN_valueM / keyN_marker id-convention becomes a field_region of field_items (marker/key/value), ordered by key number.

This closes kvp_data_example: the Markdown now matches docling's groundtruth exactly, apart from the pre-existing, deliberate table-padding divergence (MIGRATION §4) and the trailing-newline convention.

Key Changes

Core (fleischwolf-core)

  • Node::FieldRegion { items: Vec<FieldItem> } + the FieldItem { marker, key, value } struct (all Option).
  • JSON: new field_regions / field_items collections. Emitted only when a document has form fields, inserted in docling's slot (just before pages), so every non-KVP document's JSON stays byte-identical to before. Field parts serialize as marker / field_key / field_value texts with the correct parent/child refs.
  • Markdown: the region and each item render as <!-- missing-text --> (they carry no text of their own), followed by the item's parts as separate paragraphs — matching docling.

HTML backend (fleischwolf)

  • detect_field_region fires from the div/section default arm: requires the form_region class and keyN descendants, so it anchors on the tight container (never swallows the whole page) and leaves a plain form_region (or the items <table class="form_region">) as ordinary content.
  • parse_kvp_id parses the keyN / keyN_marker / keyN_valueM id convention.

Scope / deferred

  • Not using a headless browser — kept to the repo's pure-Rust, no-runtime design for declarative formats. The remaining subsystem items (nav/visibility suppression in wiki_duck, rendered nested-table cell geometry) genuinely need a rendered page and are left as follow-ups; MIGRATION §5 is updated accordingly.
  • The KVP JSON matches docling structurally (region/item/labels/children); the one remaining delta is the region's parent ref (#/body vs docling's #/texts/1), which is the pre-existing "HTML doesn't nest content under headings" divergence — orthogonal to KVP and a larger, separate change.

Also bundles a small docs commit: moving the "TableFormer is done" note from MIGRATION's out-of-scope list into the README Status section.

Testing

  • New unit test form_region_becomes_key_value_fields (region/item markers + split parts, and that a plain form_region stays ordinary text).
  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo build --workspace --all-targets, and the full cargo test suite (incl. the all-format regression harness) pass. Regenerated fixtures are limited to the 3 kvp_data_example files — no other fixture drifted.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 5 commits July 1, 2026 21:47
The parenthetical lived in MIGRATION's "Not migrated / out of scope"
list, where a "now done" aside read oddly. Reformulate it as a
capability sentence in the README Status section (next to the existing
ONNX-stack description) and drop it from the out-of-scope list.
Detect docling's HTML key-value regions statically — no browser needed.
An element classed `form_region` whose descendants carry the
`keyN` / `keyN_valueM` / `keyN_marker` `id`-convention becomes a
`FieldRegion` of `FieldItem`s (marker/key/value), ordered by key number.

Core additions:
- `Node::FieldRegion { items: Vec<FieldItem> }` + the `FieldItem` struct.
- JSON: `field_regions` / `field_items` collections (emitted only when
  present, in docling's slot just before `pages`, so non-KVP documents'
  JSON stays byte-identical). Field parts serialize as `marker` /
  `field_key` / `field_value` texts.
- Markdown: the region and each item render as `<!-- missing-text -->`
  (they carry no text of their own), followed by the item's parts as
  separate paragraphs — matching docling's output.

This closes the KVP part of the HTML browser-render subsystem
(kvp_data_example). The markdown now matches docling's groundtruth
exactly apart from the pre-existing, deliberate table-padding
divergence (MIGRATION §4). nav/visibility suppression and rendered
nested-table geometry remain (they genuinely need a rendered page).
…ssion

Three static (no-browser) HTML conformance fixes, closing hyperlink_02:

- Inline images emit nothing. docling never renders inline `![alt](#)`
  markers (zero across every HTML groundtruth); only block-level,
  `<a>`-wrapped, and `<figure>` images become `<!-- image -->` pictures.
  The inline-wrapper→picture path is now restricted to `<a>` anchors, so
  a `<span><img></span>` (inline decoration) is dropped like docling does,
  while `<a><img></a>` (a linked image) still becomes a picture.
- Inline visibility suppression: an element with the `hidden` attribute or
  an inline `display:none` / `visibility:hidden` style is dropped (a
  renderer doesn't paint it). `aria-hidden="true"` is deliberately NOT
  suppressed — it hides from the accessibility tree but stays visually
  rendered. Stylesheet/class-driven visibility still needs a real browser.

This makes hyperlink_02 content-exact (its `<span>`-wrapped logo image is
now dropped) and, applied through the shared backend, cleans the MHTML
cookbook fixture's inline logos and image-only table cells to match
docling's text-only cell extraction. HTML conformance 28/32 -> 30/32;
MIGRATION §5 updated — only the genuinely browser-dependent behaviours
(CSS-cascade nav suppression in wiki_duck, rendered nested-table geometry)
remain.
Adds a Rust-driven headless-browser HTML pre-render for the one thing a
pure-Rust parse can't do: resolve the CSS cascade (stylesheet/class-driven
`display:none`, e.g. a collapsed nav menu). It drives the system Chromium
over the DevTools protocol via `headless_chrome` — no Node/Playwright —
loads the page, removes every element the browser computes as
`display:none`, and hands the cleaned HTML back to the normal Rust
backend, so all structure/table/KVP/formatting logic still runs in Rust;
the browser only decides visibility.

- Gated behind the off-by-default `web-browser` Cargo feature so the
  standard build stays browser-dependency-free. `DocumentConverter::
  use_web_browser(bool)` + the CLI `--use-web-browser` flag
  (`--features web-browser` to compile it in; a clear error otherwise).
- Chromium located via $FLEISCHWOLF_CHROME / $CHROME /
  $PLAYWRIGHT_BROWSERS_PATH, else autodetected; sandbox off (container).
- New `backend::browser` module + `ConversionError::Browser`. A
  feature-gated test confirms a stylesheet-hidden element is stripped
  while visible siblings stay (skips gracefully if no Chromium present).

Scope: resolves the cascade only when the page's CSS is reachable (inline
`<style>`, or external stylesheets fetchable with a base host). Rendered
nested-table cell geometry remains the last browser-dependent gap.
README + MIGRATION §4/§5 updated.
MHTML and EPUB assemble HTML from their archives and route it through the
shared HTML backend, so the headless-browser pre-render now applies to
them too. Factor the pre-render into one `backend::maybe_prerender_html`
helper (feature-gated, borrows when disabled) used by the converter's
direct-HTML path and by both backends:

- `MhtmlBackend` gains a `use_web_browser` field (now a struct, `Default`);
  pre-renders the extracted `text/html` part before conversion.
- `EpubBackend` gains a `use_web_browser` field alongside `fetch_images`;
  pre-renders the concatenated spine HTML.
- The converter threads its flag into both.

Verified end-to-end through the MHTML path: an embedded-stylesheet
`display:none` element leaks through without the flag and is stripped with
it. Default build unchanged (feature off): clippy -D warnings, full test
suite, and fmt all clean. README/CLI/converter docs updated to say the
flag covers HTML/MHTML/EPUB.
@artiz
artiz merged commit b1f3af1 into master Jul 2, 2026
3 checks passed
@artiz
artiz deleted the claude/focused-curie-zkkv2p branch July 2, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants