Skip to content

fix(pptx): preserve run-level hyperlinks and character formatting - #4443

Open
avanish-garg wants to merge 4 commits into
docling-project:mainfrom
avanish-garg:fix/pptx-drops-hyperlinks-and-formatting
Open

avanish-garg wants to merge 4 commits into
docling-project:mainfrom
avanish-garg:fix/pptx-drops-hyperlinks-and-formatting

Conversation

@avanish-garg

Copy link
Copy Markdown

Issue resolved by this Pull Request:
Resolves #4442

Splits each paragraph into (text, formatting, hyperlink) tuples per run, mirroring
msword_backend.py's existing _iter_paragraph_content/_get_format_from_run/
_get_hyperlink_target pattern (python-pptx's public Font API only exposes bold/italic/underline,
unlike python-docx's richer run API, so strikethrough/sub/superscript aren't in scope here):

  • Adjacent runs sharing identical formatting/hyperlink are coalesced back into one unit first,
    since PowerPoint sometimes splits a single word across runs with no actual formatting difference
    (e.g. spell-check markup) — without this, wrapping each such run in its own item would let the
    markdown serializer's inter-sibling space (InlineGroup always joins children with " ") split
    real words apart.
  • A paragraph with more than one run is wrapped in an inline group, mirroring the DOCX backend's
    own _create_or_reuse_parent, so a sentence with a bold word or a link in the middle stays one
    flowing unit on export instead of splintering into separate markdown blocks.
  • Title/section-header paragraphs are kept as a single item exactly as before — a heading is one
    outline-level unit, not a sequence of independently formatted spans.
  • Each item's own provenance is now built from its own text rather than the whole paragraph's, so
    charspan stays correct once a paragraph can produce more than one item (python-pptx exposes no
    finer bbox than the shape's, so the bbox itself stays shape-level for every item in it, same as
    before).

Changes: docling/backend/mspowerpoint_backend.py gets three new helpers
(_get_run_format/_get_run_hyperlink_target/_iter_paragraph_runs) plus a
_add_list_item_with_formatting helper for the list case (mirroring msword_backend.py's
_add_formatted_list_item), and _handle_text_elements is rewired to use them.
tests/test_backend_pptx.py gets a new regression test,
test_pptx_preserves_run_level_formatting_and_hyperlinks, covering a bold run, a plain run, and a
hyperlinked run in one paragraph — verified to fail on unmodified main (all three collapse into
one plain-text item) and pass with the fix.

Golden fixtures regenerated (DOCLING_GEN_TEST_DATA=1). Every diff was reviewed by hand and is
exactly the fixed behavior: a real markdown hyperlink where the URL used to be silently dropped
(powerpoint_comments.pptx.md), a real bold marker where formatting used to be silently dropped
(powerpoint_issue_2663.pptx.md, **Key Benefits:**), and an explicit
"formatting": {"bold": false, ...} block now present on plain-text items where the field was
previously just absent — no text content changed anywhere, no unrelated diffs. (One golden JSON's
embedded base64 image bytes also changed on first regeneration; confirmed via a clean regen on
unmodified main that this is a pre-existing Pillow/PNG-encoder version difference in this
environment, unrelated to this fix, and reverted just that field back to its original value before
committing.)

Full tests/test_backend_pptx.py suite: 30 passed, 2 skipped (pre-existing, LibreOffice-gated), no
regressions. ruff check/ruff format --check: clean. ty check: same 7 pre-existing warnings as
unmodified main (confirmed via diff), no new ones, none on the changed lines.

Checklist:

  • Documentation has been updated, if necessary. — n/a, no user-facing docs describe PPTX
    run-formatting handling to update
  • Examples have been added, if necessary. — n/a
  • Tests have been added, if necessary.

@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @avanish-garg, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Sep 29, 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 Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/backend/mspowerpoint_backend.py 90.00% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@avanish-garg
avanish-garg force-pushed the fix/pptx-drops-hyperlinks-and-formatting branch from d13aba6 to 7e92359 Compare October 7, 2026 13:25
@ceberam ceberam added bug Something isn't working pptx issue related to pptx backend labels Oct 7, 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 @avanish-garg for this PR, since indeed the PPTX backend was not yet keeping the formatting unlike the other Office backends.
The new code and tests are technically sound and functionally correct.

My only recommendation would be to clean up the excessive inline commentary and verbose private docstrings.
There are multiple lengthy inline comment blocks that explain why previous behavior was buggy, detail developer rationale, or repeat method-level documentation. The inline comments in Docling should also limit explanations on how third-party applications like PowerPoint work, since developers and maintainers can refer to the documentation of those applications directly, if necessary.

@avanish-garg

Copy link
Copy Markdown
Author

Thanks for the review @ceberam. Done with the changes!

ceberam
ceberam previously approved these changes Oct 7, 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 @avanish-garg for addressing my comment.
All good from my side.

dolfim-ibm
dolfim-ibm previously approved these changes Oct 8, 2026
@dolfim-ibm

Copy link
Copy Markdown
Member

@avanish-garg do you mind having a last round fixing the conflict with main?

@avanish-garg
avanish-garg dismissed stale reviews from dolfim-ibm and ceberam via 0d3aed1 October 8, 2026 13:58
@avanish-garg
avanish-garg force-pushed the fix/pptx-drops-hyperlinks-and-formatting branch from 0c25dba to 0d3aed1 Compare October 8, 2026 13:58
@github-actions github-actions Bot added the ai:ci-safe AI triage found no CI-safety concern (advisory) label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

AI triage (advisory)

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

This pull request preserves run-level character formatting and hyperlinks in the PowerPoint backend. It wraps multi-run paragraphs into inline groups.

CI safety: no concern found. A maintainer must still approve CI.

1 related pull request(s) in the same code
PR State Signal Assessment
#4787 merged also references issue #4442 related: PR #4787 handles hyperlinks on entire shapes, while this pull request handles formatting and hyperlinks on individual text runs.

Reference data changes

Change Files
structure 1
text 8
  • structure tests/data/pptx/groundtruth/powerpoint_comments.pptx.json: paragraph: 2 → 4
  • text tests/data/pptx/groundtruth/powerpoint_comments.pptx.itxt: +10 / -7 lines
  • text tests/data/pptx/groundtruth/powerpoint_comments.pptx.md: +1 / -1 lines
  • text tests/data/pptx/groundtruth/powerpoint_issue_2663.pptx.json: item attributes changed
  • text tests/data/pptx/groundtruth/powerpoint_issue_2663.pptx.md: +2 / -2 lines
  • text tests/data/pptx/groundtruth/powerpoint_malformed_pictures.pptx.json: item attributes changed
  • text tests/data/pptx/groundtruth/powerpoint_sample.pptx.json: item attributes changed
  • text tests/data/pptx/groundtruth/powerpoint_unrecognized_shape.pptx.json: item attributes changed
  • text tests/data/pptx/groundtruth/powerpoint_with_image.pptx.json: item attributes changed

Assessment: expected. The reference data changes match the new paragraph run splitting and inline group structure in PowerPoint documents.

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

ceberam
ceberam previously approved these changes Oct 8, 2026
@avanish-garg
avanish-garg force-pushed the fix/pptx-drops-hyperlinks-and-formatting branch from 0d3aed1 to 2513684 Compare October 9, 2026 06:12
@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) and removed ai:ci-safe AI triage found no CI-safety concern (advisory) labels Oct 9, 2026
@avanish-garg
avanish-garg requested a review from ceberam October 9, 2026 07:53

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

@avanish-garg I've just seen a blocking issue and added a couple of suggestions worth addressing.

Comment thread docling/backend/mspowerpoint_backend.py Outdated

def _get_run_hyperlink_target(self, run: _Run) -> Optional[AnyUrl | Path]:
"""Resolve a run's hyperlink to a URL or local path, or ``None``."""
address = run.hyperlink.address

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.

_get_run_hyperlink_target does not guard against KeyError from a dangling relationship. run.hyperlink.address calls part.target_ref(rId) internally, which raises KeyError if the relationship ID no longer exists in the file, a condition that occurs with sanitized or re-saved PPTX files. The DOCX backend's counterpart explicitly catches this. Without the guard, a single malformed hyperlink crashes the entire document conversion.

Fix:

Suggested change
address = run.hyperlink.address
try:
address = run.hyperlink.address
except KeyError:
return None

Comment thread docling/backend/mspowerpoint_backend.py Outdated
_log.warning("Skipping malformed hyperlink address: %r", address)
return None

def _iter_paragraph_runs(self, paragraph) -> list[_ParagraphRun]:

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.

  • Minor performance comment: _iter_paragraph_runs is called twice per shape: once in the early-return guard (result discarded) and once inside _handle_text_elements. The old _paragraph_fragments was cheap; the new method does font inspection and hyperlink resolution per run.
  • Add type annotation to paragraph

The PPTX text-extraction loop built each paragraph's text via plain
string concatenation (p_text += e.text) and handed the whole thing to
a single add_text/add_list_item call, discarding every run's own
hyperlink and bold/italic/underline formatting - the same information
DOCX/HTML/ODF/MD backends already preserve per run.

Splits each paragraph into (text, formatting, hyperlink) tuples per
run, mirroring msword_backend.py's existing _iter_paragraph_content/
_get_format_from_run/_get_hyperlink_target pattern:

- Adjacent runs with identical formatting/hyperlink are coalesced back
  into one unit first, since PowerPoint sometimes splits a single word
  across runs with no actual formatting difference (e.g. spell-check
  markup) - without this, wrapping each such run in its own item would
  let the markdown serializer's inter-sibling space split real words.
- A paragraph with more than one run is wrapped in an inline group
  (mirroring the DOCX backend's own _create_or_reuse_parent), so a
  sentence with a bold word or a link in the middle stays one flowing
  unit on export instead of splintering into separate blocks.
- Title/section-header paragraphs are kept as a single item as before:
  a heading is one outline-level unit, not a sequence of independently
  formatted spans.
- Each item's own provenance is now built from its own text rather
  than the whole paragraph's, so charspan stays correct once a
  paragraph can produce more than one item (python-pptx exposes no
  finer bbox than the shape's, so the bbox itself stays shape-level).

New test: test_pptx_preserves_run_level_formatting_and_hyperlinks,
covering a bold run, a plain run, and a hyperlinked run in one
paragraph. Golden fixtures regenerated; diffs are exactly the fixed
behavior (a real markdown hyperlink where the URL was silently
dropped, formatting: {...} now present throughout) with no unrelated
changes.

Native Office Math equations (docling-project#4625) keep their own FORMULA items; the text
runs around them now carry their run-level formatting and hyperlinks too.

Signed-off-by: Avanish Garg <gargavanish@gmail.com>
Signed-off-by: Avanish Garg <gargavanish@gmail.com>
A run hyperlink pointing at a missing, empty or absent relationship id
made python-pptx raise KeyError and failed the whole conversion. Treat
such a link as absent and keep the run text.

Split each paragraph into runs once per shape instead of once for the
empty-shape check and again when building items, and annotate the
paragraph parameter.

Signed-off-by: Avanish Garg <gargavanish@gmail.com>
@avanish-garg
avanish-garg force-pushed the fix/pptx-drops-hyperlinks-and-formatting branch from 2513684 to ed1a75b Compare October 14, 2026 13:20
@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) and removed ai:ci-safe AI triage found no CI-safety concern (advisory) labels Oct 14, 2026
@PeterStaar-IBM

Copy link
Copy Markdown
Member

@mittalpk please fix the linting

@mittalpk

Copy link
Copy Markdown
Contributor

@PeterStaar-IBM I think this one is for @avanish-garg, since #4443 is his branch; my #4787 only references it. The lint failure is the max-lines hook: tests/test_backend_pptx.py is at 1547 lines. Moving the new tests into their own file fixes it, as I did for the same failure in #4733.

tests/test_backend_pptx.py went over the 1500-line limit enforced by the
max-lines hook.

Signed-off-by: Avanish Garg <gargavanish@gmail.com>
@github-actions github-actions Bot added ai:ci-safe AI triage found no CI-safety concern (advisory) and removed ai:ci-safe AI triage found no CI-safety concern (advisory) labels Oct 15, 2026
@avanish-garg
avanish-garg requested a review from ceberam October 16, 2026 13:21
@avanish-garg

Copy link
Copy Markdown
Author

Done with lint changes
THanks @PeterStaar-IBM @mittalpk

@avanish-garg

Copy link
Copy Markdown
Author

Any update on this @ceberam ?

@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 @avanish-garg ! Approved.
Just a non-blocking message though below if you can check it out.

Comment on lines +190 to +191
# ``(label, text, formatting, hyperlink)``; equations are FORMULA entries with
# neither formatting nor hyperlink.

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.

What are these inline comments referring to? Or they should just be removed?

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-safe AI triage found no CI-safety concern (advisory) bug Something isn't working pptx issue related to pptx backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PPTX backend drops every run-level hyperlink and character formatting (bold/italic/underline)

5 participants