Skip to content

fix(docx): read the active branch of a body-level mc:AlternateContent - #4615

Open
nikhilcrypto0 wants to merge 2 commits into
docling-project:mainfrom
nikhilcrypto0:fix/docx-body-alternate-content
Open

nikhilcrypto0 wants to merge 2 commits into
docling-project:mainfrom
nikhilcrypto0:fix/docx-body-alternate-content

Conversation

@nikhilcrypto0

Copy link
Copy Markdown

The DOCX body walk skipped a body-level mc:AlternateContent, so every paragraph in its supported mc:Choice branch was lost. This change walks one branch only: the first mc:Choice whose Requires prefixes map to namespaces the backend supports, otherwise mc:Fallback. Both branches are never read, so identical paragraphs inside the Choice are kept.

Issue resolved by this Pull Request:
Resolves #4611

This only covers the body-level case. The duplicate-paragraph loss inside text boxes (#4604) is handled in #4606 and is not touched here.

Checklist:

  • Documentation has been updated, if necessary. (Not needed: no user-facing option or API changed.)
  • Examples have been added, if necessary. (Not needed.)
  • Tests have been added, if necessary.

Review evidence:

Before: a body-level mc:AlternateContent produced only the paragraphs before and after it. The Choice paragraphs, including two identical ones, were missing.

After: the supported Choice paragraphs are present, identical paragraphs both stay, and the Fallback text is absent. If the Choice requires an unsupported namespace (for example w16se), the Fallback is read instead.

Checks run (from a clean uv sync --frozen --group dev --all-extras):

  • uv run pytest tests/test_backend_msword.py -k body_level_alternate_content: the two new tests fail on main without the fix and pass with it.
  • uv run pytest tests/test_backend_msword.py: 73 passed, 1 skipped.
  • uv run ruff check and uv run ruff format --check on the two changed files: clean.

Not run: mypy, the full test suite, and prek hooks. No reference ground-truth data changed. The new tests build the DOCX in code, so no new test file was added.

The body walk skipped mc:AlternateContent, so every paragraph in the supported mc:Choice branch was lost. Walk one branch only: the first Choice whose Requires prefixes map to supported namespaces, else the Fallback. This keeps identical paragraphs inside the Choice and never reads both branches.

Fixes docling-project#4611

Signed-off-by: nikhilcrypto0 <nikhilmittu1232@gmail.com>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

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

@mergify

mergify Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

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 Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/backend/msword_backend.py 92.30% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@dolfim-ibm dolfim-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.

Body-level AlternateContent holding a text box now emits the text twice.

The text-box detection at msword_backend.py:1123-1180 runs on the AlternateContent element with descendant XPaths before the new elif, then _walk_linear(branch) re-detects the same w:txbxContent on the Choice's child w:p. Input: body-level AlternateContent, Choice = w:p with wps:txbx → TEXTBOX_TEXT, Fallback = w:p with v:textbox → TEXTBOX_TEXT.

  • main: ['before', 'TEXTBOX_TEXT', 'after']
  • this PR: ['before', 'TEXTBOX_TEXT', 'TEXTBOX_TEXT', 'after']

Dispatching AlternateContent before the textbox/image detection (and skipping the rest of the loop body) avoids this; please add that case to the tests. (Note: on main the same detection already reads text boxes from both branches; this change makes it visible.)

Minor: Requires="wpg" / w15 fall back to mc:Fallback since only _OOXML_NAMESPACES counts as supported. That is fine, but worth a one-line comment.

The body-level mc:AlternateContent branch ran after the text-box and image detection, which looks at descendants. That detection read the whole element, and then again on the chosen branch's own paragraph, so a text box in the Choice came out twice. Dispatch mc:AlternateContent first, before that detection, and skip the rest of the loop body for it.

Add a test with a text box in both branches, and one for a block with no usable branch. Note in the helper that namespaces outside _OOXML_NAMESPACES, such as wpg or w15, yield to the Fallback.

Signed-off-by: nikhilcrypto0 <nikhilmittu1232@gmail.com>
@nikhilcrypto0

nikhilcrypto0 commented Oct 7, 2026 •

Copy link
Copy Markdown
Author

Thanks, you are right. Fixed in 9522f87.

I reproduced your input first. On the previous commit it gave ['before', 'TEXTBOX_TEXT', 'TEXTBOX_TEXT', '', 'after']. As you suggested, mc:AlternateContent is now dispatched at the top of the loop, before the image and text-box detection, and the rest of the loop body is skipped for it. The detection no longer runs on the whole element and then again on the chosen branch's paragraph.

The empty item in that list is how a paragraph that anchors a text box already comes out outside mc:AlternateContent (a plain paragraph with the same text box gives ['before', 'TEXTBOX_TEXT', '', 'after']), so the new test asserts that the text box is read exactly once, rather than the exact list.

Also in this push:

  • a one-line note in the helper that a Choice requiring a namespace outside _OOXML_NAMESPACES (such as wpg or w15) yields to the Fallback;
  • a test for a block with no usable branch, which was the one line Codecov reported as uncovered.

tests/test_backend_msword.py passes (75 passed, 1 skipped, the same skip as before) and ruff is clean.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PY-07-docling: docx: paragraphs inside a body-level mc:AlternateContent are dropped

2 participants