feat(html): key-value form regions (docling field_region/field_item) - #22
Merged
Conversation
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 `` 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First slice of the HTML browser-render subsystem (MIGRATION §5): key-value form regions, detected statically — no headless browser. An element classed
form_regionwhose descendants carry docling'skeyN/keyN_valueM/keyN_markerid-convention becomes afield_regionoffield_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> }+ theFieldItem { marker, key, value }struct (allOption).field_regions/field_itemscollections. Emitted only when a document has form fields, inserted in docling's slot (just beforepages), so every non-KVP document's JSON stays byte-identical to before. Field parts serialize asmarker/field_key/field_valuetexts with the correct parent/child refs.<!-- 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_regionfires from thediv/sectiondefault arm: requires theform_regionclass andkeyNdescendants, so it anchors on the tight container (never swallows the whole page) and leaves a plainform_region(or the items<table class="form_region">) as ordinary content.parse_kvp_idparses thekeyN/keyN_marker/keyN_valueMid convention.Scope / deferred
wiki_duck, rendered nested-table cell geometry) genuinely need a rendered page and are left as follow-ups; MIGRATION §5 is updated accordingly.parentref (#/bodyvs 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
form_region_becomes_key_value_fields(region/item markers + split parts, and that a plainform_regionstays ordinary text).cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo build --workspace --all-targets, and the fullcargo testsuite (incl. the all-format regression harness) pass. Regenerated fixtures are limited to the 3kvp_data_examplefiles — no other fixture drifted.🤖 Generated with Claude Code
Generated by Claude Code