From f4c766cd178248e188312b9acd50bca372749528 Mon Sep 17 00:00:00 2001 From: chriscoveries <212684707+chriscoveries@users.noreply.github.com> Date: Sun, 4 Oct 2026 22:03:35 +0700 Subject: [PATCH] fix(docx): skip non-element XML nodes Signed-off-by: chriscoveries <212684707+chriscoveries@users.noreply.github.com> --- docling/backend/msword_backend.py | 2 ++ tests/test_backend_msword.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docling/backend/msword_backend.py b/docling/backend/msword_backend.py index dfcb50c99c..5c11e5fafb 100644 --- a/docling/backend/msword_backend.py +++ b/docling/backend/msword_backend.py @@ -1105,6 +1105,8 @@ def _walk_linear( ) -> tuple[DoclingDocument, list[RefItem]]: added_elements = [] for element in body: + if not isinstance(element.tag, str): + continue tag_name = etree.QName(element).localname # Check for Inline Images (blip elements) _raw_drawing_blip = self.blip_xpath_expr(element) diff --git a/tests/test_backend_msword.py b/tests/test_backend_msword.py index 05a4a63d2b..d0a47b234c 100644 --- a/tests/test_backend_msword.py +++ b/tests/test_backend_msword.py @@ -1472,6 +1472,28 @@ def test_malformed_hyperlink_does_not_abort_conversion(tmp_path): assert hyperlinks == [] +def test_non_element_nodes_do_not_abort_conversion(tmp_path): + doc = Document() + doc.add_paragraph("Before metadata.") + doc.element.body.insert(1, etree.Comment("synthetic comment")) + doc.element.body.insert( + 2, etree.ProcessingInstruction("synthetic", 'value="metadata"') + ) + doc.add_paragraph("After metadata.") + + docx_path = tmp_path / "non_element_nodes.docx" + doc.save(docx_path) + + in_doc = InputDocument( + path_or_stream=docx_path, + format=InputFormat.DOCX, + backend=MsWordDocumentBackend, + ) + result = in_doc._backend.convert() + + assert result.export_to_markdown() == "Before metadata.\n\nAfter metadata." + + def test_trailing_whitespace_run_keeps_paragraph_formatting(tmp_path): """A whitespace-only trailing run must not overwrite the paragraph's formatting.