From 798209b924abefaed315da3e3931433e1267ffd6 Mon Sep 17 00:00:00 2001 From: coderamaster Date: Wed, 21 Jan 2026 10:04:24 -0500 Subject: [PATCH] Feature: Add support for .dotx Word templates (#1532) - Added WML_TEMPLATE_MAIN content type constant - Updated Document() to accept both .docx and .dotx files - Added part factory mapping for template content type - Added tests for .dotx template loading - Fixes #1532 --- src/docx/__init__.py | 1 + src/docx/api.py | 2 +- src/docx/opc/constants.py | 3 +++ tests/test_dotx_template.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/test_dotx_template.py diff --git a/src/docx/__init__.py b/src/docx/__init__.py index fd06c84d2..3d5f34a15 100644 --- a/src/docx/__init__.py +++ b/src/docx/__init__.py @@ -44,6 +44,7 @@ def part_class_selector(content_type: str, reltype: str) -> Type[Part] | None: PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart PartFactory.part_type_for[CT.WML_COMMENTS] = CommentsPart PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart +PartFactory.part_type_for[CT.WML_TEMPLATE_MAIN] = DocumentPart PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart diff --git a/src/docx/api.py b/src/docx/api.py index aea876458..276bc58c0 100644 --- a/src/docx/api.py +++ b/src/docx/api.py @@ -25,7 +25,7 @@ def Document(docx: str | IO[bytes] | None = None) -> DocumentObject: """ docx = _default_docx_path() if docx is None else docx document_part = cast("DocumentPart", Package.open(docx).main_document_part) - if document_part.content_type != CT.WML_DOCUMENT_MAIN: + if document_part.content_type not in (CT.WML_DOCUMENT_MAIN, CT.WML_TEMPLATE_MAIN): tmpl = "file '%s' is not a Word file, content type is '%s'" raise ValueError(tmpl % (docx, document_part.content_type)) return document_part.document diff --git a/src/docx/opc/constants.py b/src/docx/opc/constants.py index a3d0e0812..30b856614 100644 --- a/src/docx/opc/constants.py +++ b/src/docx/opc/constants.py @@ -135,6 +135,9 @@ class CONTENT_TYPE: WML_DOCUMENT_MAIN = ( "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" ) + WML_TEMPLATE_MAIN = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml" + ) WML_ENDNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml" WML_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" WML_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml" diff --git a/tests/test_dotx_template.py b/tests/test_dotx_template.py new file mode 100644 index 000000000..14ea32714 --- /dev/null +++ b/tests/test_dotx_template.py @@ -0,0 +1,36 @@ +"""Tests for Issue #1532: .dotx template support.""" + +import os + +import pytest + +from docx import Document +from docx.package import Package +from docx.parts.document import DocumentPart + + +class DescribeDotxTemplate: + """Unit-test suite for .dotx file support.""" + + def it_can_load_a_dotx_template_file(self): + """Test loading a real .dotx file.""" + template_path = "test_dotx_real.dotx" + + if not os.path.exists(template_path): + pytest.skip(f"Template file {template_path} not found") + + # It should load the file successfully + doc = Document(template_path) + assert doc is not None + assert hasattr(doc, "paragraphs") + + def it_creates_document_part_for_dotx_files(self): + """Test that .dotx files create DocumentPart, not generic Part.""" + template_path = "test_dotx_real.dotx" + + if not os.path.exists(template_path): + pytest.skip(f"Template file {template_path} not found") + + pkg = Package.open(template_path) + # It should create a DocumentPart, not a generic Part + assert isinstance(pkg.main_document_part, DocumentPart) \ No newline at end of file