diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py b/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py index d6fa8db69..7a44890ce 100644 --- a/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py +++ b/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py @@ -7,6 +7,9 @@ from .math.omml import OMML_NS, oMath2Latex +WORD_NS = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}" +WORD_STYLE_TYPE_ATTR = WORD_NS + "type" + MATH_ROOT_TEMPLATE = "".join( ( " bytes: return str(soup).encode() +def _pre_process_styles(content: bytes) -> bytes: + root = ET.fromstring(content) + for style in list(root.findall(f".//{WORD_NS}style")): + if WORD_STYLE_TYPE_ATTR not in style.attrib: + root.remove(style) + return ET.tostring(root, encoding="utf-8", xml_declaration=True) + + def pre_process_docx(input_docx: BinaryIO) -> BinaryIO: """ Pre-processes a DOCX file with provided steps. @@ -136,6 +147,9 @@ def pre_process_docx(input_docx: BinaryIO) -> BinaryIO: "word/footnotes.xml", "word/endnotes.xml", ] + pre_process_style_files = [ + "word/styles.xml", + ] with zipfile.ZipFile(input_docx, mode="r") as zip_input: files = {name: zip_input.read(name) for name in zip_input.namelist()} with zipfile.ZipFile(output_docx, mode="w") as zip_output: @@ -150,6 +164,12 @@ def pre_process_docx(input_docx: BinaryIO) -> BinaryIO: except Exception: # If there is an error in processing the content, write the original content zip_output.writestr(name, content) + elif name in pre_process_style_files: + try: + updated_content = _pre_process_styles(content) + zip_output.writestr(name, updated_content) + except Exception: + zip_output.writestr(name, content) else: zip_output.writestr(name, content) output_docx.seek(0) diff --git a/packages/markitdown/tests/test_module_misc.py b/packages/markitdown/tests/test_module_misc.py index 4d62e4919..e25f6650c 100644 --- a/packages/markitdown/tests/test_module_misc.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -3,7 +3,9 @@ import os import re import shutil +import zipfile import pytest +from xml.etree import ElementTree as ET from unittest.mock import MagicMock from markitdown._uri_utils import parse_data_uri, file_uri_to_path @@ -274,6 +276,38 @@ def test_docx_equations() -> None: assert block_equations, "No block equations found in the document." +def test_docx_missing_style_type_does_not_crash(tmp_path) -> None: + docx_file = os.path.join(TEST_FILES_DIR, "test.docx") + malformed_docx = tmp_path / "missing-style-type.docx" + + namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + style_type_attr = f"{{{namespace}}}type" + + with zipfile.ZipFile(docx_file, mode="r") as zip_input: + with zipfile.ZipFile(malformed_docx, mode="w") as zip_output: + zip_output.comment = zip_input.comment + removed_style_type = False + + for item in zip_input.infolist(): + content = zip_input.read(item.filename) + if item.filename == "word/styles.xml": + root = ET.fromstring(content) + for style in root.findall(f".//{{{namespace}}}style"): + if style_type_attr in style.attrib: + del style.attrib[style_type_attr] + removed_style_type = True + break + content = ET.tostring(root, encoding="utf-8", xml_declaration=True) + + zip_output.writestr(item, content) + + assert removed_style_type + + result = MarkItDown().convert(str(malformed_docx)) + + assert "AutoGen: Enabling Next-Gen LLM Applications" in result.text_content + + def test_input_as_strings() -> None: markitdown = MarkItDown()