diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py
index 6d245be..b91a670 100644
--- a/docxtpl/__init__.py
+++ b/docxtpl/__init__.py
@@ -18,13 +18,15 @@ try:
from html import escape, unescape
except ImportError:
# cgi.escape is deprecated in python 3.7
+ # import escape and unescape methods for Python 2.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 = ''
@@ -175,40 +177,35 @@ class DocxTemplate(object):
"""Escape strings for an XML Word document
which may contain <, >, &, ', and ".
"""
- def escape_recursively(d):
+ def escape_recursively(d, identities):
"""Escape string values of the passed :dict: `d` in-place
- including nested dictionaries.
+ including nested dictionaries of any depth.
"""
- nonlocal hash_values
-
- for k, v in d.items():
+ for k, v in six.iteritems(d):
if isinstance(v, dict):
- hash_value = id(v)
- if hash_value not in hash_values:
- hash_values.add(hash_value)
- escape_recursively(v)
+ identity = id(v)
+ if identity not in identities:
+ identities.add(identity)
+ escape_recursively(v, identities)
else:
# Avoid dict, Listing, InlineImage, RichText, etc. classes
- # by comparing v to str. Do not use try-except.
+ # 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), }
+ # by using a set of unique identities of iterated dictionaries.
+ initial_identities = {id(context)}
- escape_recursively(context)
+ escape_recursively(context, initial_identities)
def render(self, context, jinja_env=None, autoescape=False):
- if sys.version_info >= (3, 0) and autoescape:
+ if autoescape:
self.escape_values(context)
- else:
- # Sorry folk, use awesome Python3 such as 3.6
- pass
# Body
- xml_src = self.build_xml(context,jinja_env)
+ xml_src = self.build_xml(context, jinja_env)
# fix tables if needed
tree = self.fix_tables(xml_src)
diff --git a/tests/escape_auto.py b/tests/escape_auto.py
index d792fc2..ffa2b0a 100644
--- a/tests/escape_auto.py
+++ b/tests/escape_auto.py
@@ -3,10 +3,10 @@ from docxtpl import *
tpl = DocxTemplate("test_files/escape_tpl_auto.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'),
+ '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 : <>&'),
- 'autoescape': """These string should be auto escaped for an XML Word document which may contain <, >, &, ", and '."""
+ 'autoescape': """<, >, &, ", and '."""
}
tpl.render(context, autoescape=True)
diff --git a/tests/test_files/escape_auto.docx b/tests/test_files/escape_auto.docx
index fc0e54c..c4ed5f0 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/escape_tpl_auto.docx b/tests/test_files/escape_tpl_auto.docx
index c6f7a6c..4d59e55 100644
Binary files a/tests/test_files/escape_tpl_auto.docx and b/tests/test_files/escape_tpl_auto.docx differ