Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions docling/backend/msword_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,9 +879,13 @@ def __init__(
if self.docx_obj:
self.valid = True
self.current_part = self.docx_obj.part
self._default_paragraph_style = self.docx_obj.styles.default(
WD_STYLE_TYPE.PARAGRAPH
)
self._normalize_style_types()
try:
self._default_paragraph_style = self.docx_obj.styles.default(
WD_STYLE_TYPE.PARAGRAPH
)
except Exception:
self._default_paragraph_style = None
# Build comment mappings after loading document
self._extract_comment_ranges()

Expand Down Expand Up @@ -4834,3 +4838,27 @@ def _get_comment_ids_for_element(self, element: BaseOxmlElement) -> set[str]:
comment_ids.add(comment_id)

return comment_ids

def _normalize_style_types(self) -> None:
"""Normalize non-standard style types in styles.xml.

Some third-party DOCX generators write ``w:type="list"`` instead of
``w:type="numbering"``. In python-docx, ``WD_STYLE_TYPE.LIST`` maps to
the XML attribute value ``"numbering"``. An unmapped XML value causes
``WD_STYLE_TYPE.from_xml`` to raise a ``ValueError`` when resolving
default styles or accessing unstyled paragraphs.
"""
if not self.docx_obj or not hasattr(self.docx_obj, "styles"):
return
styles_element = getattr(self.docx_obj.styles, "element", None)
if styles_element is None:
return
type_key = f"{_W_NS_CLARK}type"
for style_el in styles_element:
if style_el.tag == f"{_W_NS_CLARK}style" or style_el.tag.endswith("style"):
val = style_el.get(type_key)
if val == "list":
style_el.set(type_key, "numbering")
elif val is None and "type" in style_el.attrib:
if style_el.attrib["type"] == "list":
style_el.attrib["type"] = "numbering"
17 changes: 17 additions & 0 deletions tests/test_backend_msword.py
Original file line number Diff line number Diff line change
Expand Up @@ -2640,3 +2640,20 @@ def test_body_level_alternate_content_with_no_usable_branch_adds_nothing(tmp_pat
"Paragraph before AlternateContent",
"Paragraph after AlternateContent",
]


def test_docx_styles_xml_with_type_list_converts(tmp_path):
"""A style with w:type='list' does not crash default style lookup or conversion."""
document = Document()
document.add_heading("Quarterly results", level=1)
document.add_paragraph("Revenue grew 3%.") # paragraph without w:pStyle

style = OxmlElement("w:style")
style.set(qn("w:type"), "list")
style.set(qn("w:styleId"), "OutlineList")
document.styles.element.append(style)

doc = _convert_built(document, tmp_path)

texts = [item.text for item in doc.texts]
assert texts == ["Quarterly results", "Revenue grew 3%."]
Loading