diff --git a/docxtpl/template.py b/docxtpl/template.py index f20280a..7886480 100644 --- a/docxtpl/template.py +++ b/docxtpl/template.py @@ -183,7 +183,7 @@ def cellbg(m): # by {% xxx %} or {{ xx }} without any surrounding tags : # This is mandatory to have jinja2 generating correct xml code pat = ( - r"](?:(?!]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?" + r"](?:(?!]).)*({%%|{{)%(y)s ((?:(?!%%}|}}).)*(?:%%}|}})).*?" % {"y": y} ) src_xml = re.sub(pat, r"\1 \2", src_xml, flags=re.DOTALL) diff --git a/tests/patch_xml_modulo_brace.py b/tests/patch_xml_modulo_brace.py new file mode 100644 index 0000000..616dc2d --- /dev/null +++ b/tests/patch_xml_modulo_brace.py @@ -0,0 +1,44 @@ +# Regression test for the {%p %}/{%tr %}/{%tc %}/{%r %} relocation regex. +# +# patch_xml() moves a {%p ... %} (or tr/tc/r) directive out of its surrounding +# /// tags. The capture group used to be `[^}%]*`, which +# stopped at the first "%" or "}" *inside* the expression. As a result a +# directive whose Jinja expression contained a "%" (e.g. a modulo operation or a +# literal percent) or a "}" (e.g. a dict literal) was silently left in place, +# producing invalid XML. The expression is now captured up to the real closing +# "%}"/"}}", so such expressions round-trip correctly. + +from docxtpl import DocxTemplate + +# patch_xml() is a pure string transformation and never opens the file, so we +# can exercise it without a real .docx template. +tpl = DocxTemplate("dummy.docx") + +CASES = [ + # "%" inside the expression (modulo / literal percent) + ( + "{%p set x = a % b %}", + "{% set x = a % b %}", + ), + # "}" inside the expression (dict literal) + ( + "{%p set d = {'a': 1} %}", + "{% set d = {'a': 1} %}", + ), + # same fix for a table-row directive carrying a "%" + ( + "{%tr for x in items if x % 2 %}" + "", + "{% for x in items if x % 2 %}", + ), +] + +for src_xml, expected in CASES: + result = tpl.patch_xml(src_xml) + assert result == expected, "patch_xml(%r) -> %r, expected %r" % ( + src_xml, + result, + expected, + ) + +print("patch_xml_modulo_brace: OK")