Skip to content

fix(docx): clamp Word Heading 7-9 to h6 - #2555

Open
stanleys12 wants to merge 1 commit into
microsoft:mainfrom
stanleys12:osc/docx-heading-styles-7-9-silently-lose-al-2105
Open

stanleys12 wants to merge 1 commit into
microsoft:mainfrom
stanleys12:osc/docx-heading-styles-7-9-silently-lose-al-2105

Conversation

@stanleys12

Copy link
Copy Markdown

Word defines Heading styles 1 through 9, but mammoth's default style map stops at Heading 6 and DocxConverter adds nothing beyond it, so paragraphs styled Heading 7, 8 or 9 come through as plain text and lose their heading structure entirely.

A document with one paragraph per heading level converts like this on main:

# Level 1
## Level 2
### Level 3
#### Level 4
##### Level 5
###### Level 6
Level 7
Level 8
Level 9

Nothing warns about it, and downstream the last three levels read as body text rather than as sections. Deep heading levels are common in legal and technical-spec documents, which is the kind of long structured input this library is usually pointed at.

Fix

Markdown has no heading level past 6, so clamp 7-9 to h6 instead of dropping them: a new _DEEP_HEADING_STYLE_MAP is joined into the style map alongside _UNDERLINE_STYLE_MAP. It uses both selector forms that mammoth's default map already uses for levels 1-6:

p.Heading7 => h6:fresh
...
p[style-name='Heading 7'] => h6:fresh

Both are needed. mammoth matches style ids exactly, so the p.HeadingN form is what catches a document with no stylesheet part; it matches style names case-insensitively, so the style-name form is what catches the non-English style ids a localized Word writes (Titre7, Überschrift7) via <w:name w:val="heading 7"/>. The entries go after the caller-supplied and embedded maps in the existing join, so a caller passing style_map= still overrides the clamp, the same way it overrides the u => u default.

After:

# Level 1
...
###### Level 6
###### Level 7
###### Level 8
###### Level 9

Documents that do not use Heading 7-9 are unaffected.

Tests

New file packages/markitdown/tests/test_docx_headings.py, over nine-heading documents built in memory:

  • levels 1-9 with the style ids Word writes and no stylesheet part, matched by style id
  • levels 1-9 with localized style ids resolved through a stylesheet, matched by style name
  • pinned: a caller-supplied style_map still outranks the clamp

Against unmodified main:

$ python -m pytest tests/test_docx_headings.py -q
E  AssertionError: assert ['# Level 1',...Level 6', ...] == ['# Level 1',...Level 6', ...]
E    At index 6 diff: 'Level 7' != '###### Level 7'
2 failed, 1 passed in 0.91s

With the fix:

$ python -m pytest tests/test_docx_headings.py -q
3 passed in 0.55s

The third test passes before and after; it is there so the ordering inside the style map join stays overridable.

Verification

  • packages/markitdown: python -m pytest tests -q -> 998 passed, 14 skipped
  • packages/markitdown-ocr: python -m pytest -q -> 108 passed (DocxConverterWithOCR reuses the core pipeline, so the clamp reaches it too)
  • black --check packages/ with black 23.7.0 -> 107 files unchanged; git diff --check clean

Run on macOS / CPython 3.13; the CI matrix was not reproduced locally.

Fixes #2530. This is the heading part of #2531, which was closed because it was bundled with unrelated whitespace changes; rebuilt here on its own.

AI assistance (Claude) was used to draft this change; the full packages/markitdown test suite (998 passed) and black --check were run locally.

Mammoth's default style map covers Heading 1-6, so paragraphs styled
Heading 7, 8 or 9 fall through as plain text and lose their heading
structure. Markdown has no level past 6, so add style map entries that
clamp those levels to h6, in both the style id and style name forms the
default map uses.
@stanleys12

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

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.

DOCX: Heading 7-9 styles lose heading structure entirely (render as plain paragraphs)

1 participant