Skip to content

feat: honor the InlineGroup whitespace contract in the backends - #3891

Open
cau-git wants to merge 18 commits into
mainfrom
cau/serializer-whitespace-fixes
Open

cau-git wants to merge 18 commits into
mainfrom
cau/serializer-whitespace-fixes

Conversation

@cau-git

@cau-git cau-git commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Important

Paired with docling-project/docling-core#693, and it must merge after it. These backends emit inline runs that carry their own boundary whitespace, and only a docling-core that joins runs faithfully renders them correctly.

Update: merged main, new producers fixed

main (90234a71) is merged into this branch. The conflicts were resolved to the versions reviewed on a rebased copy, and the newer commits were replayed on top, so no force push was needed. uv.lock pins docling-core#693 at 92a694dd.

Producers that landed on main in the meantime and broke the contract are fixed here. Each would merge words as soon as the new core is released:

  • MS Word: whitespace-only runs between other runs are kept in checkbox paragraphs and in paragraphs that hold DrawingML.
  • PowerPoint: whitespace between two inline OMML formulas is kept ($a$$b$ → $a$ $b$).
  • HTML:
    • a <br> between inline runs becomes a "\n" run;
    • captions are whitespace-normalized before they are joined;
    • a block child of a list item keeps its word boundary.
  • Markdown: rich table-cell runs keep their boundary whitespace (feat(md): preserve inline formatting in Markdown table cells #4488).
  • AsciiDoc and Chandra: runs keep their boundary whitespace. Trimming happens at the block edges only, and a boundary space never goes into a code or formula run.
  • JATS: a footnote label stays apart from its paragraph. _strip_segments is gone entirely, including the call that fix(jats): preserve styling in abstracts and footnotes #4361 added.
  • ODF: the hyperlink-boundary fix for fix(odt): populate TextItem.hyperlink for anchor elements #3949 stays. main still strips whitespace at hyperlink boundaries.

Groundtruth was regenerated rather than merged by hand. The net test-data diff against main is 167 files.

Still to do before merge:

  • Regenerate the image-bearing docx goldens (test_emf_docx.docx and others) on a machine with LibreOffice. textbox.docx stays as it is: it is xfail(strict=False) and its output differs between runs.
  • After the docling-core release: drop the docling-core entry from [tool.uv.sources], raise the floor in the root, slim and meta-package pyproject.toml, and re-lock.

Summary

docling-core no longer joins InlineGroup runs with a hard " "; it concatenates them faithfully. Producers must therefore stop stripping run boundaries, because the serializer no longer adds a separator back.

Until now every backend relied on that join, which is why the committed groundtruth contained H 2 O, x 2 + y 3 and (strong) .. Once a backend strips the boundary, the real spacing can't be recovered downstream, so the fix has to happen where the runs are made.

today : ... Water is written as H 2 O using sub. ... x 2  + y 3  uses sup. ... CO 2 * .
fixed : ... Water is written as H2O using sub. ... x2 + y3 uses sup. ... CO2*.

today : ... we have a strong emphasis (strong) . Notice that ...
fixed : ... we have a strong emphasis (strong). Notice that ...

What changed

HTML. The whitespace was lost at extraction. " ".join(text.split()) collapses runs of whitespace, which is correct per the HTML spec, but it also strips the edges, which is the defect. Everything downstream therefore received runs that were already stripped. Extraction now collapses without trimming, and normalization happens once per block across the whole run list. Collapsing per node can't see that ["a ", " ", " b"] is a single boundary. simplify_text_elements merges neighbours with the same format without a separator, to_single_text_element concatenates as-is, and the emitters no longer strip individual runs.

Two consequences worth calling out:

  • A boundary never lands inside a ` delimiter. The serializers hoist whitespace out of emphasis but not out of code, so the space goes to whichever neighbour is plain. When both sides are code, the space becomes an unformatted run of its own, because `a b` `` is corrupt either way. For the same reason the emitters keep whitespace-only runs: normalization has already trimmed the block edges, so any whitespace run that survives is significant.
  • Edge whitespace blocks two shortcuts.
    • The single-character compactor skips candidates that carry edge whitespace; otherwise it would glue words back together in rendered span soup.
    • _is_rich_table_cell uses the normalized list too. Otherwise the whitespace between tags in <td>\n<p>x</p>\n</td> counts as two extra runs and the cell is misclassified.

MS Word.

  • _get_paragraph_elements grouped runs by format and used strip() both to decide whether to emit a run and as the emitted value. It now groups without stripping and trims only the paragraph edges.
  • _add_inline_equations_to_parent builds its own InlineGroup outside that path. It stripped the text after the last equation, which merged the formula into the next word ($A= \pi r^{2}$is the area formula). It now trims only the group edges.

Markdown. The backend keeps the raw Marko RawText fragments instead of stripping them. It emits a space for a soft line break, which is how a soft-wrapped paragraph renders and what the join used to supply. A hard break before a code span emits a "\n" run.

JATS. _strip_segments became _trim_segment_edges. It drops empty segments, merges compatible neighbours, and trims only the first and last segment.

ODF was already the reference producer. Its tail-text recovery came from #3850. This PR only removes the hyperlink-boundary strip that #3949 added.

Temporary dependency source: remove before release

tool.uv.sources points at the docling-core branch, so the paired change can be installed and CI resolves the same core the goldens were generated against. uv.lock pins the exact commit.

Release checklist:

  1. Merge and release docling-core (a 2.x minor, not 3.0).
  2. Drop the docling-core entry from [tool.uv.sources].
  3. Raise the docling-core floor to the release that carries the faithful join, and re-lock.

Version skew

Both packages are on 2.x and a major bump isn't affordable, so the dangerous combination can be installed and is worth understanding:

docling-core docling backends result
old (" " join) old (runs without spaces) correct, the status quo
new ("" join) new (runs carry their whitespace) correct, the goal
new old merged words
old new (this PR) double spaces, cosmetic

Hence the merge order: docling-core first, then this.

Testing

Full suite against the docling-core#693 head (92a694dd): 4070 passed, 62 skipped, 2 xfailed, 4 failed. The 4 failures depend on the test machine: PDF end-to-end, OCR end-to-end and webp without tessdata, and ocrmac language selection. They also fail with main's code.

tests/test_inline_whitespace_contract.py covers the cases that need a source document built on the fly:

  • DOCX runs that carry their own whitespace;
  • DOCX inline-equation boundaries;
  • HTML boundaries around adjacent code spans (with and without spaces, and mixed with formatting);
  • <pre> interior whitespace;
  • JATS runs that rejoin to the source text.

Known limitations (all already on main, unchanged)

  • A loose Markdown list item - intro\n\n more **x** y exports as - intromore **x** y. This comes from the md backend's nesting.
  • A docx checkbox paragraph with several runs repeats the checkbox label on every run.
  • An HTML <br> inside <p> or <td> becomes a space. Words don't merge, but the line break is lost.

Out of scope

  • <pre> with inline markup still produces one code item per fragment. This PR only stops it from merging words (<pre>See <a>x</a> first</pre>) and keeps the interior whitespace. The fragment structure is a separate issue that already exists.
  • The synthetic trailing " " that get_text adds for p/li/th/td. It is a fake block separator at the character-extraction layer: a different concern, with other consumers.
  • Real CSS whitespace processing: the white-space property, computed display per element, &nbsp; and bidi. Collapsing at extraction plus normalizing across runs covers the normal-flow content tested here.

Checklist:

  • Documentation has been updated, if necessary.
  • Examples have been added, if necessary.
  • Tests have been added, if necessary.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

❌ DCO Check Failed

Hi @cau-git, your pull request has failed the Developer Certificate of Origin (DCO) check.

This repository supports remediation commits, so you can fix this without rewriting history — but you must follow the required message format.


🛠 Quick Fix: Add a remediation commit

Run this command:

git commit --allow-empty -s -m "DCO Remediation Commit for Christoph Auer <cau@zurich.ibm.com>

I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: 9e48e5d27b769c1b7aeed11c00ed2c81d98546c5
I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: 8a44fd21e9390cd600c19563d52e3f79b6c030a6"
git push

🔧 Advanced: Sign off each commit directly

For the latest commit:

git commit --amend --signoff
git push --force-with-lease

For multiple commits:

git rebase --signoff origin/main
git push --force-with-lease

More info: DCO check report

@mergify

mergify Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 2 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require two reviewer for test updates 👀 reviews
🟢 Enforce conventional commit —

🔴 Require two reviewer for test updates

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@codecov

codecov Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@cau-git cau-git changed the title Cau/serializer whitespace fixes feat: honor the InlineGroup whitespace contract in the backends Jul 27, 2026
Inline runs carry their own significant whitespace and the docling-core
serializers now concatenate them faithfully, so producers must stop stripping run
boundaries. Previously each backend relied on the serializer's hard `" "` join,
which invented spaces (`H 2 O`, `x 2 + y 3`, `(strong) .`) and made the real
boundary unrecoverable.

HTML: collapse whitespace without trimming at the extraction origin
(`" ".join(s.split())` did both), then normalize once per block across the whole
run list -- per-node collapsing cannot see that `["a ", " ", " b"]` is one
boundary. `simplify_text_elements` merges same-format neighbours without a
separator, `to_single_text_element` concatenates verbatim, and the emitters no
longer strip individual runs. The single-char compactor skips candidates carrying
edge whitespace, or it would glue words in rendered span soup.
`_is_rich_table_cell` consumes the normalized list too: inter-tag whitespace in
`<td>\n<p>x</p>\n</td>` otherwise counts as two extra runs and misclassifies the
cell.

A boundary never lands inside a `` ` `` delimiter, because the serializers hoist
whitespace out of emphasis but not out of code. It goes on whichever neighbour is
plain; when both sides are code it becomes an unformatted run of its own, since
`` `a`` b` `` is corrupt either way. The emitters therefore keep whitespace-only
runs: normalization has already trimmed the block edges, so one that survives is
significant.

`<pre>` is preformatted, so it is taken verbatim and only its block edges are
trimmed. Stripping each fragment merged `<pre>See <a>x</a> first</pre>` into one
word once the join stopped compensating. The one-code-item-per-fragment shape of
`<pre>` with inline markup is a separate pre-existing issue and is left alone.

MS Word: group runs by format without stripping, then trim only the paragraph
edges. `_add_inline_equations_to_parent` builds its own InlineGroup outside that
path and stripped the fragment after the last equation, merging the formula into
the next word (`$A= \pi r^{2}$is the area formula`); it now trims group edges
only. JATS: `_strip_segments` becomes `_trim_segment_edges`. Markdown: keep the
raw Marko `RawText` fragments and emit a space for a soft line break, which is
how a soft-wrapped paragraph renders and which the join used to supply.

Goldens are re-baselined. `textbox.docx` is left untouched: its output varies
between identical runs, and its test is already `xfail(strict=False)`, so
regenerating it would only bless nondeterministic data.

Ships as a minor, not a major: `feat!:` would make python-semantic-release
(7.34.6) bump to 3.0, and a 3.0 is not affordable here. Needs a docling-core
floor bump to the release that carries the faithful join, once that number exists
-- an older docling-core with these backends produces double spaces (cosmetic),
but a newer docling-core under older backends merges words.
These backends emit inline runs that own their boundary whitespace, which only a
faithful-join docling-core renders correctly. Point `tool.uv.sources` at the
branch carrying that change so the lock-step pair is installable and CI resolves
the same core the goldens were baselined against.

`uv.lock` pins the exact commit (66040cb7). Temporary: drop the source and raise
the `docling-core` floor in `dependencies` once the release carrying the faithful
join exists on PyPI.

The lock diff is larger than the one entry because moving docling-core from a
registry wheel to a git checkout re-narrows environment markers across the graph.
The resolved package set is unchanged at 382, and no dependency is added or
removed.
I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: ec6163c
I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: 8a7b4fe
I, Christoph Auer <cau@zurich.ibm.com>, hereby add my Signed-off-by to this commit: 31a4a73

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Rebase integration. Upstream #3949 (ODF anchor hyperlinks) stripped whitespace
at hyperlink boundaries in _normalize_odf_text_runs, correct under the old ''
join which re-inserted a separator, but a word-merge under the faithful join
(Watch[the talk]for context). The boundary space now stays in the plain run
and the serializer places it outside the link markup, per the contract.

- drop the hyperlink-boundary strip; update #3949's two tests to the
  contract-correct whitespace-bearing item texts (markdown was already right)
- regenerate ODF goldens (double-space / space-before-punct artifacts gone,
  upstream #3850 inter-span text renders tight)
- bump three upstream-added docx goldens to schema 1.11.0

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Re-resolve the docling-core git source to the rebased branch tip
(e4e745a, v2.91.0) after both PRs were rebased onto main; the lock
previously pinned the pre-rebase commit 66040cb7.

Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
@cau-git
cau-git force-pushed the cau/serializer-whitespace-fixes branch from 33c43dc to 93e3049 Compare August 18, 2026 13:26
@cau-git
cau-git marked this pull request as ready for review August 19, 2026 08:15
@el-belgounetos

Copy link
Copy Markdown

Hi,

Thanks for this work. One extra case that may be worth a test: a word split mid-word by a style change in DOCX, with no space at the run boundary.

Source Crédit Débit with a bold C and a bold D (runs C(bold) rédit D(bold) ébit). On docling 2.135.0 (before this PR) the Markdown is **C** rédit **D** ébit: _get_paragraph_elements strips the group edges, then MarkdownInlineSerializer joins with " ", so the original absence of a space is lost.

A control paragraph Plain + bo(bold) + ld middle and end (a real space before the bold run, none after) must give Plain **bo**ld middle and end.

The test descriptions mention DOCX runs owning their whitespace and unspaced HTML code spans, but I could not tell from the PR page whether an unspaced bold run inside a word is asserted for DOCX. Expected with the faithful join: **C**rédit **D**ébit and Plain **bo**ld middle and end. I have not run this against the PR branch. The generator script and fixture are attached (min_split_word.docx).

min_split_word.docx

cau-git and others added 13 commits October 26, 2026 09:06
Conflicts resolved to the versions reviewed on the rebased branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
…aragraphs

Under the faithful InlineGroup join, whitespace-only runs between other
runs are text. `_add_body_text_items` dropped them when `skip_empty_text`
was set, and `_clean_checkbox_symbols` stripped every run of a checkbox
paragraph, which merged words (`Buy**milk**now`).

Clean the checkbox symbol only from the leading edge of the first run and
re-trim the block edges. With `skip_empty_text`, skip only a block that is
empty as a whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
The OMML paragraph path dropped every whitespace-only text fragment, so
`Let $a$ $b$ hold.` became `Let $a$$b$ hold.` under the faithful
InlineGroup join, and the list path kept untrimmed block edges.

Trim only the outer edges of the fragment sequence in both paths and keep
interior whitespace fragments. Without an inline group the formulas stay
separate blocks, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Under the faithful InlineGroup join, a list item or <pre> run that held
only <br> tags was dropped, so `<li><b>Bold</b><br><i>italic</i></li>`
became `**Bold***italic*`. Keep it as a plain "\n" run between its
neighbours (never inside a code run); the serializers render it as a hard
line break (`**Bold**  \n*italic*`, `<br>`). A <br> at an edge of a list
item is a block edge and is trimmed, also when it ends the last run.

`_emit_caption` joined runs without collapsing their boundary whitespace,
which gave double spaces; normalize the runs first, as the figure caption
path does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
The shared Chandra HTML mapper (also used by dots.ocr and Nemotron Parse)
stripped every inline run and relied on docling-core's " " join, so a
faithful-join core merges words ("**Rent**and terms"). Keep each run's
boundary whitespace, collapse a boundary to one space carried by the left
run, and trim only the block's outer edges. Edge whitespace of a code or
formula element moves out into a plain " " run, so a boundary space never
lands inside them. A <br> at a run edge now stays where it is, since core
hoists edge whitespace outside the emphasis markers; multi-run headings
collapse whitespace without trimming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
_iterate_cell_inline stripped every RawText run of a GFM rich cell, so a
faithful-join docling-core merges words ("**C**Cadre", "run`build`now").
Keep the raw fragment like the paragraph path does: marko already trims the
cell edges, and whitespace-only runs between formatted runs are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
_flush_text_data stripped every inline run and relied on docling-core's
" " join, so a faithful-join core merges words ("Some**bold**,"). Keep each
run's boundary whitespace and trim only the block's outer edges. A code
run's edge whitespace (possible in ``unconstrained`` spans) moves out into
a plain " " run, so it never lands inside the code delimiters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
A list item's text is extracted from all its non-list descendants. Under the
faithful run join, the text before a block child and the block's own text were
concatenated, so `<ul><li>a<p>para<li>b</ul>` exported `- apara`. A block child
(`_PARA_BREAKERS`) is now a word boundary: a space is added at its edges unless
the source already has whitespace there, and normalize_whitespace() collapses
it and moves it out of code runs. Preformatted extraction is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
These tests still expected stripped runs or the double spaces of the old
" " join. Each new value was checked against the contract: boundary
whitespace sits on the plain neighbour (never inside a code or formula run),
a soft break before a code span is a plain " " run, and the exports read
`*one* two`, `Value $x=1$`, `Already in `Reference/``.

- html: test_text_directly_inside_figure_is_kept (2 cases),
  test_orig_keeps_source_text, test_link_and_image_outside_base_directory_keep_conversion
- msword: test_docx_hidden_text_next_to_equations (no double space before $x=1$)
- md: test_convert_line_breaks ("Author "), test_convert_line_break_next_to_code_span,
  test_line_break_does_not_cross_block_boundary (" " run before the code span)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
A footnote's runs are collected from the whole <fn>. In compact XML
(`<fn><label>1</label><p>See ...`) nothing separated the label from the
paragraph, so under the faithful run join it exported `1See`. A <p> is now a
block boundary: a plain " " run is added unless whitespace is already there,
so compact and indented XML give the same runs.

The JATS styling, footnote and ext-link run-list tests still expected
stripped runs; they now expect the boundary whitespace on each run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Regenerated with DOCLING_GEN_TEST_DATA=1 for docx, html, md, jats, odf, epub,
xbrl, latex, pages/keynote/numbers, asciidoc, pptx, chandra, boxnote, vtt,
mhtml and email, then audited mechanically (whitespace removed, 1.10.0 mapped
to 1.11.0 on both sides). Only spacing changes remain, e.g. latex
`states $h_t$,` (was `states  $h_t$ ,`), plus these explained differences:

- inline-group goldens carry the 1.11.0 stamp; version-only changes to goldens
  without inline groups were reverted
- DocLang `.dclg` header 0.7 -> 0.8 (docling-core #880) and link destinations
  `(`/`)` -> `%28`/`%29` in wiki_duck.html.md (docling-core #920)
- fields that verify_docitems does not compare and that main left stale:
  epub `orig` keeps the source typography (#4434), latex 1706.03762 multirow
  span clamped to the table (#4974), escaped_characters list marker `1.` and
  the binary hash of the internal HTML round trip
- md goldens were generated with docling-core #894's CodeItem escaping
  disabled, so they do not bake in that regression of the HTML-block round trip
- embedded image bytes (machine dependent, partly LibreOffice) restored from HEAD

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
Relock docling-core to the docling-core#693 head (92a694dd). Conflicting goldens were regenerated against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Christoph Auer <cau@zurich.ibm.com>
@mergify

mergify Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 2 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require two reviewer for test updates 👀 reviews
🟢 Enforce conventional commit —

🔴 Require two reviewer for test updates

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@github-actions github-actions Bot added ai:ci-needs-care AI triage: check CI-safety findings before you approve CI asciidoc issue related to asciidoc backend docx issue related to docx backend enhancement New feature or request html issue related to html backend markdown issue related to markdown backend pptx issue related to pptx backend labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI triage (advisory)

Checked commit e021aa9063. The analysis read the diff only. It did not run code from this pull request.

This PR updates document backends to preserve boundary whitespace in inline runs. It aligns backend output with the inline group whitespace contract in docling-core.

Topic labels added: enhancement, asciidoc, html, markdown, pptx, docx.

CI safety: needs maintainer care before you approve CI.

  • pyproject.toml: changes build, dependency, or tool configuration
  • uv.lock: changes locked dependencies
2 related pull request(s) in the same code
PR State Signal Assessment
#4998 open changes the same code related: PR #4998 changes whitespace handling in HTML <pre> blocks, while this PR updates whitespace handling across multiple backends.
#4443 open changes the same code related: PR #4443 adds run formatting and hyperlinks to PowerPoint text, while this PR adjusts whitespace between PowerPoint formulas.

Reference data changes

Change Files
tables 2
text 73
formatting only 5
  • tables tests/data/docx/groundtruth/docx_rich_cells.docx.md: +3 / -3 lines; Markdown table rows changed
  • tables tests/data/html/groundtruth/html_inline_group_in_table_cell.html.md: +1 / -1 lines; Markdown table rows changed
  • text tests/data/boxnote/groundtruth/sample.boxnote.json: item attributes changed
  • text tests/data/boxnote/groundtruth/sample.boxnote.md: line or paragraph breaks changed
  • text tests/data/csv/groundtruth/csv-comma-in-cell.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-comma.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-inconsistent-header.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-pipe.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-semicolon.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-single-column.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-tab.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-too-few-columns.csv.json: item attributes changed
  • text tests/data/csv/groundtruth/csv-too-many-columns.csv.json: item attributes changed
  • text tests/data/docx/groundtruth/Strict.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/Transitional.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_checkboxes.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_code_blocks.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_comments.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_external_image.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_grouped_images.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_list_blank_spacer.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_list_resumed_numbering.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_lists.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_page_header_footer_first_page.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_rich_cells.docx.html: +9 / -9 lines
  • text tests/data/docx/groundtruth/docx_rich_cells.docx.itxt: line or paragraph breaks changed
  • text tests/data/docx/groundtruth/docx_rich_cells.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_rich_tables_01.docx.itxt: +2 / -2 lines
  • text tests/data/docx/groundtruth/docx_rich_tables_01.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/docx_vml_images.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/equations.docx.itxt: line or paragraph breaks changed
  • text tests/data/docx/groundtruth/equations.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/equations.docx.md: +6 / -6 lines
  • text tests/data/docx/groundtruth/list_after_num_headers.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/list_after_num_headers.docx.md: +5 / -5 lines
  • text tests/data/docx/groundtruth/lorem_ipsum.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/omml_frac_superscript.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/omml_func_log.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/omml_multi_equation_paragraph.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/omml_text_escapes_in_math.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/table_with_equations.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/tablecell.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/test_emf_docx.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/unit_test_formatting.docx.itxt: line or paragraph breaks changed
  • text tests/data/docx/groundtruth/unit_test_formatting.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/unit_test_headers.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/unit_test_headers_numbered.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/word_image_anchors.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/word_image_anchors.docx.md: line or paragraph breaks changed
  • text tests/data/docx/groundtruth/word_sample.docx.json: item attributes changed
  • text tests/data/docx/groundtruth/word_tables.docx.json: item attributes changed
  • text tests/data/epub/groundtruth/epub_purvis_poetry.epub.itxt: +40 / -40 lines
  • text tests/data/epub/groundtruth/epub_purvis_poetry.epub.json: item attributes changed
  • text tests/data/epub/groundtruth/epub_purvis_poetry.epub.md: +26 / -26 lines
  • text tests/data/html/groundtruth/example_01.html.json: item attributes changed
  • text tests/data/html/groundtruth/example_01_images.html.json: item attributes changed
  • text tests/data/html/groundtruth/example_02.html.json: item attributes changed
  • text tests/data/html/groundtruth/example_03.html.json: item attributes changed
  • text tests/data/html/groundtruth/example_04.html.json: item attributes changed
  • text tests/data/html/groundtruth/example_05.html.json: item attributes changed
  • … 20 more file(s) not listed

Only the first 80 of 167 reference files were compared.

Assessment: expected. The reference data changes are expected because the backends now preserve boundary whitespace in inline runs.

The ai:* labels are advisory. They do not approve CI and they do not add tests:full. A new push runs this check again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI first review (advisory): blocker found

This review covers the full diff at e021aa9063. It read the code and did not run it.

This PR changes every backend so that inline runs carry their own boundary whitespace, requiring the new docling-core faithful-join semantics. pyproject.toml pins docling-core to a git branch only in [tool.uv.sources], which is a workspace-local dev override never shipped in the wheel. The published package still declares docling-core>=2.103.1, so any released docling-core would apply the old join that invents separators and renders the new runs incorrectly. The reviewer must confirm that docling-core#693 has been released and that the dependency floor in the wheel metadata is raised to that version before merge.

Findings outside the diff lines

  • blocker pyproject.toml:51: Dependency floor not raised: any released docling-core breaks the new whitespace contract
    Every backend in this PR emits inline runs that carry their own boundary whitespace and relies on docling-core joining them with "" (faithful join from docling-core#693). The [tool.uv.sources] pin to branch = "cau/serializer-whitespace-fixes" is a workspace dev override; it is not packaged into the wheel. The published wheel's metadata still says docling-core>=2.103.1,<3.0.0. A user who installs from PyPI gets any released docling-core, which uses the old serialiser that invents a " " separator between runs. The backends that previously stripped their runs will now emit un-stripped runs that the old serialiser joins with an extra space, producing doubled spaces throughout: "bold " + " tail" → "bold tail".

    Fix: wait until docling-core#693 is released, then raise the floor in dependencies and in the slim-package and lock-file entries (lines 51, 291, 343) to that new release. The PR description says this but the code does not yet contain it.

A maintainer makes the merge decision. This review never approves the pull request.

@github-actions github-actions Bot added the ai:review-changes AI first review suggests changes label Oct 26, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:ci-needs-care AI triage: check CI-safety findings before you approve CI ai:review-changes AI first review suggests changes asciidoc issue related to asciidoc backend docx issue related to docx backend enhancement New feature or request html issue related to html backend markdown issue related to markdown backend pptx issue related to pptx backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants