From 5ae0dbbf2984fb0701829daa2147b7ce39010cae Mon Sep 17 00:00:00 2001 From: Nick Gashkov Date: Fri, 31 Aug 2018 18:57:54 +0300 Subject: [PATCH] Fix broken tables caused by vertical merge inside complex nested tables Updated regex pattern to make '{% vm %}' replace a two-step process: - Search for ...{% vm %}...; - Parse table cell and put jinja tags inside correct places. --- docxtpl/__init__.py | 29 ++++++++++++------ tests/test_files/vertical_merge_nested.docx | Bin 0 -> 5039 bytes .../test_files/vertical_merge_nested_tpl.docx | Bin 0 -> 5198 bytes tests/vertical_merge_nested.py | 5 +++ 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tests/test_files/vertical_merge_nested.docx create mode 100644 tests/test_files/vertical_merge_nested_tpl.docx create mode 100644 tests/vertical_merge_nested.py diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 99d28cc..b5bb828 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -88,17 +88,28 @@ class DocxTemplate(object): # This is mandatory to have jinja2 generating correct xml code pat = r'](?:(?!]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?' % {'y':y} src_xml = re.sub(pat, r'\1 \2',src_xml,flags=re.DOTALL) - + # add vMerge # use {% vm %} to make this table cell and its copies be vertically merged within a {% for %} - pat_vm = r'(<\/w:tcPr>.*)(<\/w:tcPr>)(.*?){%\s*vm\s*%}.*?(.*?)(<\/w:t>)' - def vMerge(m): - return m.group(1) + '' + m.group(2) + m.group(3) + "{% if loop.first %}"+ m.group(4) +"{% endif %}" + m.group(5) - pat_num_vm = re.compile(r'{%\s*vm\s*%}') - num = len(pat_num_vm.findall(src_xml)) - for i in range(0,num): - src_xml = re.sub(pat_vm,vMerge,src_xml) - + def tc(m): + def vMerge(m1): + return ( + '' + + m1.group(1) + # Everything between ```` and ````. + "{% if loop.first %}" + + m1.group(2) + # Everything before ``{% vm %}``. + m1.group(3) + # Everything after ``{% vm %}``. + "{% endif %}" + + m1.group(4) # ````. + ) + return re.sub( + r'(.*?)(.*?)(?:{%\s*vm\s*%})(.*?)()', + vMerge, + m.group(), # Everything between ```` and ```` with ``{% vm %}`` inside. + flags=re.DOTALL, + ) + src_xml = re.sub(r'](?:(?!]).)*?{%\s*vm\s*%}.*?]', tc, src_xml, flags=re.DOTALL) + def clean_tags(m): return m.group(0).replace(r"‘","'").replace('<','<').replace('>','>') src_xml = re.sub(r'(?<=\{[\{%])(.*?)(?=[\}%]})',clean_tags,src_xml) diff --git a/tests/test_files/vertical_merge_nested.docx b/tests/test_files/vertical_merge_nested.docx new file mode 100644 index 0000000000000000000000000000000000000000..d8fe9760b6f97a1a311c029ea860e0ba95bff15d GIT binary patch literal 5039 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 literal 0 HcmV?d00001 diff --git a/tests/vertical_merge_nested.py b/tests/vertical_merge_nested.py new file mode 100644 index 0000000..f2eced8 --- /dev/null +++ b/tests/vertical_merge_nested.py @@ -0,0 +1,5 @@ +from docxtpl import DocxTemplate + +tpl = DocxTemplate('test_files/vertical_merge_nested.docx') +tpl.render() +tpl.save('test_files/word2016.docx')