Conversation
Signed-off-by: Cesar Berrospi Ramis <ceb@zurich.ibm.com>
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
|
✅ DCO Check Passed Thanks @ceberam, all your commits are properly signed off. 🎉 |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
DanielNg0729
left a comment
There was a problem hiding this comment.
Hi Dr @ceberam, thank you very much for this fix. I agree that the opt-out is a cleaner approach than the heuristic I proposed in #4124.
I tried the change locally with @kubasamanek's fixture from #4106. With the default setting, the output is the same as on main. With use_outline_level_for_headings=False, the clause bodies come out as TextItem, as intended. The new tests also pass for me, and the disabled-case test fails on main, so it protects the fix well.
While reading the change, I noticed a few small things. Please feel free to ignore them if I have misunderstood something:
- I may be wrong, but I think isinstance(self.options, MsWordBackendOptions) and self.options.use_outline_level_for_headings becomes False when the backend gets a different options object. I tried it with DeclarativeBackendOptions(), and the fallback was turned off even though the default is True. Would it make sense to write it as not isinstance(self.options, MsWordBackendOptions) or self.options.use_outline_level_for_headings, so the default is kept in that case?
- When the option is False, styles detected by name still take their level from w:outlineLvl. Would a short note in the field description help, so the option name doesn't surprise users?
- One small idea, only if you think it's useful: a test with the localized Nadpis 1 style from the reporter's fixture would show that those headings also become text when the option is off.
These are only small suggestions, and the PR looks good to me. Thank you very much again for your guidance on this issue!
Problem
Since #3961, the DOCX backend uses
w:outlineLvlas a fallback heading signal for styles that cannot be identified by name — the main motivation being localized heading styles produced by LibreOffice (e.g. CzechNadpis1). This fallback is correct for that case, butw:outlineLvlis not reserved exclusively for structural headings in OOXML: legal and regulatory templates routinely assign an outline level to paragraph styles that are used for numbered clause bodies, so that those paragraphs participate in the document outline and TOC without being structural headings. When such a document is converted, every paragraph of those styles is promoted to aSectionHeaderItem, which causes consumers that treat section headers as breadcrumbs rather than content to silently drop large portions of the document body.Solution
A new boolean option
use_outline_level_for_headings(defaultTrue) is added toMsWordBackendOptions. When set toFalse, thew:outlineLvl-only fallback path in_get_label_and_levelis skipped entirely. Name-based heading detection — which covers the built-in English styles and any style whose name or id contains the substring "heading" — is unaffected regardless of the option value.The default is kept at
Trueto preserve the existing behaviour for localized documents that depend on the fallback. Users who know their document usesw:outlineLvlon non-heading styles can opt out explicitly.The known limitation (a style used for both outline participation and body prose cannot be distinguished from a true heading style using OOXML signals alone) is documented in the
Fielddescription and in the_get_label_and_leveldocstring.Changes
docling/datamodel/backend_options.py— addsuse_outline_level_for_headings: bool = TruetoMsWordBackendOptions, using theAnnotated+Fieldpattern with a description that explains the purpose and known limitation.docling/backend/msword_backend.py— gates thew:outlineLvl-only fallback on the new option; documents the known limitation in the_get_label_and_leveldocstring.tests/test_backend_msword_outline.py— adds amixed_heading_docxpytest fixture that builds a single document containing a named heading (Heading 1), two outline-level-only paragraphs (Level3style), and a plain body paragraph; two tests exerciseuse_outline_level_for_headings=Trueand=Falseagainst that same fixture.Testing
Closes #4106.
Checklist: