diff --git a/docs/index.rst b/docs/index.rst index 03aaef9..a6d545f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -252,11 +252,15 @@ And in your template, use the {{r notation:: Tables ------ -You can span table cells in two ways, horizontally (see tests/dynamic_table.py) by using:: +You can span table cells horizontally in two ways, by using ``colspan`` tag (see tests/dynamic_table.py):: {% colspan %} -or vertically within a for loop (see tests/vertical_merge.py):: +or within a for loop (see tests/horizontal_merge.py):: + + {% hm %} + +You can also merge cells vertically within a for loop (see tests/vertical_merge.py):: {% vm %} diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 574dc11..86aa160 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -98,8 +98,8 @@ class DocxTemplate(object): # add vMerge # use {% vm %} to make this table cell and its copies be vertically merged within a {% for %} - def tc(m): - def vMerge(m1): + def v_merge_tc(m): + def v_merge(m1): return ( '' + m1.group(1) + # Everything between ```` and ````. @@ -111,11 +111,61 @@ class DocxTemplate(object): ) return re.sub( r'(].*?)(.*?)(?:{%\s*vm\s*%})(.*?)()', - vMerge, + v_merge, 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) + src_xml = re.sub(r'](?:(?!]).)*?{%\s*vm\s*%}.*?]', v_merge_tc, src_xml, flags=re.DOTALL) + + # Use ``{% hm %}`` to make table cell become horizontally merged within + # a ``{% for %}``. + def h_merge_tc(m): + xml_to_patch = m.group() # Everything between ```` and ```` with ``{% hm %}`` inside. + + def with_gridspan(m1): + return ( + m1.group(1) + # ``w:gridSpan w:val="``. + '{{ ' + m1.group(2) + ' * loop.length }}' + # Content of ``w:val``, multiplied by loop length. + m1.group(3) # Closing quotation mark. + ) + + def without_gridspan(m2): + return ( + '' + + m2.group(1) + # Everything between ```` and ````. + m2.group(2) + # Everything before ``{% hm %}``. + m2.group(3) + # Everything after ``{% hm %}``. + m2.group(4) # ````. + ) + + if re.search(r'w:gridSpan', xml_to_patch): + # Simple case, there's already ``gridSpan``, multiply its value. + + xml = re.sub( + r'(w:gridSpan w:val=")(\d+)(")', + with_gridspan, + xml_to_patch, + flags=re.DOTALL, + ) + xml = re.sub( + r'{%\s*hm\s*%}', + '', + xml, # Patched xml. + flags=re.DOTALL, + ) + else: + # There're no ``gridSpan``, add one. + xml = re.sub( + r'(].*?)(.*?)(?:{%\s*hm\s*%})(.*?)()', + without_gridspan, + xml_to_patch, + flags=re.DOTALL, + ) + + # Discard every other cell generated in loop. + return "{% if loop.first %}" + xml + "{% endif %}" + + src_xml = re.sub(r'](?:(?!]).)*?{%\s*hm\s*%}.*?]', h_merge_tc, src_xml, flags=re.DOTALL) def clean_tags(m): return m.group(0).replace(r"‘","'").replace('<','<').replace('>','>') diff --git a/tests/horizontal_merge.py b/tests/horizontal_merge.py new file mode 100644 index 0000000..63610f1 --- /dev/null +++ b/tests/horizontal_merge.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +from docxtpl import DocxTemplate + +tpl = DocxTemplate('test_files/horizontal_merge_tpl.docx') +tpl.render({}) +tpl.save('test_files/horizontal_merge.docx') diff --git a/tests/test_files/cellbg.docx b/tests/test_files/cellbg.docx index 9633b29..a6d73fd 100644 Binary files a/tests/test_files/cellbg.docx and b/tests/test_files/cellbg.docx differ diff --git a/tests/test_files/dynamic_table.docx b/tests/test_files/dynamic_table.docx index 2ae4212..f122648 100644 Binary files a/tests/test_files/dynamic_table.docx and b/tests/test_files/dynamic_table.docx differ diff --git a/tests/test_files/embedded.docx b/tests/test_files/embedded.docx index 9a31c91..6ab8e60 100644 Binary files a/tests/test_files/embedded.docx and b/tests/test_files/embedded.docx differ diff --git a/tests/test_files/embedded_embedded_docx.docx b/tests/test_files/embedded_embedded_docx.docx index dd0cdd6..267c889 100644 Binary files a/tests/test_files/embedded_embedded_docx.docx and b/tests/test_files/embedded_embedded_docx.docx differ diff --git a/tests/test_files/escape.docx b/tests/test_files/escape.docx index 85d0abb..1bcdc3b 100644 Binary files a/tests/test_files/escape.docx and b/tests/test_files/escape.docx differ diff --git a/tests/test_files/escape_auto.docx b/tests/test_files/escape_auto.docx index 2f30dd6..9e7d5fd 100644 Binary files a/tests/test_files/escape_auto.docx and b/tests/test_files/escape_auto.docx differ diff --git a/tests/test_files/header_footer.docx b/tests/test_files/header_footer.docx index 0ce516c..dce45d1 100644 Binary files a/tests/test_files/header_footer.docx and b/tests/test_files/header_footer.docx differ diff --git a/tests/test_files/header_footer_entities.docx b/tests/test_files/header_footer_entities.docx index 288191b..519bc30 100644 Binary files a/tests/test_files/header_footer_entities.docx and b/tests/test_files/header_footer_entities.docx differ diff --git a/tests/test_files/header_footer_image.docx b/tests/test_files/header_footer_image.docx index 3a82849..aa0cf50 100644 Binary files a/tests/test_files/header_footer_image.docx and b/tests/test_files/header_footer_image.docx differ diff --git a/tests/test_files/header_footer_utf8.docx b/tests/test_files/header_footer_utf8.docx index ca76326..3c537c3 100644 Binary files a/tests/test_files/header_footer_utf8.docx and b/tests/test_files/header_footer_utf8.docx differ diff --git a/tests/test_files/horizontal_merge.docx b/tests/test_files/horizontal_merge.docx new file mode 100644 index 0000000..2b88603 Binary files /dev/null and b/tests/test_files/horizontal_merge.docx differ diff --git a/tests/test_files/horizontal_merge_tpl.docx b/tests/test_files/horizontal_merge_tpl.docx new file mode 100644 index 0000000..c738cd0 Binary files /dev/null and b/tests/test_files/horizontal_merge_tpl.docx differ diff --git a/tests/test_files/inline_image.docx b/tests/test_files/inline_image.docx index 4a25fe7..b583f0e 100644 Binary files a/tests/test_files/inline_image.docx and b/tests/test_files/inline_image.docx differ diff --git a/tests/test_files/nested_for.docx b/tests/test_files/nested_for.docx index 4b13e77..3c0f235 100644 Binary files a/tests/test_files/nested_for.docx and b/tests/test_files/nested_for.docx differ diff --git a/tests/test_files/order.docx b/tests/test_files/order.docx index 7fd1cb2..7af77f5 100644 Binary files a/tests/test_files/order.docx and b/tests/test_files/order.docx differ diff --git a/tests/test_files/replace_picture.docx b/tests/test_files/replace_picture.docx index a07a456..d0e6cfd 100644 Binary files a/tests/test_files/replace_picture.docx and b/tests/test_files/replace_picture.docx differ diff --git a/tests/test_files/richtext.docx b/tests/test_files/richtext.docx index 14a617e..c259e1b 100644 Binary files a/tests/test_files/richtext.docx and b/tests/test_files/richtext.docx differ diff --git a/tests/test_files/richtext_and_if.docx b/tests/test_files/richtext_and_if.docx index 4ca8242..e763287 100644 Binary files a/tests/test_files/richtext_and_if.docx and b/tests/test_files/richtext_and_if.docx differ diff --git a/tests/test_files/subdoc.docx b/tests/test_files/subdoc.docx index b5b52b4..a7a97b1 100644 Binary files a/tests/test_files/subdoc.docx and b/tests/test_files/subdoc.docx differ diff --git a/tests/test_files/template_error.docx b/tests/test_files/template_error.docx index 569aa1f..4a26529 100644 Binary files a/tests/test_files/template_error.docx and b/tests/test_files/template_error.docx differ diff --git a/tests/test_files/vertical_merge.docx b/tests/test_files/vertical_merge.docx index 1d89687..c8590b5 100644 Binary files a/tests/test_files/vertical_merge.docx and b/tests/test_files/vertical_merge.docx differ diff --git a/tests/test_files/vertical_merge_nested.docx b/tests/test_files/vertical_merge_nested.docx index e15bdce..33134de 100644 Binary files a/tests/test_files/vertical_merge_nested.docx and b/tests/test_files/vertical_merge_nested.docx differ diff --git a/tests/test_files/word2016.docx b/tests/test_files/word2016.docx index 1183b95..7808826 100644 Binary files a/tests/test_files/word2016.docx and b/tests/test_files/word2016.docx differ