From 40bf39cde7fb9e00c70fbab15ddef106873e920b Mon Sep 17 00:00:00 2001 From: Max P <48almonds@gmail.com> Date: Mon, 5 Nov 2018 18:17:43 -0800 Subject: [PATCH] Added method `escape_values`. Exclusively for Python3. --- docxtpl/__init__.py | 41 ++++++++++++++++++++++++++++++++++++++--- tests/escape_auto.py | 12 ++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 tests/escape_auto.py diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 3291b61..287fcd0 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -19,13 +19,12 @@ try: except ImportError: # cgi.escape is deprecated in python 3.7 from cgi import escape - import HTMLParser - unescape = HTMLParser.HTMLParser().unescape import re import six import binascii import os import zipfile +import sys NEWLINE_XML = '' NEWPARAGRAPH_XML = '' @@ -171,7 +170,43 @@ class DocxTemplate(object): def map_headers_footers_xml(self, relKey, xml): self.docx._part._rels[relKey]._target._blob = xml - def render(self,context,jinja_env=None): + @staticmethod + def escape_values(context): + """Escape strings for an XML Word document + which may contain <, >, &, ', and ". + """ + def escape_recursively(d): + """Escape string values of the passed :dict: `d` in-place + including nested dictionaries. + """ + nonlocal hash_values + + for k, v in d.items(): + if isinstance(v, dict): + hash_value = id(v) + if hash_value not in hash_values: + hash_values.add(hash_value) + escape_recursively(v) + else: + # Avoid dict, Listing, InlineImage, RichText, etc. classes + # by comparing v to str. Do not use try-except. + if isinstance(v, str): + # Unescape at first to avoid secondary escaping + d[k] = escape(unescape(v)) + + # Avoid RecursionError (if back edges, i.e. cycles, exist) + # by using a set of hash values of iterated dictionaries. + hash_values = {id(context), } + + escape_recursively(context) + + def render(self, context, jinja_env=None): + if sys.version_info >= (3, 0): + self.escape_values(context) + else: + # Sorry folk, use awesome Python3 such as 3.6 + pass + # Body xml_src = self.build_xml(context,jinja_env) diff --git a/tests/escape_auto.py b/tests/escape_auto.py new file mode 100644 index 0000000..97708ef --- /dev/null +++ b/tests/escape_auto.py @@ -0,0 +1,12 @@ +from docxtpl import * + +tpl = DocxTemplate("test_files/escape_tpl.docx") + +context = {'myvar': 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 : < ', + 'nlnp' : R('Here is a multiple\nlines\nstring\aand some\aother\aparagraphs\aNOTE: the current character styling is removed'), + 'mylisting': Listing('the listing\nwith\nsome\nlines\nand special chars : <>&'), + } + +tpl.render(context) +tpl.save("test_files/escape.docx")