Skip to content

fix(pptx): do not emit a Notes heading for a slide with no notes - #2427

Merged
afourney merged 2 commits into
microsoft:mainfrom
kevin9327:fix/pptx-empty-notes-heading
Sep 9, 2026
Merged

fix(pptx): do not emit a Notes heading for a slide with no notes#2427
afourney merged 2 commits into
microsoft:mainfrom
kevin9327:fix/pptx-empty-notes-heading

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

What this changes

A slide gets a ### Notes: heading with nothing under it whenever the deck has a notes slide
that carries no text:

<!-- Slide number: 1 -->
# Slide title

### Notes:

The heading announces a section that does not exist. Downstream that reads as a slide whose
speaker notes were dropped, rather than a slide that never had any.

Why

The converter treats "has a notes slide" as "has notes":

if slide.has_notes_slide:
    md_content += "\n\n### Notes:\n"
    notes_frame = slide.notes_slide.notes_text_frame
    if notes_frame is not None:
        md_content += notes_frame.text or ""
    md_content = md_content.strip()

Those are different things. PowerPoint writes a notesSlide part for a slide whose notes pane
has merely been opened, and a deck saved from a template can carry one for every slide, so
has_notes_slide is true for a great many slides with empty notes. The heading is emitted
before the text is read, so an empty (or whitespace-only) notes frame still gets one. The
trailing .strip() removes the newline after the heading but not the heading itself.

Fix

Read the notes first and only emit the heading when there is something to head. This is the
same shape as the fix in #1990, where WikipediaConverter stopped rendering a # None
heading for a page with no title.

Tests

New file packages/markitdown/tests/test_pptx_notes.py, over one-slide decks built in memory:

  • a notes slide whose text is "" produces no heading
  • a notes slide whose text is whitespace produces no heading
  • pinned: a slide with no notes slide at all is unaffected
  • pinned: real notes are still emitted, heading and all

Against unmodified main:

E  AssertionError: assert '### Notes:' not in '<!-- Slide number: 1 -->\n# Slide title\n\n### Notes:'
E  AssertionError: assert '### Notes:' not in '<!-- Slide number: 1 -->\n# Slide title\n\n### Notes:'
2 failed, 2 passed

With the fix:

4 passed

The test.pptx fixture emits no ### Notes: before or after this change, so the existing
vectors are untouched.

How I tested

Windows 11, Python 3.12, editable install of packages/markitdown[all].

pytest tests/test_pptx_notes.py   ->  2 failed, 2 passed  (before)
pytest tests/test_pptx_notes.py   ->  4 passed            (after)
pytest tests/                     ->  18 failed, 430 passed, 4 skipped

Those 18 are unchanged by this PR: main gives 18 failed, 426 passed, 4 skipped on this
machine before any edit, and the 4 added tests account for the difference. They are the CLI
stdout-encoding, Windows file-URI and speech-transcription tests that need a UTF-8 console, a
case-sensitive path and network/ffmpeg.

black --check clean on both files.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified locally (Arch Linux, Python 3.14.7, markitdown from source at b6e8bbd, python-pptx installed).

Reproduced before the fix. Applying only this PR's test file to main without the source change fails 2 tests, with the empty heading visible in the output:

assert '### Notes:' not in '<!-- Slide ...\n### Notes:'

So the heading really is emitted for a slide whose notes slide carries no text, matching the report.

After the fix: the PR's 4 tests pass. I also ran the wider pptx selection (-k pptx, ignoring test_outlook_msg_ansi.py which fails to collect here for an unrelated missing module): 14 passed with the change vs 12 passed / 2 failed on main. No regressions.

The change reads right to me: gating on notes_text.strip() instead of on has_notes_slide is the actual distinction, and it keeps the heading and its content emitted together rather than stripping afterwards.

Copilot AI 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.

🟡 Changes recommended

The OCR-enhanced PPTX converter retains the same empty-heading bug.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Prevents empty PPTX speaker-note headings by checking note content before rendering.

Changes:

  • Filters empty and whitespace-only notes.
  • Adds regression coverage for empty, absent, and populated notes.
File summaries
File Description
_pptx_converter.py Conditionally emits notes sections.
test_pptx_notes.py Adds notes-rendering tests.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +217 to +218
if notes_text.strip():
md_content += "\n\n### Notes:\n" + notes_text
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.

4 participants