diff --git a/CHANGES.rst b/CHANGES.rst index 33b3165..a81d7cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,17 +1,10 @@ +0.11.0 (2020-10-19) +------------------- +- \\n, \\a, \\t and \\f are now accepted in simple context string. Thanks to chabErch@github + 0.10.5 (2020-10-15) ------------------- -- Revert 0.10.4 as the XML code get corrupted - -0.10.4 (2020-10-15) -------------------- -- \\n \\t and \\f are now accepted in simple context string (#307, #312) - -0.10.1 (2020-08-23) -------------------- - Remove extension testing (#297) - -0.10.0 (2020-05-25) -------------------- - Fix spaces missing in some cases (#116, #227) 0.9.2 (2020-04-26) @@ -19,16 +12,9 @@ - Fix #271 - Code styling -0.9.0 (2020-04-15) -------------------- -- New syntax : {%- and -%} to merge lines/paragraphs - 0.8.1 (2020-04-14) ------------------- - fix #266 - -0.8.0 (2020-04-10) -------------------- - docxtpl is now able to use latest python-docx (0.8.10). Thanks to Dutchy-@github. 0.7.0 (2020-04-09) diff --git a/docs/index.rst b/docs/index.rst index 29291fa..16c844e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -142,6 +142,8 @@ As part of jinja2, one can used double braces:: {{ }} +if ```` is a string, ``\n``, ``\a``, ``\t`` and ``\f`` will be translated respectively into newlines, new paragraphs, tabs and page breaks + But if ```` is a RichText_ object, you must specify that you are changing the actual 'run':: {{r }} @@ -240,7 +242,7 @@ especially ``<``, ``>`` and ``&``. In order to use them, you must escape them. T * ``context = { 'var':escape('my text')}`` and ``{{ }}`` in the template. * enable autoescaping when calling render method: ``tpl.render(context, autoescape=True)`` (default is autoescape=False) -The ``RichText()`` or ``R()`` offers newline, new paragraph, and page break features : just use ``\n``, ``\a``, or ``\f`` in the +The ``RichText()`` or ``R()`` offers newline, new paragraph, and page break features : just use ``\n``, ``\a``, ``\t`` or ``\f`` in the text, they will be converted accordingly. See tests/escape.py example for more informations. diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 1bef924..51c1646 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -4,11 +4,10 @@ Created : 2015-03-12 @author: Eric Lapouyade ''' +__version__ = '0.11.0' + import functools import io - -__version__ = '0.10.5' - from lxml import etree from docx import Document from docx.opc.oxml import parse_xml @@ -28,11 +27,6 @@ import binascii import os import zipfile -NEWLINE_XML = '' -NEWPARAGRAPH_XML = '' -TAB_XML = '' -PAGE_BREAK = '' - class DocxTemplate(object): """ Class for managing docx files as they were jinja2 templates """ @@ -234,8 +228,42 @@ class DocxTemplate(object): .replace('}_}', '}}') .replace('{_%', '{%') .replace('%_}', '%}')) + dst_xml = self.resolve_listing(dst_xml) return dst_xml + def resolve_listing(self, xml): + + def resolve_text(run_properties, paragraph_properties, m): + xml = m[0].replace('\t', '' + '%s' + '%s' % (run_properties, run_properties)) + xml = xml.replace('\a', '' + '%s%s' % (paragraph_properties, run_properties)) + xml = xml.replace('\n', '') + xml = xml.replace('\f', '' + '' + '%s%s' % (paragraph_properties, run_properties)) + return xml + + def resolve_run(paragraph_properties, m): + run_properties = re.search(r'.*', m[0]) + run_properties = run_properties[0] if run_properties else '' + + p_resolve_text = lambda x:resolve_text(run_properties, paragraph_properties, x) + return re.sub(r']*)?>.*?', p_resolve_text, m[0], flags=re.DOTALL) + + def resolve_paragraph(m): + paragraph_properties = re.search(r'.*', m[0]) + paragraph_properties = paragraph_properties[0] if paragraph_properties else '' + + p_resolve_run = lambda x:resolve_run(paragraph_properties, x) + + return re.sub(r']*)?>.*?', p_resolve_run, m[0], flags=re.DOTALL) + + xml = re.sub(r']*)?>.*?', resolve_paragraph, xml, flags=re.DOTALL) + + return xml + def build_xml(self, context, jinja_env=None): xml = self.get_xml() xml = self.patch_xml(xml) @@ -684,11 +712,7 @@ class RichText(object): text = six.text_type(text) if not isinstance(text, six.text_type): text = text.decode('utf-8', errors='ignore') - text = (escape(text) - .replace('\n', NEWLINE_XML) - .replace('\a', NEWPARAGRAPH_XML) - .replace('\t', TAB_XML) - .replace('\f', PAGE_BREAK)) + text = escape(text) prop = u'' @@ -756,11 +780,7 @@ class Listing(object): # If not a string : cast to string (ex: int, dict etc...) if not isinstance(text, (six.text_type, six.binary_type)): text = six.text_type(text) - self.xml = (escape(text) - .replace('\n', NEWLINE_XML) - .replace('\a', NEWPARAGRAPH_XML) - .replace('\t', TAB_XML) - .replace('\f', PAGE_BREAK)) + self.xml = escape(text) def __unicode__(self): return self.xml diff --git a/tests/escape.py b/tests/escape.py index a18b926..865d693 100644 --- a/tests/escape.py +++ b/tests/escape.py @@ -7,13 +7,20 @@ context = { '"less than" must be escaped : <, this can be done with RichText() or R()' ), 'myescvar': 'It can be escaped with a "|e" jinja filter in the template too : < ', - 'nlnp': R( - 'Here is a multiple\nlines\nstring\aand some\aother\aparagraphs\aNOTE: the current character styling is removed' - ), + 'nlnp': R('Here is a multiple\nlines\nstring\aand some\aother\aparagraphs', + color='#ff00ff'), 'mylisting': Listing( - 'the listing\nwith\nsome\nlines\nand special chars : <>&\f ... and a page break' + 'the listing\nwith\nsome\nlines\nand special chars : <>& ...' ), 'page_break': R('\f'), + 'new_listing':""" +This is a new listing +Now, does not require Listing() Object +Here is a \t tab\a +Here is a new paragraph\a +Here is a page break : \f +That's it +""", } tpl.render(context) diff --git a/tests/templates/escape_tpl.docx b/tests/templates/escape_tpl.docx index d676492..d377494 100644 Binary files a/tests/templates/escape_tpl.docx and b/tests/templates/escape_tpl.docx differ