Code

Ticket #2594: ticket-2594-template-strip-leading-whitespace-2012-06-28.diff

File ticket-2594-template-strip-leading-whitespace-2012-06-28.diff, 26.8 KB (added by leonov, 2 years ago)

Update patch against 1.5-dev

Line 
1diff --git a/AUTHORS b/AUTHORS
2index ea0b019..969ba49 100644
3--- a/AUTHORS
4+++ b/AUTHORS
5@@ -292,6 +292,7 @@ answer newbie questions, and generally made Django that much better:
6     Ian G. Kelly <ian.g.kelly@gmail.com>
7     Niall Kelly <duke.sam.vimes@gmail.com>
8     Ryan Kelly <ryan@rfk.id.au>
9+    Stephen Kelly <steveire@gmail.com>
10     Thomas Kerpe <thomas@kerpe.net>
11     Wiley Kestner <wiley.kestner@gmail.com>
12     Ossama M. Khayat <okhayat@yahoo.com>
13diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
14index 13f7991..05bb413 100644
15--- a/django/conf/global_settings.py
16+++ b/django/conf/global_settings.py
17@@ -209,6 +209,9 @@ TEMPLATE_CONTEXT_PROCESSORS = (
18 # Output to use in template system for invalid (e.g. misspelled) variables.
19 TEMPLATE_STRING_IF_INVALID = ''
20 
21+# Remove the empty lines that had template tags on them.
22+TEMPLATE_STRIP_LEADING_WHITESPACE = False
23+
24 # Default email address to use for various automated correspondence from
25 # the site managers.
26 DEFAULT_FROM_EMAIL = 'webmaster@localhost'
27diff --git a/django/template/base.py b/django/template/base.py
28index 89bc909..0964f5d 100644
29--- a/django/template/base.py
30+++ b/django/template/base.py
31@@ -55,10 +55,58 @@ UNKNOWN_SOURCE = '<unknown source>'
32 
33 # match a variable or block tag and capture the entire tag, including start/end
34 # delimiters
35-tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
36-          (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
37-           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
38-           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))))
39+tag_re = re.compile('''
40+    (
41+          %(BLOCK_TAG_START)s.*?%(BLOCK_TAG_END)s        # block start + inner + end
42+        | %(VARIABLE_TAG_START)s.*?%(VARIABLE_TAG_END)s  # variable start + inner + end
43+        | %(COMMENT_TAG_START)s.*?%(COMMENT_TAG_END)s    # comment start + inner + end
44+    )
45+    ''' % {
46+        'BLOCK_TAG_START': re.escape(BLOCK_TAG_START),
47+        'BLOCK_TAG_END': re.escape(BLOCK_TAG_END),
48+        'VARIABLE_TAG_START': re.escape(VARIABLE_TAG_START),
49+        'VARIABLE_TAG_END': re.escape(VARIABLE_TAG_END),
50+        'COMMENT_TAG_START': re.escape(COMMENT_TAG_START),
51+        'COMMENT_TAG_END': re.escape(COMMENT_TAG_END),
52+    }, re.VERBOSE)
53+
54+strip_leading_whitespace_tag_re = re.compile('''
55+    (
56+    [\n\r]{1}     # 1 vertical white space (the one starting this line)
57+    [ \t]*?       # any number of tabs or spaces
58+    (?:           # group but don't match
59+        %(BLOCK_TAG_START)s
60+        [^\n\r%(BLOCK_TAG_START)s%(BLOCK_TAG_END)s]*?       # anything *not* vertical whitespace or
61+        %(BLOCK_TAG_END)s                                   # opening/closing block tag
62+    |
63+        %(COMMENT_TAG_START)s
64+        [^\n\r%(COMMENT_TAG_START)s%(COMMENT_TAG_END)s]*?   # anything *not* vertical whitespace or
65+        %(COMMENT_TAG_END)s                                 # opening/closing comment tag
66+    |
67+        %(VARIABLE_TAG_START)s
68+        [^\n\r%(VARIABLE_TAG_START)s%(VARIABLE_TAG_END)s]*? # anything *not* vertical whitespace or
69+        %(VARIABLE_TAG_END)s                                # opening/closing variable tag
70+    )
71+    (?=                      # Match this only if the previous matched
72+        [ \t]*?
73+        [\n\r]+
74+    )|
75+          %(BLOCK_TAG_START)s.*?%(BLOCK_TAG_END)s           # block start + inner + end
76+        | %(VARIABLE_TAG_START)s.*?%(VARIABLE_TAG_END)s     # variable start + inner + end
77+        | %(COMMENT_TAG_START)s.*?%(COMMENT_TAG_END)s       # comment start + inner + end
78+    )''' % {
79+        'BLOCK_TAG_START': re.escape(BLOCK_TAG_START),
80+        'BLOCK_TAG_END': re.escape(BLOCK_TAG_END),
81+        'VARIABLE_TAG_START': re.escape(VARIABLE_TAG_START),
82+        'VARIABLE_TAG_END': re.escape(VARIABLE_TAG_END),
83+        'COMMENT_TAG_START': re.escape(COMMENT_TAG_START),
84+        'COMMENT_TAG_END': re.escape(COMMENT_TAG_END),
85+    }, re.VERBOSE)
86+
87+# find the command within a block tag
88+block_command_re = re.compile('%s(?P<token_string>.*?)%s' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END)))
89+comment_command_re = re.compile('%s(?P<token_string>.*?)%s' % (re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
90+variable_command_re = re.compile('%s(?P<token_string>.*?)%s' % (re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
91 
92 # global dictionary of libraries that have been loaded using get_library
93 libraries = {}
94@@ -179,12 +227,13 @@ class Token(object):
95             split.append(bit)
96         return split
97 
98+
99 class Lexer(object):
100     def __init__(self, template_string, origin):
101         self.template_string = template_string
102         self.origin = origin
103         self.lineno = 1
104-        self.verbatim = False
105+        self.verbatim = False   # or 'endverbatim' or 'endverbatim %s' % block
106 
107     def tokenize(self):
108         """
109@@ -192,7 +241,8 @@ class Lexer(object):
110         """
111         in_tag = False
112         result = []
113-        for bit in tag_re.split(self.template_string):
114+        tag_splitter = strip_leading_whitespace_tag_re if settings.TEMPLATE_STRIP_LEADING_WHITESPACE else tag_re
115+        for bit in tag_splitter.split(self.template_string):
116             if bit:
117                 result.append(self.create_token(bit, in_tag))
118             in_tag = not in_tag
119@@ -204,7 +254,8 @@ class Lexer(object):
120         If in_tag is True, we are processing something that matched a tag,
121         otherwise it should be treated as a literal string.
122         """
123-        if in_tag and token_string.startswith(BLOCK_TAG_START):
124+        # Extract ``block_content``, check for verbatim tag
125+        if in_tag and token_string.lstrip().startswith(BLOCK_TAG_START):
126             # The [2:-2] ranges below strip off *_TAG_START and *_TAG_END.
127             # We could do len(BLOCK_TAG_START) to be more "correct", but we've
128             # hard-coded the 2s here for performance. And it's not like
129@@ -212,24 +263,39 @@ class Lexer(object):
130             block_content = token_string[2:-2].strip()
131             if self.verbatim and block_content == self.verbatim:
132                 self.verbatim = False
133+
134+        # Create ``token`` object...
135         if in_tag and not self.verbatim:
136-            if token_string.startswith(VARIABLE_TAG_START):
137-                token = Token(TOKEN_VAR, token_string[2:-2].strip())
138-            elif token_string.startswith(BLOCK_TAG_START):
139+            # ...by processing a...
140+            if token_string.lstrip().startswith(VARIABLE_TAG_START):
141+                # ...variable tag.
142+                token_struct = variable_command_re.search(token_string)
143+                token_string = token_struct.group('token_string')
144+                token = Token(TOKEN_VAR, token_string.strip())
145+            elif token_string.lstrip().startswith(BLOCK_TAG_START):
146+                # ...block tag.
147                 if block_content[:9] in ('verbatim', 'verbatim '):
148                     self.verbatim = 'end%s' % block_content
149-                token = Token(TOKEN_BLOCK, block_content)
150-            elif token_string.startswith(COMMENT_TAG_START):
151+                token_struct = block_command_re.search(token_string)
152+                token_string = token_struct.group('token_string')
153+                token = Token(TOKEN_BLOCK, token_string.strip())
154+            elif token_string.lstrip().startswith(COMMENT_TAG_START):
155+                # ...comment tag.
156                 content = ''
157+                token_struct = comment_command_re.search(token_string)
158+                token_string = token_struct.group('token_string')
159                 if token_string.find(TRANSLATOR_COMMENT_MARK):
160-                    content = token_string[2:-2].strip()
161+                    content = token_string.strip()
162                 token = Token(TOKEN_COMMENT, content)
163         else:
164+            # ...from a string literal.
165             token = Token(TOKEN_TEXT, token_string)
166+
167         token.lineno = self.lineno
168         self.lineno += token_string.count('\n')
169         return token
170 
171+
172 class Parser(object):
173     def __init__(self, tokens):
174         self.tokens = tokens
175diff --git a/django/template/debug.py b/django/template/debug.py
176index 6167403..3df6681 100644
177--- a/django/template/debug.py
178+++ b/django/template/debug.py
179@@ -1,4 +1,5 @@
180-from django.template.base import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
181+from django.conf import settings
182+from django.template.base import Lexer, Parser, tag_re, strip_leading_whitespace_tag_re, NodeList, VariableNode, TemplateSyntaxError
183 from django.utils.encoding import force_unicode
184 from django.utils.html import escape
185 from django.utils.safestring import SafeData, EscapeData
186@@ -13,7 +14,8 @@ class DebugLexer(Lexer):
187     def tokenize(self):
188         "Return a list of tokens from a given template_string"
189         result, upto = [], 0
190-        for match in tag_re.finditer(self.template_string):
191+        tag_splitter = strip_leading_whitespace_tag_re if settings.TEMPLATE_STRIP_LEADING_WHITESPACE else tag_re
192+        for match in tag_splitter.finditer(self.template_string):
193             start, end = match.span()
194             if start > upto:
195                 result.append(self.create_token(self.template_string[upto:start], (upto, start), False))
196diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
197index 83b72e1..2ca094d 100644
198--- a/django/template/defaulttags.py
199+++ b/django/template/defaulttags.py
200@@ -14,9 +14,11 @@ from django.template.base import (Node, NodeList, Template, Context, Library,
201     VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re)
202 from django.template.smartif import IfParser, Literal
203 from django.template.defaultfilters import date
204+from django.utils import timezone
205 from django.utils.encoding import smart_unicode
206 from django.utils.safestring import mark_safe
207-from django.utils import timezone
208+from django.utils.text import smart_split
209+
210 
211 register = Library()
212 
213diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
214index 627aa50..ce9779e 100644
215--- a/docs/ref/settings.txt
216+++ b/docs/ref/settings.txt
217@@ -1956,6 +1956,63 @@ Default: ``''`` (Empty string)
218 Output, as a string, that the template system should use for invalid (e.g.
219 misspelled) variables. See :ref:`invalid-template-variables`..
220 
221+.. setting:: TEMPLATE_STRIP_LEADING_WHITESPACE
222+
223+TEMPLATE_STRIP_LEADING_WHITESPACE
224+---------------------------------
225+
226+.. versionadded:: 1.5
227+
228+Default: ``False``
229+
230+Whether to strip leading whitespace on template lines containing only template
231+syntax.
232+
233+This template code::
234+    <h1>My list</h1>
235+
236+    {# This is a comment #}
237+    {# It describes the loop below #}
238+    {# Each line appears in the output as an empty line #}
239+    <ul>
240+    {% for item in items %}
241+        {# Description of a list item #}
242+        <li>{{ item }}</li>
243+    {% endfor %}
244+    </ul>
245+
246+Normally evaluates to::
247+    <h1>My list</h1>
248+
249+
250+
251+
252+    <ul>
253+
254+
255+        <li>item 1</li>
256+
257+        <li>item 2</li>
258+
259+        <li>item 3</li>
260+
261+    </ul>
262+
263+However, if TEMPLATE_STRIP_LEADING_WHITESPACE is set to True, lines which
264+contain only one template tag, variable or comment no not cause a newline
265+to be inserted in the output::
266+
267+    <h1>My list</h1>
268+
269+    <ul>
270+        <li>item 1</li>
271+        <li>item 2</li>
272+        <li>item 3</li>
273+    </ul>
274+
275+Trailing whitespace does not get stripped, and lines which contain more
276+than one template tag will not have their leading whitespace trimmed.
277+
278 .. setting:: TEST_RUNNER
279 
280 TEST_RUNNER
281diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
282index 35d0122..a69fa23 100644
283--- a/tests/regressiontests/templates/tests.py
284+++ b/tests/regressiontests/templates/tests.py
285@@ -387,6 +387,211 @@ class Templates(unittest.TestCase):
286         except TemplateSyntaxError as e:
287             self.assertEqual(e.args[0], "Invalid block tag: 'endblock', expected 'elif', 'else' or 'endif'")
288 
289+    def test_insignificant_whitespace(self):
290+        whitespace_tests = {
291+            # The test tuple contents is (template_content, context_args, stripped_output, unstripped_output)
292+            # Tags on their own line should collapse the newline before them
293+            # Trailing newline is not removed
294+            # Leading whitespace before single template tag
295+            'insignificant-whitespace01': ('\n {% templatetag openblock %}\n', {}, '{%\n',
296+                                                                                 '\n {%\n'),
297+            'insignificant-whitespace02': ('\n{% templatetag openblock %}\n', {}, '{%\n',
298+                                                                                '\n{%\n'),
299+            'insignificant-whitespace03': ('{% templatetag openblock %}\n', {}, '{%\n',
300+                                                                              '{%\n'),
301+            'insignificant-whitespace04': ('\n\t \t {% templatetag openblock %}\n', {}, '{%\n',
302+                                                                                      '\n\t \t {%\n'),
303+            # Leading whitespace with text before single template tag
304+            'insignificant-whitespace05': ('\n some\ttext {% templatetag openblock %}\n', {}, '\n some\ttext {%\n',
305+                                                                                            '\n some\ttext {%\n'),
306+            # Leading line with text before single template tag
307+            'insignificant-whitespace06': ('\n some\ttext\n {% templatetag openblock %}\n', {}, '\n some\ttext{%\n',
308+                                                                                              '\n some\ttext\n {%\n'),
309+            'insignificant-whitespace07': ('\n some\ttext \n \t {% templatetag openblock %}\n', {}, '\n some\ttext {%\n',
310+                                                                                                  '\n some\ttext \n \t {%\n'),
311+            # whitespace leading /before/ the newline is not stripped.
312+            'insignificant-whitespace08': ('\n some\ttext \t \n {% templatetag openblock %}\n', {}, '\n some\ttext \t {%\n',
313+                                                                                                  '\n some\ttext \t \n {%\n'),
314+            # Multiple text lines before tag
315+            'insignificant-whitespace09': ('\n some\ntext \t \n {% templatetag openblock %}\n', {}, '\n some\ntext \t {%\n',
316+                                                                                                  '\n some\ntext \t \n {%\n'),
317+            'insignificant-whitespace10': ('\n some \t \n \t text \t \n {% templatetag openblock %}\n', {}, '\n some \t \n \t text \t {%\n',
318+                                                                                                          '\n some \t \n \t text \t \n {%\n'),
319+            # Leading whitespace before tag, some text after
320+            'insignificant-whitespace11': ('\n   \t {% templatetag openblock %} some text\n', {}, '\n   \t {% some text\n',
321+                                                                                                '\n   \t {% some text\n'),
322+            # Leading whitespace before tag, some text with trailing whitespace after
323+            'insignificant-whitespace12': ('\n   \t {% templatetag openblock %} some text  \t \n', {}, '\n   \t {% some text  \t \n',
324+                                                                                                     '\n   \t {% some text  \t \n'),
325+            # Whitespace after tag is not removed
326+            'insignificant-whitespace13': ('\n \t {% templatetag openblock %} \t \n \t some text  \t \n', {}, '{% \t \n \t some text  \t \n',
327+                                                                                                            '\n \t {% \t \n \t some text  \t \n'),
328+            # Multiple lines of leading whitespace. Only one leading newline is removed
329+            'insignificant-whitespace14': ('\n\n\n{% templatetag openblock %}\n some text\n', {}, '\n\n{%\n some text\n',
330+                                                                                                '\n\n\n{%\n some text\n'),
331+            # Trailing whitespace after tag
332+            'insignificant-whitespace15': ('\n\n\n{% templatetag openblock %}\t \t \t\n some text\n', {}, '\n\n{%\t \t \t\n some text\n',
333+                                                                                                        '\n\n\n{%\t \t \t\n some text\n'),
334+            # Removable newline followed by leading whitespace
335+            'insignificant-whitespace16': ('\n\n\n\t \t \t{% templatetag openblock %}\n some text\n', {}, '\n\n{%\n some text\n',
336+                                                                                                        '\n\n\n\t \t \t{%\n some text\n'),
337+            # Removable leading whitespace and trailing whitespace
338+            'insignificant-whitespace17': ('\n\n\n\t \t \t{% templatetag openblock %}\t \t \t\n some text\n', {}, '\n\n{%\t \t \t\n some text\n',
339+                                                                                                                '\n\n\n\t \t \t{%\t \t \t\n some text\n'),
340+            # Multiple lines of trailing whitespace. No trailing newline is removed.
341+            'insignificant-whitespace18': ('\n{% templatetag openblock %}\n\n\n some text\n', {}, '{%\n\n\n some text\n',
342+                                                                                                '\n{%\n\n\n some text\n'),
343+            'insignificant-whitespace19': ('\n{% templatetag openblock %}\t \n\n\n some text\n', {}, '{%\t \n\n\n some text\n',
344+                                                                                                   '\n{%\t \n\n\n some text\n'),
345+            # Consecutive trimmed lines with tags strips one newline each
346+            'insignificant-whitespace20': (
347+                '\n{% templatetag openblock %}\n{% templatetag openblock %}\n{% templatetag openblock %}\n some text\n'
348+                , {}, '{%{%{%\n some text\n',
349+                      '\n{%\n{%\n{%\n some text\n'),
350+            # Consecutive trimmed lines with tags strips one newline each. Intermediate newlines are preserved
351+            'insignificant-whitespace21': (
352+                '\n\n{% templatetag openblock %}\n\n{% templatetag openblock %}\n\n{% templatetag openblock %}\n\n some text\n'
353+                , {}, '\n{%\n{%\n{%\n\n some text\n',
354+                      '\n\n{%\n\n{%\n\n{%\n\n some text\n'),
355+            # Consecutive trimmed lines with tags strips one newline each. Leading whitespace is stripped but trailing is not
356+            'insignificant-whitespace22': (
357+                '\n\n\t {% templatetag openblock %}\t \n\n\t {% templatetag openblock %}\t \n\n\t {% templatetag openblock %}\t \n some text\n'
358+                , {}, '\n{%\t \n{%\t \n{%\t \n some text\n',
359+                      '\n\n\t {%\t \n\n\t {%\t \n\n\t {%\t \n some text\n'),
360+            # Consecutive trimmed lines with tags strips one newline each. Intermediate whitespace is stripped
361+            'insignificant-whitespace23': (
362+                '\n\t {% templatetag openblock %}\t \n\t {% templatetag openblock %}\t \n\t {% templatetag openblock %}\t \n some text\n'
363+                , {}, '{%\t {%\t {%\t \n some text\n',
364+                      '\n\t {%\t \n\t {%\t \n\t {%\t \n some text\n'),
365+            # Intermediate whitespace on one line is preserved
366+            # Consecutive tags on one line do not have intermediate whitespace or leading whitespace stripped
367+            'insignificant-whitespace24': (
368+                '\n\t {% templatetag openblock %}\t \t {% templatetag openblock %}\t \t {% templatetag openblock %}\t \n some text\n'
369+                , {}, '\n\t {%\t \t {%\t \t {%\t \n some text\n',
370+                      '\n\t {%\t \t {%\t \t {%\t \n some text\n'),
371+            # Still, only one leading newline is removed.
372+            'insignificant-whitespace25': (
373+                '\n\n {% templatetag openblock %}\n \t {% templatetag openblock %}\n \t {% templatetag openblock %}\n some text\n'
374+                , {}, '\n{%{%{%\n some text\n',
375+                      '\n\n {%\n \t {%\n \t {%\n some text\n'),
376+            # Lines with {# comments #} have the same stripping behavior
377+            'insignificant-whitespace26': (
378+                '\n\n {% templatetag openblock %}\n \t {# some comment #}\n some text\n'
379+                , {}, '\n{%\n some text\n',
380+                      '\n\n {%\n \t \n some text\n'),
381+            # Only {# comments #}
382+            'insignificant-whitespace27': (
383+                '\n\n {# a comment #}\n \t {# some comment #}\n some text\n'
384+                , {}, '\n\n some text\n',
385+                      '\n\n \n \t \n some text\n'),
386+            # Consecutive newlines with tags and comments
387+            'insignificant-whitespace28': (
388+                '\n\t {% templatetag openblock %}\t \n\t {# some comment #}\t \n\t {% templatetag openblock %}\t \n some text\n'
389+                , {}, '{%\t \t {%\t \n some text\n',
390+                      '\n\t {%\t \n\t \t \n\t {%\t \n some text\n'),
391+
392+            # Lines with only {{ values }} have the same stripping behavior
393+            'insignificant-whitespace29': (
394+                '\n {% templatetag openblock %}\t\n \t {{ spam }}\t \n \t {% templatetag openblock %}\t \n some text\n'
395+                , {"spam" : "ham"}, '{%\tham\t {%\t \n some text\n',
396+                                    '\n {%\t\n \t ham\t \n \t {%\t \n some text\n'),
397+            'insignificant-whitespace30': (
398+                '\n\n {% templatetag openblock %}\t\n\n \t {{ spam }}\t \n\n \t {% templatetag openblock %}\t \n some text\n'
399+                , {"spam" : "ham"}, '\n{%\t\nham\t \n{%\t \n some text\n',
400+                                    '\n\n {%\t\n\n \t ham\t \n\n \t {%\t \n some text\n'),
401+            ## Leading whitespace not stripped when followed by anything. See insignificant-whitespace24
402+            'insignificant-whitespace31': (
403+                '\n {% templatetag openblock %}\t \t {{ spam }}\t \t {% templatetag openblock %}\t \n some text\n'
404+                , {"spam" : "ham"}, '\n {%\t \t ham\t \t {%\t \n some text\n',
405+                                    '\n {%\t \t ham\t \t {%\t \n some text\n'),
406+            #  {{ value }} {% tag %} {{ value }} this time
407+            'insignificant-whitespace32': (
408+                '\n {{ spam }}\t\n \t {% templatetag openblock %}\t \n \t {{ spam }}\t \n some text\n'
409+                , {"spam" : "ham"}, 'ham\t{%\t ham\t \n some text\n',
410+                                    '\n ham\t\n \t {%\t \n \t ham\t \n some text\n'),
411+
412+            # Invalid stuff is still invalid
413+            # Newlines inside begin-end tokens, even in {# comments #}, make it not a tag.
414+            'insignificant-whitespace33': (
415+                '\n\n {# \n{% templatetag openblock #}\t \n some text\n'
416+                , {}, '\n\n {# \n{% templatetag openblock #}\t \n some text\n',
417+                      '\n\n {# \n{% templatetag openblock #}\t \n some text\n'),
418+            # Complete comment matching tags on one line are processed
419+            'insignificant-whitespace34': (
420+                '\n\n {# \n{# templatetag openblock #}\t \n some text\n'
421+                , {}, '\n\n {# \t \n some text\n',
422+                      '\n\n {# \n\t \n some text\n'),
423+            'insignificant-whitespace35': (
424+                '\n\n {# \n{# templatetag openblock\n #}\t \n some text\n'
425+                , {}, '\n\n {# \n{# templatetag openblock\n #}\t \n some text\n',
426+                      '\n\n {# \n{# templatetag openblock\n #}\t \n some text\n'),
427+            'insignificant-whitespace36': (
428+                '\n\n {# \n{{ some comment #}\t \n some text\n'
429+                , {}, '\n\n {# \n{{ some comment #}\t \n some text\n',
430+                      '\n\n {# \n{{ some comment #}\t \n some text\n'),
431+            'insignificant-whitespace37': (
432+                '\n\n {# \n \t {% templatetag openblock #}\t \n some text\n'
433+                , {}, '\n\n {# \n \t {% templatetag openblock #}\t \n some text\n',
434+                      '\n\n {# \n \t {% templatetag openblock #}\t \n some text\n'),
435+            'insignificant-whitespace38': (
436+                "\n\n {# templatetag openblock #\n}\t \n some text\n"
437+                , {}, "\n\n {# templatetag openblock #\n}\t \n some text\n",
438+                      "\n\n {# templatetag openblock #\n}\t \n some text\n" ),
439+            'insignificant-whitespace39': (
440+                "\n\n {% templatetag openblock %\n}\t \n some text\n"
441+                , {}, "\n\n {% templatetag openblock %\n}\t \n some text\n",
442+                      "\n\n {% templatetag openblock %\n}\t \n some text\n" ),
443+            'insignificant-whitespace40': (
444+                "\n\n {{ templatetag openblock }\n}\t \n some text\n"
445+                , {}, "\n\n {{ templatetag openblock }\n}\t \n some text\n",
446+                      "\n\n {{ templatetag openblock }\n}\t \n some text\n" ),
447+            'insignificant-whitespace41': (
448+                "\n\n {\n# {# templatetag openblock #}\t \n some text\n"
449+                , {}, "\n\n {\n# \t \n some text\n",
450+                      "\n\n {\n# \t \n some text\n"),
451+            'insignificant-whitespace42': (
452+                "\n\n {\n {# templatetag openblock #}\t \n some text\n"
453+                , {}, "\n\n {\t \n some text\n",
454+                      "\n\n {\n \t \n some text\n"),
455+            'insignificant-whitespace43': (
456+                "\n{{# foo #};{# bar #}\n"
457+                , {}, "\n{;\n",
458+                      "\n{;\n"),
459+          }
460+        tests = whitespace_tests.items()
461+        tests.sort()
462+
463+        # Register our custom template loader.
464+        def test_whitespace_loader(template_name, template_dirs=None):
465+            "A custom template loader that loads the unit-test templates."
466+            try:
467+                return (whitespace_tests[template_name][0] , "test:%s" % template_name)
468+            except KeyError:
469+                raise template.TemplateDoesNotExist, template_name
470+
471+        old_template_loaders = loader.template_source_loaders
472+        loader.template_source_loaders = [test_whitespace_loader]
473+
474+        failures = []
475+
476+        old_strip_leading_whitespace = settings.TEMPLATE_STRIP_LEADING_WHITESPACE
477+
478+        for name, vals in tests:
479+            for strip_leading_whitespace in (True, False):
480+                settings.TEMPLATE_STRIP_LEADING_WHITESPACE = strip_leading_whitespace
481+                test_template = loader.get_template(name)
482+                result = vals[2] if strip_leading_whitespace else vals[3]
483+                output = self.render(test_template, vals)
484+
485+                if output != result:
486+                    failures.append("Whitespace test: %s -- FAILED. Expected %r, got %r" % (name, result, output))
487+
488+        loader.template_source_loaders = old_template_loaders
489+        settings.TEMPLATE_STRIP_LEADING_WHITESPACE = old_strip_leading_whitespace
490+
491+        self.assertEqual(failures, [], "Tests failed:\n%s\n%s" %
492+            ('-'*70, ("\n%s\n" % ('-'*70)).join(failures)))
493+
494     def test_templates(self):
495         template_tests = self.get_template_tests()
496         filter_tests = filters.get_filter_tests()