\n, \a, \t and \f are now accepted in simple context string

This commit is contained in:
Eric Lapouyade 2020-10-19 13:59:36 +02:00
parent b616daf50a
commit 44a0819625
5 changed files with 56 additions and 41 deletions

View File

@ -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) 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) - Remove extension testing (#297)
0.10.0 (2020-05-25)
-------------------
- Fix spaces missing in some cases (#116, #227) - Fix spaces missing in some cases (#116, #227)
0.9.2 (2020-04-26) 0.9.2 (2020-04-26)
@ -19,16 +12,9 @@
- Fix #271 - Fix #271
- Code styling - Code styling
0.9.0 (2020-04-15)
-------------------
- New syntax : {%- and -%} to merge lines/paragraphs
0.8.1 (2020-04-14) 0.8.1 (2020-04-14)
------------------- -------------------
- fix #266 - fix #266
0.8.0 (2020-04-10)
-------------------
- docxtpl is now able to use latest python-docx (0.8.10). Thanks to Dutchy-@github. - docxtpl is now able to use latest python-docx (0.8.10). Thanks to Dutchy-@github.
0.7.0 (2020-04-09) 0.7.0 (2020-04-09)

View File

@ -142,6 +142,8 @@ As part of jinja2, one can used double braces::
{{ <var> }} {{ <var> }}
if ``<var>`` is a string, ``\n``, ``\a``, ``\t`` and ``\f`` will be translated respectively into newlines, new paragraphs, tabs and page breaks
But if ``<var>`` is a RichText_ object, you must specify that you are changing the actual 'run':: But if ``<var>`` is a RichText_ object, you must specify that you are changing the actual 'run'::
{{r <var> }} {{r <var> }}
@ -240,7 +242,7 @@ especially ``<``, ``>`` and ``&``. In order to use them, you must escape them. T
* ``context = { 'var':escape('my text')}`` and ``{{ <var> }}`` in the template. * ``context = { 'var':escape('my text')}`` and ``{{ <var> }}`` in the template.
* enable autoescaping when calling render method: ``tpl.render(context, autoescape=True)`` (default is autoescape=False) * 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. text, they will be converted accordingly.
See tests/escape.py example for more informations. See tests/escape.py example for more informations.

View File

@ -4,11 +4,10 @@ Created : 2015-03-12
@author: Eric Lapouyade @author: Eric Lapouyade
''' '''
__version__ = '0.11.0'
import functools import functools
import io import io
__version__ = '0.10.5'
from lxml import etree from lxml import etree
from docx import Document from docx import Document
from docx.opc.oxml import parse_xml from docx.opc.oxml import parse_xml
@ -28,11 +27,6 @@ import binascii
import os import os
import zipfile import zipfile
NEWLINE_XML = '</w:t><w:br/><w:t xml:space="preserve">'
NEWPARAGRAPH_XML = '</w:t></w:r></w:p><w:p><w:r><w:t xml:space="preserve">'
TAB_XML = '</w:t></w:r><w:r><w:tab/></w:r><w:r><w:t xml:space="preserve">'
PAGE_BREAK = '</w:t><w:br w:type="page"/><w:t xml:space="preserve">'
class DocxTemplate(object): class DocxTemplate(object):
""" Class for managing docx files as they were jinja2 templates """ """ Class for managing docx files as they were jinja2 templates """
@ -234,8 +228,42 @@ class DocxTemplate(object):
.replace('}_}', '}}') .replace('}_}', '}}')
.replace('{_%', '{%') .replace('{_%', '{%')
.replace('%_}', '%}')) .replace('%_}', '%}'))
dst_xml = self.resolve_listing(dst_xml)
return dst_xml return dst_xml
def resolve_listing(self, xml):
def resolve_text(run_properties, paragraph_properties, m):
xml = m[0].replace('\t', '</w:t></w:r>'
'<w:r>%s<w:tab/></w:r>'
'<w:r>%s<w:t xml:space="preserve">' % (run_properties, run_properties))
xml = xml.replace('\a', '</w:t></w:r></w:p>'
'<w:p>%s<w:r>%s<w:t xml:space="preserve">' % (paragraph_properties, run_properties))
xml = xml.replace('\n', '</w:t><w:br/><w:t xml:space="preserve">')
xml = xml.replace('\f', '</w:t></w:r></w:p>'
'<w:p><w:r><w:br w:type="page"/></w:r></w:p>'
'<w:p>%s<w:r>%s<w:t xml:space="preserve">' % (paragraph_properties, run_properties))
return xml
def resolve_run(paragraph_properties, m):
run_properties = re.search(r'<w:rPr>.*</w:rPr>', 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'<w:t(?:[^>]*)?>.*?</w:t>', p_resolve_text, m[0], flags=re.DOTALL)
def resolve_paragraph(m):
paragraph_properties = re.search(r'<w:pPr>.*</w:pPr>', 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'<w:r(?:[^>]*)?>.*?</w:r>', p_resolve_run, m[0], flags=re.DOTALL)
xml = re.sub(r'<w:p(?:[^>]*)?>.*?</w:p>', resolve_paragraph, xml, flags=re.DOTALL)
return xml
def build_xml(self, context, jinja_env=None): def build_xml(self, context, jinja_env=None):
xml = self.get_xml() xml = self.get_xml()
xml = self.patch_xml(xml) xml = self.patch_xml(xml)
@ -684,11 +712,7 @@ class RichText(object):
text = six.text_type(text) text = six.text_type(text)
if not isinstance(text, six.text_type): if not isinstance(text, six.text_type):
text = text.decode('utf-8', errors='ignore') text = text.decode('utf-8', errors='ignore')
text = (escape(text) text = escape(text)
.replace('\n', NEWLINE_XML)
.replace('\a', NEWPARAGRAPH_XML)
.replace('\t', TAB_XML)
.replace('\f', PAGE_BREAK))
prop = u'' prop = u''
@ -756,11 +780,7 @@ class Listing(object):
# If not a string : cast to string (ex: int, dict etc...) # If not a string : cast to string (ex: int, dict etc...)
if not isinstance(text, (six.text_type, six.binary_type)): if not isinstance(text, (six.text_type, six.binary_type)):
text = six.text_type(text) text = six.text_type(text)
self.xml = (escape(text) self.xml = escape(text)
.replace('\n', NEWLINE_XML)
.replace('\a', NEWPARAGRAPH_XML)
.replace('\t', TAB_XML)
.replace('\f', PAGE_BREAK))
def __unicode__(self): def __unicode__(self):
return self.xml return self.xml

View File

@ -7,13 +7,20 @@ context = {
'"less than" must be escaped : <, this can be done with RichText() or R()' '"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 : < ', 'myescvar': 'It can be escaped with a "|e" jinja filter in the template too : < ',
'nlnp': R( 'nlnp': R('Here is a multiple\nlines\nstring\aand some\aother\aparagraphs',
'Here is a multiple\nlines\nstring\aand some\aother\aparagraphs\aNOTE: the current character styling is removed' color='#ff00ff'),
),
'mylisting': Listing( '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'), '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) tpl.render(context)

Binary file not shown.