From df9e895b2be5a46317d7d312c2c9f4ae61b9d90e Mon Sep 17 00:00:00 2001
From: zxyasfas <138371657+zxyasfas@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:18:37 +0800
Subject: [PATCH] Expand self-closing tcPr before inserting gridSpan and shd
---
docxtpl/template.py | 14 +++++++++++++
tests/cellbg_selfclosing.py | 42 +++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 tests/cellbg_selfclosing.py
diff --git a/docxtpl/template.py b/docxtpl/template.py
index f20280a..3757a03 100644
--- a/docxtpl/template.py
+++ b/docxtpl/template.py
@@ -120,6 +120,13 @@ def colspan(m):
flags=re.DOTALL,
)
cell_xml = re.sub(r"", "", cell_xml, count=1)
+ # a self-closing would leave the inserted gridSpan outside of it
+ cell_xml = re.sub(
+ r"]*)?/>",
+ lambda mm: "" % (mm.group(1) or ""),
+ cell_xml,
+ count=1,
+ )
return re.sub(
r"(]*>)",
r'\1' % m.group(2),
@@ -143,6 +150,13 @@ def cellbg(m):
flags=re.DOTALL,
)
cell_xml = re.sub(r"", "", cell_xml, count=1)
+ # a self-closing would leave the inserted shd outside of it
+ cell_xml = re.sub(
+ r"]*)?/>",
+ lambda mm: "" % (mm.group(1) or ""),
+ cell_xml,
+ count=1,
+ )
return re.sub(
r"(]*>)",
r'\1' % m.group(2),
diff --git a/tests/cellbg_selfclosing.py b/tests/cellbg_selfclosing.py
new file mode 100644
index 0000000..35387ce
--- /dev/null
+++ b/tests/cellbg_selfclosing.py
@@ -0,0 +1,42 @@
+# Regression test for #559: pandoc-produced tables collapse empty cell
+# properties to a self-closing , which used to make cellbg and
+# colspan insert / as a sibling after it instead of
+# inside, so Word silently ignored them. No template file needed, the
+# checks work on patch_xml output directly.
+
+import re
+
+from docxtpl import DocxTemplate
+
+tpl = DocxTemplate("dummy.docx")
+
+
+def patched(body):
+ return tpl.patch_xml("%s" % body)
+
+
+def assert_inside_tcpr(xml, element):
+ m = re.search(r"]*>(.*?)", xml, flags=re.DOTALL)
+ assert m, "no expanded pair in: %s" % xml
+ assert element in m.group(1), "%s not inside : %s" % (element, xml)
+
+
+# self-closing (the #559 case)
+xml = patched("{% cellbg color %}x")
+assert_inside_tcpr(xml, "{% colspan span %}x")
+assert_inside_tcpr(xml, "{% cellbg color %}x')
+assert_inside_tcpr(xml, " keeps working as before
+xml = patched(
+ '{% cellbg color %}x'
+)
+assert_inside_tcpr(xml, "