From 73634287824a15846a429f8545e2f41c33eb6777 Mon Sep 17 00:00:00 2001 From: Eric Lapouyade Date: Sun, 24 May 2020 11:52:03 +0200 Subject: [PATCH] v0.10.0 --- CHANGES.rst | 4 ++++ docs/index.rst | 19 ++++++++++++++++++- docxtpl/__init__.py | 17 +++++++++++------ tests/preserve_spaces.py | 14 ++++++++++++++ tests/templates/preserve_spaces_tpl.docx | Bin 0 -> 12431 bytes 5 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 tests/preserve_spaces.py create mode 100644 tests/templates/preserve_spaces_tpl.docx diff --git a/CHANGES.rst b/CHANGES.rst index 07dc889..653c985 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,7 @@ +0.10.0 (2020-05-25) +------------------- +- Fix spaces missing in some cases (#116, #227) + 0.9.2 (2020-04-26) ------------------- - Fix #271 diff --git a/docs/index.rst b/docs/index.rst index aeaf5a6..3c38391 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,8 +54,25 @@ If you want to manage paragraphs, table rows and a whole run with its style, you **Note:** -a 'run' for Microsoft Word is a sequence of characters with the same style. For example, if you create a paragraph with all characters of the same style, MS Word will create internally only one 'run' in the paragraph. Now, if you put in bold a text in the middle of this paragraph, word will transform the previous 'run' into 3 different 'runs' (normal - bold - normal). +a 'run' for Microsoft Word is a sequence of characters with the same style. +For example, if you create a paragraph with all characters of the same style, +MS Word will create internally only one 'run' in the paragraph. Now, +if you put in bold a text in the middle of this paragraph, +word will transform the previous 'run' into 3 different 'runs' (normal - bold - normal). +**Important:** + +Always put space after a jinja2 starting var/tag delimiter and a space before the ending one : + +Avoid:: + + {{myvariable}} + {%if something%} + +Use instead:: + + {{ myvariable }} + {% if something %} Extensions ++++++++++ diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 72755ce..3b55f16 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -7,7 +7,7 @@ Created : 2015-03-12 import functools import io -__version__ = '0.9.2' +__version__ = '0.10.0' from lxml import etree from docx import Document @@ -67,11 +67,17 @@ class DocxTemplate(object): fh.write(self.get_xml()) def patch_xml(self, src_xml): - # strip all xml tags inside {% %} and {{ }} that MS word can insert - # into xml source also unescape html entities + """ Make a lots of cleanning to have a raw xml understandable by jinja2 : + strip all unnecessary xml tags, manage table cell background color and colspan, + unescape html entities, etc... """ + + # replace {{ by {{ ( works with {{ }} {% and %} ) src_xml = re.sub(r'(?<={)(<[^>]*>)+(?=[\{%])|(?<=[%\}])(<[^>]*>)+(?=\})', '', src_xml, flags=re.DOTALL) + # replace {{jinja2 stuff}} by {{jinja2 stuff}} + # same thing with {% ... %} + # "jinja2 stuff" could a variable, a 'if' etc... anything jinja2 will understand def striptags(m): return re.sub('.*?(|]*>)', '', m.group(0), flags=re.DOTALL) @@ -101,9 +107,8 @@ class DocxTemplate(object): src_xml = re.sub(r'(](?:(?!]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?)', cellbg, src_xml, flags=re.DOTALL) - # avoid {{r and {%r tags to strip MS xml tags too far - # ensure space preservation when splitting - src_xml = re.sub(r'((?:(?!).)*)({{r\s.*?}}|{%r\s.*?%})', + # ensure space preservation + src_xml = re.sub(r'((?:(?!).)*)({{.*?}}|{%.*?%})', r'\1\2', src_xml, flags=re.DOTALL) src_xml = re.sub(r'({{r\s.*?}}|{%r\s.*?%})', diff --git a/tests/preserve_spaces.py b/tests/preserve_spaces.py new file mode 100644 index 0000000..07e0caf --- /dev/null +++ b/tests/preserve_spaces.py @@ -0,0 +1,14 @@ +from docxtpl import DocxTemplate, InlineImage + +# With old docxtpl version, "... for spicy ..." was replaced by "... forspicy..." +# This test is for checking that is some cases the spaces are not lost anymore + +tpl = DocxTemplate('templates/preserve_spaces_tpl.docx') + +tags = ['tag_1', 'tag_2'] +replacement = ['looking', 'too'] + +context = dict(zip(tags, replacement)) + +tpl.render(context) +tpl.save('output/preserve_spaces.docx') diff --git a/tests/templates/preserve_spaces_tpl.docx b/tests/templates/preserve_spaces_tpl.docx new file mode 100644 index 0000000000000000000000000000000000000000..4bef9dde4d18c5cf81ceed059cb6623bf0b5f417 GIT binary patch literal 12431 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