Skip to content

fix(jats): preserve styling in abstracts and footnotes - #4361

Merged
ceberam merged 8 commits into
docling-project:mainfrom
Kylinny:jats-abstract-footnote-styling
Oct 6, 2026
Merged

ceberam merged 8 commits into
docling-project:mainfrom
Kylinny:jats-abstract-footnote-styling

Conversation

@Kylinny

@Kylinny Kylinny commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Part of #3583 — covers the remaining Styling (bold, italic, superscript, ...) item.

Problem

The JATS backend flattened abstract paragraphs and footnotes to plain text via _get_text() (which even carries a TODO: apply styling according to child.tag), so <italic>, <bold>, <sub>/<sup>, <underline> and <strike> were silently dropped there — while body paragraphs already preserved them since #3726.

Changes

  • Abstract <p> elements are now walked with the paragraph implementation (_walk_linear, from feat(jats/inline-formula): Support emphasis elements and Inline formulas #3726) at emit time, so abstracts share the body-paragraph styling path instead of a parallel one. This covers plain and structured abstracts, including mixed content.
  • _add_footnote_group() parses each <fn> with the shared inline walker (_walk_inline_formula + _strip_segments, from feat(jats/inline-formula): Support emphasis elements and Inline formulas #3726), keeping the FOOTNOTE label on text runs (formulas stay FORMULA).
  • No abstract/footnote-specific emphasis or spacing workaround: inline-group spacing is delegated to the docling-core serializers per their contract (docling-core#693). Markdown export may show temporarily suboptimal spacing (e.g. CO 2, *in vitro* .) until docs: add integrations, revamp docs #693 merges; ground truths will be regenerated then.
  • Unstyled abstracts/footnotes keep the exact previous document structure (single text item).

Tests

  • tests/test_backend_jats.py: plain-abstract styling (italic/bold/nested/sub/sup/underline/strike), structured-abstract styling, footnote styling incl. FOOTNOTE labels, a no-styling structure-preservation test, and inline-run contract tests (no run fusion, no punctuation movement, source whitespace preserved; markdown output deliberately not asserted).
  • Regenerated the affected e2e groundtruth files (.md/.itxt/.json for pmc2231364, pntd.0008301, pone.0234687); the other two fixtures are untouched.
  • ruff check / ruff format --check clean; ty check shows only the 5 pre-existing warnings; tach check passes. (Full uv prek could not run locally due to disk constraints on this machine; the individual hooks were verified equivalently.)

Parse abstract paragraphs and footnotes into styled inline runs
(bold/italic/underline/strike/sub/sup) instead of flattening them
to plain text, and emit them via _emit_inline so formatting is
preserved in the document. Unstyled content keeps the previous
single-item structure.

Extends the JATS styling tests and regenerates the affected
e2e groundtruth files.

Part of docling-project#3583
@Kylinny Kylinny mentioned this pull request Sep 24, 2026
8 tasks done
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @Kylinny, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 2 merge protections satisfied — ready to merge.

Show 2 satisfied protections

🟢 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)(?:\(.+\))?(!)?:

🟢 Require two reviewer for test updates

When test data is updated, we require two reviewers

  • #approved-reviews-by >= 2

I, Junyi Yao <j.yao@wustl.edu>, hereby add my Signed-off-by to this commit: a9a0960

Signed-off-by: Junyi Yao <j.yao@wustl.edu>
The docling-core serializers join sibling inline text items with a
single space, so splitting a styled run off from adjacent text that
had no whitespace in the source XML (e.g. `<italic>in vitro</italic>.`
or `CO<sub>2</sub>`) exported spurious spaces (`in vitro .`, `CO 2`).
Strip-then-join also destroyed the information of whether a boundary
originally had whitespace.

Add `_coalesce_no_space_boundaries`, run between whitespace
normalization and boundary stripping in `_flattened_inline_runs`
(abstracts and footnotes). At each spaceless run boundary it:

1. moves trailing opening punctuation (`(`, `[`, quotes) forward
   onto the next run;
2. moves leading closing punctuation (`.`, `,`, `)`, ...) backward
   onto the previous run;
3. fuses the two runs, keeping the non-baseline formatting when
   exactly one run carries it.

Step 3 trades precise formatting scope for text fidelity: `CO2`
keeps its subscript on the fused run instead of exporting `CO 2`.
Formula runs and runs with different hyperlink targets are never
fused. The body-paragraph path (`_emit_inline`) is untouched.

Regenerate the three affected groundtruth fixtures; their exported
text (modulo styling markers) is now byte-identical to the pre-PR
output. Add `test_jats_plain_abstract_styling_keeps_source_spacing`
covering italic+period/comma, `CO<sub>2</sub>e`, `(ECM))<sup>-1</sup>`,
parens/brackets around styled runs, and ragged source whitespace.

Signed-off-by: Junyi Yao <j.yao@wustl.edu>
PeterStaar-IBM
PeterStaar-IBM previously approved these changes Sep 24, 2026

@PeterStaar-IBM PeterStaar-IBM left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm!

@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/backend/xml/jats_backend.py 93.33% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

- Sort import blocks (I001) in jats_backend.py and test_backend_jats.py.
- Mark intentional curly quotes in punctuation constants (RUF001 noqa).
- Condense docstrings/comments to bring jats_backend.py back under
  the 1500-line max-lines guardrail (1496 lines).

Signed-off-by: Junyi Yao <j.yao@wustl.edu>
@ceberam ceberam added the xml issue related to supported schema-specific XML formats label Sep 24, 2026

@ceberam ceberam left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @Kylinny for addressing this open topic for JATS articles.
Without entering into a detailed review, I noticed some aspects that you may want to have a look at:

  • The style checks still fail. Please, check the Contributor guidelines and ensure they pass locally before pushing to the remote server, using uv prek.

  • Reading through your inline comments I have the impression that you are implementing a workaround to an existing issue in docling-core: the space management of the inline groups in the serializers. This is an existing issue that is currently being addressed in PR docling-project/docling-core#693 and it involves the DoclingDocument, not the backend parsers. I would suggest that you stick to the following contract: respect the blank spaces in the inline groups and delegate the proper (markdown) export to docling-core serializers. The serialization will not be optimal at this point, but we will regenerate the ground-truth files once PR docling-project/docling-core#693 is finalized and merged. Please, do not re-implement the emphasis parsing just for the abstract section. Instead, reuse the current implementation for paragraphs.

  • Small correction to your statement:

    The JATS backend flattened abstract paragraphs and footnotes to plain text via _get_text()...were silently dropped there — while body paragraphs already preserved them since #4029.

    The emphasis in paragraphs is actually supported since PR #3726 . The code from this PR should be the basis of your implementation to support styling in abstracts and footnotes.

Address ceberam's review: drop the abstract/footnote-specific emphasis
and spacing workaround (_normalize_segments, _coalesce_no_space_boundaries,
_flattened_inline_runs) and reuse the paragraph styling implementation
from docling-project#3726 instead.

- Abstract <p> elements are now walked with _walk_linear at emit time,
  sharing the body-paragraph code path.
- Footnotes use _walk_inline_formula + _strip_segments directly.
- Inline-group spacing is delegated to the docling-core serializers per
  their contract (docling-core#693); no fusing or punctuation movement.
- Tests assert raw inline-run content/formatting instead of markdown output.
- Ground truths regenerated for the new (temporarily suboptimal) spacing.

Signed-off-by: Junyi Yao <j.yao@wustl.edu>
@Kylinny

Kylinny commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review, @ceberam — all points addressed in the latest push (15fdd1e):

  • Dropped the workaround entirely: _normalize_segments, _coalesce_no_space_boundaries, _flattened_inline_runs, and the punctuation constants are gone.
  • Reuse feat(jats/inline-formula): Support emphasis elements and Inline formulas #3726's implementation: abstract <p> elements are now walked with _walk_linear at emit time, so abstracts share the body-paragraph code path; footnotes use _walk_inline_formula + _strip_segments directly. No run fusion, no punctuation movement.
  • Spacing delegated to docling-core: per the docling-core#693 contract, inline runs keep their source whitespace and serialization spacing is the serializers' job. Markdown may look suboptimal until docs: add integrations, revamp docs #693 lands (e.g. CO 2, *in vitro* .); ground truths were regenerated accordingly and will be refreshed once more after docs: add integrations, revamp docs #693.
  • PR description corrected: paragraph emphasis support is attributed to feat(jats/inline-formula): Support emphasis elements and Inline formulas #3726, not feat(jats): preserve external hyperlinks #4029.
  • Tests now assert raw inline-run content/formatting (no fused runs, style scopes faithful) rather than markdown output.
  • Checks: ruff check, ruff format --check, tach check clean; ty check shows only the 5 pre-existing warnings; all 62 JATS tests pass. (I verified each prek hook individually — the full uv prek run wasn't possible on this machine due to disk space, but every hook it runs was checked equivalently.)

Comment thread tests/data/jats/groundtruth/pmc2231364.nxml.md Outdated
Signed-off-by: Junyi Yao <j.yao@wustl.edu>
@Kylinny
Kylinny force-pushed the jats-abstract-footnote-styling branch 2 times, most recently from 21270ae to c50b37b Compare October 2, 2026 19:34

@ceberam ceberam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @Kylinny for accepting the challenge and refactor the PR to strengthen the emphasis handling. I have some comments below, please check.

Comment thread docling/backend/xml/jats_backend.py Outdated
Comment thread docling/backend/xml/jats_backend.py Outdated
Comment thread docling/backend/xml/jats_backend.py Outdated
Comment thread docling/backend/xml/jats_backend.py Outdated
Signed-off-by: Junyi Yao <j.yao@wustl.edu>
Signed-off-by: Junyi Yao <j.yao@wustl.edu>
@Kylinny

Kylinny commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @ceberam for the detailed review! All four points are addressed:

  1. Footnotes now delegate to _emit_inline unconditionally, so single-segment footnotes skip the inline group (consistent with the abstract path) while keeping the FOOTNOTE label.
  2. Review-context comments removed; the docling-core#693 reference is kept to a single brief mention.
  3. Both docstrings trimmed to single-line Google style.
  4. _normalize_xml_line_breaks is now split/join based; import re and the regex constant are gone. One note: I kept collapsing consecutive line breaks to a single space to match the old regex behavior (e.g. "a\n\n b" -> "a b"), covered by the new parametrized test.

Also added the missing footnote single-segment test you pointed out. lint-and-type is green.

@ceberam ceberam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @Kylinny , all good now 🚀

@ceberam
ceberam merged commit 893fdd1 into docling-project:main Oct 6, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

xml issue related to supported schema-specific XML formats

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants