Code

Ticket #7799: tplrf.diff

File tplrf.diff, 115.7 KB (added by emulbreh, 6 years ago)
Line 
1Index: django/template/nodes.py
2===================================================================
3--- django/template/nodes.py    (revision 0)
4+++ django/template/nodes.py    (revision 0)
5@@ -0,0 +1,88 @@
6+from django.utils.encoding import force_unicode
7+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
8+from django.utils.html import escape
9+from django.template.expressions import LookupError
10+from django.conf import settings
11+
12+class Node(object):
13+    # Set this to True for nodes that must be first in the template (although
14+    # they can be preceded by text nodes.
15+    must_be_first = False
16+
17+    def render(self, context):
18+        "Return the node rendered as a string"
19+        pass
20+
21+    def __iter__(self):
22+        yield self
23+
24+    def get_nodes_by_type(self, nodetype):
25+        "Return a list of all nodes (within this node and its nodelist) of the given type"
26+        nodes = []
27+        if isinstance(self, nodetype):
28+            nodes.append(self)
29+        if hasattr(self, 'nodelist'):
30+            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
31+        return nodes
32+
33+class NodeList(list):
34+    # Set to True the first time a non-TextNode is inserted by
35+    # extend_nodelist().
36+    contains_nontext = False
37+
38+    def render(self, context):
39+        bits = []
40+        for node in self:
41+            if isinstance(node, Node):
42+                bits.append(self.render_node(node, context))
43+            else:
44+                bits.append(node)
45+        return mark_safe(''.join([force_unicode(b) for b in bits]))
46+
47+    def get_nodes_by_type(self, nodetype):
48+        "Return a list of all nodes of the given type"
49+        nodes = []
50+        for node in self:
51+            nodes.extend(node.get_nodes_by_type(nodetype))
52+        return nodes
53+
54+    def render_node(self, node, context):
55+        return node.render(context)
56+
57+class TextNode(Node):
58+    def __init__(self, s):
59+        self.s = s
60+
61+    def __repr__(self):
62+        return "<Text Node: '%s'>" % self.s[:25]
63+
64+    def render(self, context):
65+        return self.s
66+
67+class ExpressionNode(Node):
68+    def __init__(self, expression):
69+        self.expression = expression
70+
71+    def __repr__(self):
72+        return "<Variable Node: %s>" % self.expression
73+
74+    def render(self, context):
75+        try:
76+            output = force_unicode(self.expression.resolve(context))
77+        except LookupError:
78+            if settings.TEMPLATE_STRING_IF_INVALID:
79+                from django.template import invalid_var_format_string
80+                if invalid_var_format_string is None:
81+                    invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
82+                if invalid_var_format_string:
83+                    return settings.TEMPLATE_STRING_IF_INVALID % self.expression
84+                return settings.TEMPLATE_STRING_IF_INVALID
85+            else:
86+                return ''
87+        except UnicodeDecodeError:
88+            # Unicode conversion can fail sometimes for reasons out of our
89+            # control (e.g. exception rendering). In that case, we fail quietly.
90+            return ''       
91+        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
92+            output = escape(output)
93+        return output
94Index: django/template/compiler.py
95===================================================================
96--- django/template/compiler.py (revision 0)
97+++ django/template/compiler.py (revision 0)
98@@ -0,0 +1,492 @@
99+import re
100+from inspect import getargspec
101+from django.conf import settings
102+from django.template.context import Context, RequestContext, ContextPopException
103+from django.template.expressions import Expression, Literal, Lookup, FilterExpression, VARIABLE_ATTRIBUTE_SEPARATOR
104+from django.template.nodes import Node, NodeList, ExpressionNode, TextNode
105+from django.utils.text import smart_split
106+from django.utils.encoding import smart_unicode, smart_str
107+from django.utils.safestring import mark_safe
108+from django.utils.translation import ugettext
109+
110+__all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream')
111+
112+TOKEN_TEXT = 0
113+TOKEN_VAR = 1
114+TOKEN_BLOCK = 2
115+TOKEN_COMMENT = 3
116+
117+# template syntax constants
118+FILTER_SEPARATOR = '|'
119+FILTER_ARGUMENT_SEPARATOR = ':'
120+BLOCK_TAG_START = '{%'
121+BLOCK_TAG_END = '%}'
122+VARIABLE_TAG_START = '{{'
123+VARIABLE_TAG_END = '}}'
124+COMMENT_TAG_START = '{#'
125+COMMENT_TAG_END = '#}'
126+SINGLE_BRACE_START = '{'
127+SINGLE_BRACE_END = '}'
128+
129+ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
130+
131+# what to report as the origin for templates that come from non-loader sources
132+# (e.g. strings)
133+UNKNOWN_SOURCE="&lt;unknown source&gt;"
134+
135+# match a variable or block tag and capture the entire tag, including start/end delimiters
136+tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
137+                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
138+                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
139+
140+class TemplateSyntaxError(Exception):
141+    def __str__(self):
142+        try:
143+            import cStringIO as StringIO
144+        except ImportError:
145+            import StringIO
146+        output = StringIO.StringIO()
147+        output.write(Exception.__str__(self))
148+        # Check if we wrapped an exception and print that too.
149+        if hasattr(self, 'exc_info'):
150+            import traceback
151+            output.write('\n\nOriginal ')
152+            e = self.exc_info
153+            traceback.print_exception(e[0], e[1], e[2], 500, output)
154+        return output.getvalue()
155+
156+class TemplateEncodingError(Exception):
157+    pass
158+
159+class Origin(object):
160+    def __init__(self, name):
161+        self.name = name
162+
163+    def reload(self):
164+        raise NotImplementedError
165+
166+    def __str__(self):
167+        return self.name
168+
169+class StringOrigin(Origin):
170+    def __init__(self, source):
171+        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
172+        self.source = source
173+
174+    def reload(self):
175+        return self.source
176+
177+class Template(object):
178+    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
179+        try:
180+            template_string = smart_unicode(template_string)
181+        except UnicodeDecodeError:
182+            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
183+        if settings.TEMPLATE_DEBUG and origin is None:
184+            origin = StringOrigin(template_string)
185+        self.nodelist = compile_string(template_string, origin)
186+        self.name = name
187+
188+    def __iter__(self):
189+        for node in self.nodelist:
190+            for subnode in node:
191+                yield subnode
192+
193+    def render(self, context):
194+        "Display stage -- can be called many times"
195+        return self.nodelist.render(context)
196+
197+def compile_string(template_string, origin):
198+    "Compiles template_string into NodeList ready for rendering"
199+    if settings.TEMPLATE_DEBUG:
200+        from debug import DebugLexer, DebugParser
201+        lexer_class, parser_class = DebugLexer, DebugParser
202+    else:
203+        lexer_class, parser_class = Lexer, Parser
204+    lexer = lexer_class(template_string, origin)
205+    parser = parser_class(lexer.tokenize())
206+    return parser.parse()
207+
208+class Token(object):
209+    def __init__(self, token_type, contents):
210+        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
211+        self.token_type, self.contents = token_type, contents
212+
213+    def __str__(self):
214+        return '<%s token: "%s...">' % \
215+            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
216+            self.contents[:20].replace('\n', ''))
217+
218+    def split_contents(self):
219+        return list(smart_split(self.contents))
220+
221+class Lexer(object):
222+    def __init__(self, template_string, origin):
223+        self.template_string = template_string
224+        self.origin = origin
225+
226+    def tokenize(self):
227+        "Return a list of tokens from a given template_string."
228+        in_tag = False
229+        result = []
230+        for bit in tag_re.split(self.template_string):
231+            if bit:
232+                result.append(self.create_token(bit, in_tag))
233+            in_tag = not in_tag
234+        return result
235+
236+    def create_token(self, token_string, in_tag):
237+        """
238+        Convert the given token string into a new Token object and return it.
239+        If in_tag is True, we are processing something that matched a tag,
240+        otherwise it should be treated as a literal string.
241+        """
242+        if in_tag:
243+            if token_string.startswith(VARIABLE_TAG_START):
244+                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
245+            elif token_string.startswith(BLOCK_TAG_START):
246+                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
247+            elif token_string.startswith(COMMENT_TAG_START):
248+                token = Token(TOKEN_COMMENT, '')
249+        else:
250+            token = Token(TOKEN_TEXT, token_string)
251+        return token
252+
253+class Parser(object):
254+    def __init__(self, tokens):
255+        self.tokens = tokens
256+        self.tags = {}
257+        self.filters = {}
258+        from django.template.library import builtins
259+        for lib in builtins:
260+            self.add_library(lib)
261+
262+    def parse(self, parse_until=None):
263+        if parse_until is None: parse_until = []
264+        nodelist = self.create_nodelist()
265+        while self.tokens:
266+            token = self.next_token()
267+            if token.token_type == TOKEN_TEXT:
268+                self.extend_nodelist(nodelist, TextNode(token.contents), token)
269+            elif token.token_type == TOKEN_VAR:
270+                if not token.contents:
271+                    self.empty_variable(token)
272+                filter_expression = self.compile_filter(token.contents)
273+                var_node = self.create_variable_node(filter_expression)
274+                self.extend_nodelist(nodelist, var_node,token)
275+            elif token.token_type == TOKEN_BLOCK:
276+                if token.contents in parse_until:
277+                    # put token back on token list so calling code knows why it terminated
278+                    self.prepend_token(token)
279+                    return nodelist
280+                try:
281+                    command = token.contents.split()[0]
282+                except IndexError:
283+                    self.empty_block_tag(token)
284+                # execute callback function for this tag and append resulting node
285+                self.enter_command(command, token)
286+                try:
287+                    compile_func = self.tags[command]
288+                except KeyError:
289+                    self.invalid_block_tag(token, command)
290+                try:
291+                    compiled_result = compile_func(self, token)
292+                except TemplateSyntaxError, e:
293+                    if not self.compile_function_error(token, e):
294+                        raise
295+                self.extend_nodelist(nodelist, compiled_result, token)
296+                self.exit_command()
297+        if parse_until:
298+            self.unclosed_block_tag(parse_until)
299+        return nodelist
300+
301+    def parse_nodelist(self, parse_until=None):
302+        nodelist = self.parse(parse_until=parse_until)
303+        self.delete_first_token()
304+        return nodelist
305+
306+    def skip_past(self, endtag):
307+        while self.tokens:
308+            token = self.next_token()
309+            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
310+                return
311+        self.unclosed_block_tag([endtag])
312+
313+    def create_variable_node(self, expression):   
314+        return ExpressionNode(expression)
315+
316+    def create_nodelist(self):
317+        return NodeList()
318+
319+    def extend_nodelist(self, nodelist, node, token):
320+        if node.must_be_first and nodelist:
321+            try:
322+                if nodelist.contains_nontext:
323+                    raise AttributeError
324+            except AttributeError:
325+                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
326+        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
327+            nodelist.contains_nontext = True
328+        nodelist.append(node)
329+
330+    def enter_command(self, command, token):
331+        pass
332+
333+    def exit_command(self):
334+        pass
335+
336+    def error(self, token, msg):
337+        return TemplateSyntaxError(msg)
338+
339+    def empty_variable(self, token):
340+        raise self.error(token, "Empty variable tag")
341+
342+    def empty_block_tag(self, token):
343+        raise self.error(token, "Empty block tag")
344+
345+    def invalid_block_tag(self, token, command):
346+        raise self.error(token, "Invalid block tag: '%s'" % command)
347+
348+    def unclosed_block_tag(self, parse_until):
349+        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
350+
351+    def compile_function_error(self, token, e):
352+        pass
353+
354+    def next_token(self):
355+        return self.tokens.pop(0)
356+
357+    def prepend_token(self, token):
358+        self.tokens.insert(0, token)
359+
360+    def delete_first_token(self):
361+        del self.tokens[0]
362+
363+    def add_library(self, lib):
364+        self.tags.update(lib.tags)
365+        self.filters.update(lib.filters)
366+
367+    def token_stream(self, token):
368+        return TokenStream(self, token)
369+
370+    def compile_filter(self, token):
371+        stream = TokenStream(self, token)
372+        try:
373+            expr = stream.parse_expression()
374+        except TokenSyntaxError:
375+            stream.expected("expression")
376+        stream.assert_consumed("invalid filter expression")
377+        return expr
378+
379+    def find_filter(self, filter_name):
380+        if filter_name in self.filters:
381+            return self.filters[filter_name]
382+        else:
383+            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
384+
385+def filter_args_check(name, func, provided):
386+    provided = list(provided)
387+    plen = len(provided)
388+    # Check to see if a decorator is providing the real function.
389+    func = getattr(func, '_decorated_function', func)
390+    args, varargs, varkw, defaults = getargspec(func)
391+   
392+    if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)):
393+        return True
394+   
395+    # First argument is filter input.
396+    args.pop(0)
397+    if defaults:
398+        nondefs = args[:-len(defaults)]
399+    else:
400+        nondefs = args
401+    # Args without defaults must be provided.
402+    try:
403+        for arg in nondefs:
404+            provided.pop(0)
405+    except IndexError:
406+        # Not enough
407+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
408+
409+    # Defaults can be overridden.
410+    defaults = defaults and list(defaults) or []
411+    try:
412+        for parg in provided:
413+            defaults.pop(0)
414+    except IndexError:
415+        # Too many.
416+        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
417+
418+    return True
419+
420+
421+bit_re = re.compile(r"""
422+    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
423+    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
424+    |(?P<name>[\w.]+) # keyword or variable
425+    |(?P<char>\S) # punctuation
426+""", re.VERBOSE)
427+
428+class TokenSyntaxError(Exception):
429+    pass
430+
431+def token_stream_parser(func):
432+    def wrapper(self, *args, **kwargs):
433+        mark = self.offset
434+        try:
435+            return func(self, *args, **kwargs)
436+        except TokenSyntaxError:
437+            self.offset = mark
438+            raise
439+    return wrapper
440+   
441+class TokenStream(object):
442+    exception = TokenSyntaxError()
443+    def __init__(self, parser, source):
444+        self.parser = parser
445+        self.source = source
446+        self.offset = 0
447+        self.name = None
448+        if isinstance(source, Token):
449+            bits = source.contents.split(None, 1)               
450+            self.source = len(bits) == 2 and bits[1] or ''
451+            self.name = bits[0]
452+        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]           
453+
454+    def peek(self):
455+        return self.tokens[self.offset]
456+       
457+    def consumed(self):
458+        return self.offset == len(self.tokens)
459+           
460+    def pop(self):
461+        if self.offset == len(self.tokens):
462+            raise self.exception
463+        next = self.tokens[self.offset]
464+        self.offset += 1       
465+        return next
466+   
467+    def pop_lexem(self, lexem):
468+        if self.offset == len(self.tokens):
469+            return False
470+        _, next = self.tokens[self.offset]
471+        if next == lexem:
472+            self.offset += 1
473+            return True
474+        return False
475+       
476+    def pop_name(self):
477+        if self.offset == len(self.tokens):
478+            return None
479+        tokentype, lexem = self.tokens[self.offset]
480+        if tokentype == 'name':
481+            self.offset += 1
482+            return lexem
483+        return None
484+       
485+    def pushback(self):
486+        self.offset -= 1
487+       
488+    def assert_consumed(self, msg=None):
489+        if self.offset != len(self.tokens):
490+            raise TemplateSyntaxError, (msg or "unmatched input") + repr(self.tokens[self.offset:])
491+
492+    def expected(self, what):
493+        if self.consumed():
494+            found = "<EOT>"
495+        else:
496+            found = "<%s> %s" % self.tokens[self.offset]
497+        raise TemplateSyntaxError, "expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace'))
498+
499+    @token_stream_parser
500+    def parse_string(self, bare=False):
501+        tokentype, lexem = self.pop()
502+        if tokentype == 'string_literal':
503+            return lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
504+        if bare and tokentype == 'name':
505+            return lexem
506+        raise self.exception
507+
508+    @token_stream_parser
509+    def parse_value(self):
510+        translate = False
511+        if self.pop_lexem('_'):
512+            if not self.pop_lexem('('):
513+                raise self.exception
514+            translate = True
515+        tokentype, lexem = self.pop()
516+   
517+        if tokentype == 'char':
518+            raise self.exception
519+   
520+        if tokentype == 'name':
521+            if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_':
522+                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem)
523+            value = Lookup(lexem)       
524+        elif tokentype == 'string_literal':
525+            value = lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
526+            value = Literal(mark_safe(value))
527+        elif tokentype == 'numeric_literal':
528+            try:
529+                value = float(lexem)
530+            except ValueError:
531+                raise self.exception
532+            if '.' not in lexem and 'e' not in lexem.lower():
533+                value = int(value)
534+            #FIXME: this causes a test failure: `ifequal-numeric07`
535+            if lexem.endswith('.'):
536+                raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem           
537+            value = Literal(value)
538+        if translate:
539+            if not self.pop_lexem(')'):
540+                raise self.exception
541+            value = FilterExpression(value, [(ugettext, ())])       
542+        return value
543+       
544+
545+    @token_stream_parser
546+    def parse_filter(self):
547+        if not self.pop_lexem('|'):
548+            raise self.exception
549+        name = self.pop_name()
550+        if not name:
551+            raise self.exception
552+        args = []
553+        if self.pop_lexem(':'):
554+            args.append(self.parse_value())
555+        func = self.parser.find_filter(name)
556+        filter_args_check(name, func, args)
557+        return func, args
558+       
559+
560+    @token_stream_parser
561+    def parse_expression(self):
562+        var = self.parse_value()
563+        filters = []
564+        try:
565+            while True:
566+                filters.append(self.parse_filter())
567+        except TokenSyntaxError:
568+            pass
569+        if filters:
570+            return FilterExpression(var, filters)
571+        return var
572+       
573+       
574+    @token_stream_parser
575+    def parse_expression_list(self, minimum=0, maximum=None, count=None):
576+        expressions = []
577+        if count:
578+            minimum = count
579+            maximum = count
580+        try:
581+            while True:
582+                if len(expressions) == maximum:
583+                    break           
584+                expressions.append(self.parse_expression())
585+        except TokenSyntaxError:
586+            pass
587+        if len(expressions) < minimum:
588+            self.expected("expression")
589+        return expressions
590+
591Index: django/template/__init__.py
592===================================================================
593--- django/template/__init__.py (revision 7884)
594+++ django/template/__init__.py (working copy)
595@@ -12,9 +12,9 @@
596 Node objects.
597 
598 Each Node is responsible for creating some sort of output -- e.g. simple text
599-(TextNode), variable values in a given context (VariableNode), results of basic
600+(TextNode), variable values in a given context (ExpressionNode), results of basic
601 logic (IfNode), results of looping (ForNode), or anything else. The core Node
602-types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
603+types are TextNode, ExpressionNode, IfNode and ForNode, but plugin modules can
604 define their own custom node types.
605 
606 Each Node has a render() method, which takes a Context and returns a string of
607@@ -48,887 +48,33 @@
608 >>> t.render(c)
609 u'<html></html>'
610 """
611-import re
612-from inspect import getargspec
613-from django.conf import settings
614+
615 from django.template.context import Context, RequestContext, ContextPopException
616-from django.utils.itercompat import is_iterable
617-from django.utils.functional import curry, Promise
618-from django.utils.text import smart_split
619-from django.utils.encoding import smart_unicode, force_unicode
620-from django.utils.translation import ugettext as _
621-from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
622-from django.utils.html import escape
623+from django.template.nodes import Node, NodeList, TextNode, ExpressionNode
624+from django.template.compiler import Origin, StringOrigin, Template, \
625+    TemplateSyntaxError, compile_string, TokenSyntaxError
626+from django.template.library import Library, InvalidTemplateLibrary, \
627+    get_library, add_to_builtins, libraries
628+from django.template.expressions import Expression, LookupError
629 
630-__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
631+#backcompat
632+from django.template.compat import Variable, VariableDoesNotExist, VariableNode, \
633+    resolve_variable, TokenParser
634+from django.template.loader import TemplateDoesNotExist
635 
636-TOKEN_TEXT = 0
637-TOKEN_VAR = 1
638-TOKEN_BLOCK = 2
639-TOKEN_COMMENT = 3
640+__all__ = (
641+    'Template', 'TemplateSyntaxError', 'compile_string', 'Origin', 'TokenSyntaxError',
642+    'Context', 'RequestContext',
643+    'Expression', 'LookupError',
644+    #FIXME: should these be public? 'Literal', 'Lookup', 'FilterExpression'
645+    'Library', 'InvalidTemplateLibrary', 'get_library', 'add_to_builtins', 'libraries',
646+    #backcompat
647+    'Variable', 'VariableDoesNotExist', 'resolve_variable', 'TemplateDoesNotExist', 'TokenParser', 'VariableNode',
648+)
649 
650-# template syntax constants
651-FILTER_SEPARATOR = '|'
652-FILTER_ARGUMENT_SEPARATOR = ':'
653-VARIABLE_ATTRIBUTE_SEPARATOR = '.'
654-BLOCK_TAG_START = '{%'
655-BLOCK_TAG_END = '%}'
656-VARIABLE_TAG_START = '{{'
657-VARIABLE_TAG_END = '}}'
658-COMMENT_TAG_START = '{#'
659-COMMENT_TAG_END = '#}'
660-SINGLE_BRACE_START = '{'
661-SINGLE_BRACE_END = '}'
662-
663-ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
664-
665-# what to report as the origin for templates that come from non-loader sources
666-# (e.g. strings)
667-UNKNOWN_SOURCE="&lt;unknown source&gt;"
668-
669-# match a variable or block tag and capture the entire tag, including start/end delimiters
670-tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
671-                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
672-                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
673-
674-# global dictionary of libraries that have been loaded using get_library
675-libraries = {}
676-# global list of libraries to load by default for a new parser
677-builtins = []
678-
679 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
680 # uninitialised.
681 invalid_var_format_string = None
682 
683-class TemplateSyntaxError(Exception):
684-    def __str__(self):
685-        try:
686-            import cStringIO as StringIO
687-        except ImportError:
688-            import StringIO
689-        output = StringIO.StringIO()
690-        output.write(Exception.__str__(self))
691-        # Check if we wrapped an exception and print that too.
692-        if hasattr(self, 'exc_info'):
693-            import traceback
694-            output.write('\n\nOriginal ')
695-            e = self.exc_info
696-            traceback.print_exception(e[0], e[1], e[2], 500, output)
697-        return output.getvalue()
698-
699-class TemplateDoesNotExist(Exception):
700-    pass
701-
702-class TemplateEncodingError(Exception):
703-    pass
704-
705-class VariableDoesNotExist(Exception):
706-
707-    def __init__(self, msg, params=()):
708-        self.msg = msg
709-        self.params = params
710-
711-    def __str__(self):
712-        return unicode(self).encode('utf-8')
713-
714-    def __unicode__(self):
715-        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
716-
717-class InvalidTemplateLibrary(Exception):
718-    pass
719-
720-class Origin(object):
721-    def __init__(self, name):
722-        self.name = name
723-
724-    def reload(self):
725-        raise NotImplementedError
726-
727-    def __str__(self):
728-        return self.name
729-
730-class StringOrigin(Origin):
731-    def __init__(self, source):
732-        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
733-        self.source = source
734-
735-    def reload(self):
736-        return self.source
737-
738-class Template(object):
739-    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
740-        try:
741-            template_string = smart_unicode(template_string)
742-        except UnicodeDecodeError:
743-            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
744-        if settings.TEMPLATE_DEBUG and origin is None:
745-            origin = StringOrigin(template_string)
746-        self.nodelist = compile_string(template_string, origin)
747-        self.name = name
748-
749-    def __iter__(self):
750-        for node in self.nodelist:
751-            for subnode in node:
752-                yield subnode
753-
754-    def render(self, context):
755-        "Display stage -- can be called many times"
756-        return self.nodelist.render(context)
757-
758-def compile_string(template_string, origin):
759-    "Compiles template_string into NodeList ready for rendering"
760-    if settings.TEMPLATE_DEBUG:
761-        from debug import DebugLexer, DebugParser
762-        lexer_class, parser_class = DebugLexer, DebugParser
763-    else:
764-        lexer_class, parser_class = Lexer, Parser
765-    lexer = lexer_class(template_string, origin)
766-    parser = parser_class(lexer.tokenize())
767-    return parser.parse()
768-
769-class Token(object):
770-    def __init__(self, token_type, contents):
771-        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
772-        self.token_type, self.contents = token_type, contents
773-
774-    def __str__(self):
775-        return '<%s token: "%s...">' % \
776-            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
777-            self.contents[:20].replace('\n', ''))
778-
779-    def split_contents(self):
780-        return list(smart_split(self.contents))
781-
782-class Lexer(object):
783-    def __init__(self, template_string, origin):
784-        self.template_string = template_string
785-        self.origin = origin
786-
787-    def tokenize(self):
788-        "Return a list of tokens from a given template_string."
789-        in_tag = False
790-        result = []
791-        for bit in tag_re.split(self.template_string):
792-            if bit:
793-                result.append(self.create_token(bit, in_tag))
794-            in_tag = not in_tag
795-        return result
796-
797-    def create_token(self, token_string, in_tag):
798-        """
799-        Convert the given token string into a new Token object and return it.
800-        If in_tag is True, we are processing something that matched a tag,
801-        otherwise it should be treated as a literal string.
802-        """
803-        if in_tag:
804-            if token_string.startswith(VARIABLE_TAG_START):
805-                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
806-            elif token_string.startswith(BLOCK_TAG_START):
807-                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
808-            elif token_string.startswith(COMMENT_TAG_START):
809-                token = Token(TOKEN_COMMENT, '')
810-        else:
811-            token = Token(TOKEN_TEXT, token_string)
812-        return token
813-
814-class Parser(object):
815-    def __init__(self, tokens):
816-        self.tokens = tokens
817-        self.tags = {}
818-        self.filters = {}
819-        for lib in builtins:
820-            self.add_library(lib)
821-
822-    def parse(self, parse_until=None):
823-        if parse_until is None: parse_until = []
824-        nodelist = self.create_nodelist()
825-        while self.tokens:
826-            token = self.next_token()
827-            if token.token_type == TOKEN_TEXT:
828-                self.extend_nodelist(nodelist, TextNode(token.contents), token)
829-            elif token.token_type == TOKEN_VAR:
830-                if not token.contents:
831-                    self.empty_variable(token)
832-                filter_expression = self.compile_filter(token.contents)
833-                var_node = self.create_variable_node(filter_expression)
834-                self.extend_nodelist(nodelist, var_node,token)
835-            elif token.token_type == TOKEN_BLOCK:
836-                if token.contents in parse_until:
837-                    # put token back on token list so calling code knows why it terminated
838-                    self.prepend_token(token)
839-                    return nodelist
840-                try:
841-                    command = token.contents.split()[0]
842-                except IndexError:
843-                    self.empty_block_tag(token)
844-                # execute callback function for this tag and append resulting node
845-                self.enter_command(command, token)
846-                try:
847-                    compile_func = self.tags[command]
848-                except KeyError:
849-                    self.invalid_block_tag(token, command)
850-                try:
851-                    compiled_result = compile_func(self, token)
852-                except TemplateSyntaxError, e:
853-                    if not self.compile_function_error(token, e):
854-                        raise
855-                self.extend_nodelist(nodelist, compiled_result, token)
856-                self.exit_command()
857-        if parse_until:
858-            self.unclosed_block_tag(parse_until)
859-        return nodelist
860-
861-    def skip_past(self, endtag):
862-        while self.tokens:
863-            token = self.next_token()
864-            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
865-                return
866-        self.unclosed_block_tag([endtag])
867-
868-    def create_variable_node(self, filter_expression):
869-        return VariableNode(filter_expression)
870-
871-    def create_nodelist(self):
872-        return NodeList()
873-
874-    def extend_nodelist(self, nodelist, node, token):
875-        if node.must_be_first and nodelist:
876-            try:
877-                if nodelist.contains_nontext:
878-                    raise AttributeError
879-            except AttributeError:
880-                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
881-        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
882-            nodelist.contains_nontext = True
883-        nodelist.append(node)
884-
885-    def enter_command(self, command, token):
886-        pass
887-
888-    def exit_command(self):
889-        pass
890-
891-    def error(self, token, msg):
892-        return TemplateSyntaxError(msg)
893-
894-    def empty_variable(self, token):
895-        raise self.error(token, "Empty variable tag")
896-
897-    def empty_block_tag(self, token):
898-        raise self.error(token, "Empty block tag")
899-
900-    def invalid_block_tag(self, token, command):
901-        raise self.error(token, "Invalid block tag: '%s'" % command)
902-
903-    def unclosed_block_tag(self, parse_until):
904-        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
905-
906-    def compile_function_error(self, token, e):
907-        pass
908-
909-    def next_token(self):
910-        return self.tokens.pop(0)
911-
912-    def prepend_token(self, token):
913-        self.tokens.insert(0, token)
914-
915-    def delete_first_token(self):
916-        del self.tokens[0]
917-
918-    def add_library(self, lib):
919-        self.tags.update(lib.tags)
920-        self.filters.update(lib.filters)
921-
922-    def compile_filter(self, token):
923-        "Convenient wrapper for FilterExpression"
924-        return FilterExpression(token, self)
925-
926-    def find_filter(self, filter_name):
927-        if filter_name in self.filters:
928-            return self.filters[filter_name]
929-        else:
930-            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
931-
932-class TokenParser(object):
933-    """
934-    Subclass this and implement the top() method to parse a template line. When
935-    instantiating the parser, pass in the line from the Django template parser.
936-
937-    The parser's "tagname" instance-variable stores the name of the tag that
938-    the filter was called with.
939-    """
940-    def __init__(self, subject):
941-        self.subject = subject
942-        self.pointer = 0
943-        self.backout = []
944-        self.tagname = self.tag()
945-
946-    def top(self):
947-        "Overload this method to do the actual parsing and return the result."
948-        raise NotImplementedError()
949-
950-    def more(self):
951-        "Returns True if there is more stuff in the tag."
952-        return self.pointer < len(self.subject)
953-
954-    def back(self):
955-        "Undoes the last microparser. Use this for lookahead and backtracking."
956-        if not len(self.backout):
957-            raise TemplateSyntaxError("back called without some previous parsing")
958-        self.pointer = self.backout.pop()
959-
960-    def tag(self):
961-        "A microparser that just returns the next tag from the line."
962-        subject = self.subject
963-        i = self.pointer
964-        if i >= len(subject):
965-            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
966-        p = i
967-        while i < len(subject) and subject[i] not in (' ', '\t'):
968-            i += 1
969-        s = subject[p:i]
970-        while i < len(subject) and subject[i] in (' ', '\t'):
971-            i += 1
972-        self.backout.append(self.pointer)
973-        self.pointer = i
974-        return s
975-
976-    def value(self):
977-        "A microparser that parses for a value: some string constant or variable name."
978-        subject = self.subject
979-        i = self.pointer
980-        if i >= len(subject):
981-            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
982-        if subject[i] in ('"', "'"):
983-            p = i
984-            i += 1
985-            while i < len(subject) and subject[i] != subject[p]:
986-                i += 1
987-            if i >= len(subject):
988-                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
989-            i += 1
990-            res = subject[p:i]
991-            while i < len(subject) and subject[i] in (' ', '\t'):
992-                i += 1
993-            self.backout.append(self.pointer)
994-            self.pointer = i
995-            return res
996-        else:
997-            p = i
998-            while i < len(subject) and subject[i] not in (' ', '\t'):
999-                if subject[i] in ('"', "'"):
1000-                    c = subject[i]
1001-                    i += 1
1002-                    while i < len(subject) and subject[i] != c:
1003-                        i += 1
1004-                    if i >= len(subject):
1005-                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
1006-                i += 1
1007-            s = subject[p:i]
1008-            while i < len(subject) and subject[i] in (' ', '\t'):
1009-                i += 1
1010-            self.backout.append(self.pointer)
1011-            self.pointer = i
1012-            return s
1013-
1014-filter_raw_string = r"""
1015-^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
1016-^"(?P<constant>%(str)s)"|
1017-^(?P<var>[%(var_chars)s]+)|
1018- (?:%(filter_sep)s
1019-     (?P<filter_name>\w+)
1020-         (?:%(arg_sep)s
1021-             (?:
1022-              %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
1023-              "(?P<constant_arg>%(str)s)"|
1024-              (?P<var_arg>[%(var_chars)s]+)
1025-             )
1026-         )?
1027- )""" % {
1028-    'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
1029-    'var_chars': "\w\." ,
1030-    'filter_sep': re.escape(FILTER_SEPARATOR),
1031-    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
1032-    'i18n_open' : re.escape("_("),
1033-    'i18n_close' : re.escape(")"),
1034-  }
1035-
1036-filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
1037-filter_re = re.compile(filter_raw_string, re.UNICODE)
1038-
1039-class FilterExpression(object):
1040-    """
1041-    Parses a variable token and its optional filters (all as a single string),
1042-    and return a list of tuples of the filter name and arguments.
1043-    Sample:
1044-        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
1045-        >>> p = Parser('')
1046-        >>> fe = FilterExpression(token, p)
1047-        >>> len(fe.filters)
1048-        2
1049-        >>> fe.var
1050-        <Variable: 'variable'>
1051-
1052-    This class should never be instantiated outside of the
1053-    get_filters_from_token helper function.
1054-    """
1055-    def __init__(self, token, parser):
1056-        self.token = token
1057-        matches = filter_re.finditer(token)
1058-        var = None
1059-        filters = []
1060-        upto = 0
1061-        for match in matches:
1062-            start = match.start()
1063-            if upto != start:
1064-                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
1065-                                           (token[:upto], token[upto:start], token[start:]))
1066-            if var == None:
1067-                var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
1068-                if i18n_constant:
1069-                    var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
1070-                elif constant:
1071-                    var = '"%s"' % constant.replace(r'\"', '"')
1072-                upto = match.end()
1073-                if var == None:
1074-                    raise TemplateSyntaxError("Could not find variable at start of %s" % token)
1075-                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
1076-                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
1077-            else:
1078-                filter_name = match.group("filter_name")
1079-                args = []
1080-                constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
1081-                if i18n_arg:
1082-                    args.append((False, _(i18n_arg.replace(r'\"', '"'))))
1083-                elif constant_arg is not None:
1084-                    args.append((False, constant_arg.replace(r'\"', '"')))
1085-                elif var_arg:
1086-                    args.append((True, Variable(var_arg)))
1087-                filter_func = parser.find_filter(filter_name)
1088-                self.args_check(filter_name,filter_func, args)
1089-                filters.append( (filter_func,args))
1090-                upto = match.end()
1091-        if upto != len(token):
1092-            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
1093-        self.filters = filters
1094-        self.var = Variable(var)
1095-
1096-    def resolve(self, context, ignore_failures=False):
1097-        try:
1098-            obj = self.var.resolve(context)
1099-        except VariableDoesNotExist:
1100-            if ignore_failures:
1101-                obj = None
1102-            else:
1103-                if settings.TEMPLATE_STRING_IF_INVALID:
1104-                    global invalid_var_format_string
1105-                    if invalid_var_format_string is None:
1106-                        invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
1107-                    if invalid_var_format_string:
1108-                        return settings.TEMPLATE_STRING_IF_INVALID % self.var
1109-                    return settings.TEMPLATE_STRING_IF_INVALID
1110-                else:
1111-                    obj = settings.TEMPLATE_STRING_IF_INVALID
1112-        for func, args in self.filters:
1113-            arg_vals = []
1114-            for lookup, arg in args:
1115-                if not lookup:
1116-                    arg_vals.append(mark_safe(arg))
1117-                else:
1118-                    arg_vals.append(arg.resolve(context))
1119-            if getattr(func, 'needs_autoescape', False):
1120-                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
1121-            else:
1122-                new_obj = func(obj, *arg_vals)
1123-            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
1124-                obj = mark_safe(new_obj)
1125-            elif isinstance(obj, EscapeData):
1126-                obj = mark_for_escaping(new_obj)
1127-            else:
1128-                obj = new_obj
1129-        return obj
1130-
1131-    def args_check(name, func, provided):
1132-        provided = list(provided)
1133-        plen = len(provided)
1134-        # Check to see if a decorator is providing the real function.
1135-        func = getattr(func, '_decorated_function', func)
1136-        args, varargs, varkw, defaults = getargspec(func)
1137-        # First argument is filter input.
1138-        args.pop(0)
1139-        if defaults:
1140-            nondefs = args[:-len(defaults)]
1141-        else:
1142-            nondefs = args
1143-        # Args without defaults must be provided.
1144-        try:
1145-            for arg in nondefs:
1146-                provided.pop(0)
1147-        except IndexError:
1148-            # Not enough
1149-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
1150-
1151-        # Defaults can be overridden.
1152-        defaults = defaults and list(defaults) or []
1153-        try:
1154-            for parg in provided:
1155-                defaults.pop(0)
1156-        except IndexError:
1157-            # Too many.
1158-            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
1159-
1160-        return True
1161-    args_check = staticmethod(args_check)
1162-
1163-    def __str__(self):
1164-        return self.token
1165-
1166-def resolve_variable(path, context):
1167-    """
1168-    Returns the resolved variable, which may contain attribute syntax, within
1169-    the given context.
1170-
1171-    Deprecated; use the Variable class instead.
1172-    """
1173-    return Variable(path).resolve(context)
1174-
1175-class Variable(object):
1176-    """
1177-    A template variable, resolvable against a given context. The variable may be
1178-    a hard-coded string (if it begins and ends with single or double quote
1179-    marks)::
1180-
1181-        >>> c = {'article': {'section':u'News'}}
1182-        >>> Variable('article.section').resolve(c)
1183-        u'News'
1184-        >>> Variable('article').resolve(c)
1185-        {'section': u'News'}
1186-        >>> class AClass: pass
1187-        >>> c = AClass()
1188-        >>> c.article = AClass()
1189-        >>> c.article.section = u'News'
1190-        >>> Variable('article.section').resolve(c)
1191-        u'News'
1192-
1193-    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
1194-    """
1195-
1196-    def __init__(self, var):
1197-        self.var = var
1198-        self.literal = None
1199-        self.lookups = None
1200-        self.translate = False
1201-
1202-        try:
1203-            # First try to treat this variable as a number.
1204-            #
1205-            # Note that this could cause an OverflowError here that we're not
1206-            # catching. Since this should only happen at compile time, that's
1207-            # probably OK.
1208-            self.literal = float(var)
1209-
1210-            # So it's a float... is it an int? If the original value contained a
1211-            # dot or an "e" then it was a float, not an int.
1212-            if '.' not in var and 'e' not in var.lower():
1213-                self.literal = int(self.literal)
1214-
1215-            # "2." is invalid
1216-            if var.endswith('.'):
1217-                raise ValueError
1218-
1219-        except ValueError:
1220-            # A ValueError means that the variable isn't a number.
1221-            if var.startswith('_(') and var.endswith(')'):
1222-                # The result of the lookup should be translated at rendering
1223-                # time.
1224-                self.translate = True
1225-                var = var[2:-1]
1226-            # If it's wrapped with quotes (single or double), then
1227-            # we're also dealing with a literal.
1228-            if var[0] in "\"'" and var[0] == var[-1]:
1229-                self.literal = mark_safe(var[1:-1])
1230-            else:
1231-                # Otherwise we'll set self.lookups so that resolve() knows we're
1232-                # dealing with a bonafide variable
1233-                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
1234-
1235-    def resolve(self, context):
1236-        """Resolve this variable against a given context."""
1237-        if self.lookups is not None:
1238-            # We're dealing with a variable that needs to be resolved
1239-            value = self._resolve_lookup(context)
1240-        else:
1241-            # We're dealing with a literal, so it's already been "resolved"
1242-            value = self.literal
1243-        if self.translate:
1244-            return _(value)
1245-        return value
1246-
1247-    def __repr__(self):
1248-        return "<%s: %r>" % (self.__class__.__name__, self.var)
1249-
1250-    def __str__(self):
1251-        return self.var
1252-
1253-    def _resolve_lookup(self, context):
1254-        """
1255-        Performs resolution of a real variable (i.e. not a literal) against the
1256-        given context.
1257-
1258-        As indicated by the method's name, this method is an implementation
1259-        detail and shouldn't be called by external code. Use Variable.resolve()
1260-        instead.
1261-        """
1262-        current = context
1263-        for bit in self.lookups:
1264-            try: # dictionary lookup
1265-                current = current[bit]
1266-            except (TypeError, AttributeError, KeyError):
1267-                try: # attribute lookup
1268-                    current = getattr(current, bit)
1269-                    if callable(current):
1270-                        if getattr(current, 'alters_data', False):
1271-                            current = settings.TEMPLATE_STRING_IF_INVALID
1272-                        else:
1273-                            try: # method call (assuming no args required)
1274-                                current = current()
1275-                            except TypeError: # arguments *were* required
1276-                                # GOTCHA: This will also catch any TypeError
1277-                                # raised in the function itself.
1278-                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
1279-                            except Exception, e:
1280-                                if getattr(e, 'silent_variable_failure', False):
1281-                                    current = settings.TEMPLATE_STRING_IF_INVALID
1282-                                else:
1283-                                    raise
1284-                except (TypeError, AttributeError):
1285-                    try: # list-index lookup
1286-                        current = current[int(bit)]
1287-                    except (IndexError, # list index out of range
1288-                            ValueError, # invalid literal for int()
1289-                            KeyError,   # current is a dict without `int(bit)` key
1290-                            TypeError,  # unsubscriptable object
1291-                            ):
1292-                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
1293-                except Exception, e:
1294-                    if getattr(e, 'silent_variable_failure', False):
1295-                        current = settings.TEMPLATE_STRING_IF_INVALID
1296-                    else:
1297-                        raise
1298-
1299-        return current
1300-
1301-class Node(object):
1302-    # Set this to True for nodes that must be first in the template (although
1303-    # they can be preceded by text nodes.
1304-    must_be_first = False
1305-
1306-    def render(self, context):
1307-        "Return the node rendered as a string"
1308-        pass
1309-
1310-    def __iter__(self):
1311-        yield self
1312-
1313-    def get_nodes_by_type(self, nodetype):
1314-        "Return a list of all nodes (within this node and its nodelist) of the given type"
1315-        nodes = []
1316-        if isinstance(self, nodetype):
1317-            nodes.append(self)
1318-        if hasattr(self, 'nodelist'):
1319-            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
1320-        return nodes
1321-
1322-class NodeList(list):
1323-    # Set to True the first time a non-TextNode is inserted by
1324-    # extend_nodelist().
1325-    contains_nontext = False
1326-
1327-    def render(self, context):
1328-        bits = []
1329-        for node in self:
1330-            if isinstance(node, Node):
1331-                bits.append(self.render_node(node, context))
1332-            else:
1333-                bits.append(node)
1334-        return mark_safe(''.join([force_unicode(b) for b in bits]))
1335-
1336-    def get_nodes_by_type(self, nodetype):
1337-        "Return a list of all nodes of the given type"
1338-        nodes = []
1339-        for node in self:
1340-            nodes.extend(node.get_nodes_by_type(nodetype))
1341-        return nodes
1342-
1343-    def render_node(self, node, context):
1344-        return node.render(context)
1345-
1346-class TextNode(Node):
1347-    def __init__(self, s):
1348-        self.s = s
1349-
1350-    def __repr__(self):
1351-        return "<Text Node: '%s'>" % self.s[:25]
1352-
1353-    def render(self, context):
1354-        return self.s
1355-
1356-class VariableNode(Node):
1357-    def __init__(self, filter_expression):
1358-        self.filter_expression = filter_expression
1359-
1360-    def __repr__(self):
1361-        return "<Variable Node: %s>" % self.filter_expression
1362-
1363-    def render(self, context):
1364-        try:
1365-            output = force_unicode(self.filter_expression.resolve(context))
1366-        except UnicodeDecodeError:
1367-            # Unicode conversion can fail sometimes for reasons out of our
1368-            # control (e.g. exception rendering). In that case, we fail quietly.
1369-            return ''
1370-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
1371-            return force_unicode(escape(output))
1372-        else:
1373-            return force_unicode(output)
1374-
1375-def generic_tag_compiler(params, defaults, name, node_class, parser, token):
1376-    "Returns a template.Node subclass."
1377-    bits = token.split_contents()[1:]
1378-    bmax = len(params)
1379-    def_len = defaults and len(defaults) or 0
1380-    bmin = bmax - def_len
1381-    if(len(bits) < bmin or len(bits) > bmax):
1382-        if bmin == bmax:
1383-            message = "%s takes %s arguments" % (name, bmin)
1384-        else:
1385-            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
1386-        raise TemplateSyntaxError(message)
1387-    return node_class(bits)
1388-
1389-class Library(object):
1390-    def __init__(self):
1391-        self.filters = {}
1392-        self.tags = {}
1393-
1394-    def tag(self, name=None, compile_function=None):
1395-        if name == None and compile_function == None:
1396-            # @register.tag()
1397-            return self.tag_function
1398-        elif name != None and compile_function == None:
1399-            if(callable(name)):
1400-                # @register.tag
1401-                return self.tag_function(name)
1402-            else:
1403-                # @register.tag('somename') or @register.tag(name='somename')
1404-                def dec(func):
1405-                    return self.tag(name, func)
1406-                return dec
1407-        elif name != None and compile_function != None:
1408-            # register.tag('somename', somefunc)
1409-            self.tags[name] = compile_function
1410-            return compile_function
1411-        else:
1412-            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
1413-
1414-    def tag_function(self,func):
1415-        self.tags[getattr(func, "_decorated_function", func).__name__] = func
1416-        return func
1417-
1418-    def filter(self, name=None, filter_func=None):
1419-        if name == None and filter_func == None:
1420-            # @register.filter()
1421-            return self.filter_function
1422-        elif filter_func == None:
1423-            if(callable(name)):
1424-                # @register.filter
1425-                return self.filter_function(name)
1426-            else:
1427-                # @register.filter('somename') or @register.filter(name='somename')
1428-                def dec(func):
1429-                    return self.filter(name, func)
1430-                return dec
1431-        elif name != None and filter_func != None:
1432-            # register.filter('somename', somefunc)
1433-            self.filters[name] = filter_func
1434-            return filter_func
1435-        else:
1436-            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
1437-
1438-    def filter_function(self, func):
1439-        self.filters[getattr(func, "_decorated_function", func).__name__] = func
1440-        return func
1441-
1442-    def simple_tag(self,func):
1443-        params, xx, xxx, defaults = getargspec(func)
1444-
1445-        class SimpleNode(Node):
1446-            def __init__(self, vars_to_resolve):
1447-                self.vars_to_resolve = map(Variable, vars_to_resolve)
1448-
1449-            def render(self, context):
1450-                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
1451-                return func(*resolved_vars)
1452-
1453-        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
1454-        compile_func.__doc__ = func.__doc__
1455-        self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
1456-        return func
1457-
1458-    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
1459-        def dec(func):
1460-            params, xx, xxx, defaults = getargspec(func)
1461-            if takes_context:
1462-                if params[0] == 'context':
1463-                    params = params[1:]
1464-                else:
1465-                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
1466-
1467-            class InclusionNode(Node):
1468-                def __init__(self, vars_to_resolve):
1469-                    self.vars_to_resolve = map(Variable, vars_to_resolve)
1470-
1471-                def render(self, context):
1472-                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
1473-                    if takes_context:
1474-                        args = [context] + resolved_vars
1475-                    else:
1476-                        args = resolved_vars
1477-
1478-                    dict = func(*args)
1479-
1480-                    if not getattr(self, 'nodelist', False):
1481-                        from django.template.loader import get_template, select_template
1482-                        if not isinstance(file_name, basestring) and is_iterable(file_name):
1483-                            t = select_template(file_name)
1484-                        else:
1485-                            t = get_template(file_name)
1486-                        self.nodelist = t.nodelist
1487-                    return self.nodelist.render(context_class(dict,
1488-                            autoescape=context.autoescape))
1489-
1490-            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
1491-            compile_func.__doc__ = func.__doc__
1492-            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
1493-            return func
1494-        return dec
1495-
1496-def get_library(module_name):
1497-    lib = libraries.get(module_name, None)
1498-    if not lib:
1499-        try:
1500-            mod = __import__(module_name, {}, {}, [''])
1501-        except ImportError, e:
1502-            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
1503-        try:
1504-            lib = mod.register
1505-            libraries[module_name] = lib
1506-        except AttributeError:
1507-            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
1508-    return lib
1509-
1510-def add_to_builtins(module_name):
1511-    builtins.append(get_library(module_name))
1512-
1513 add_to_builtins('django.template.defaulttags')
1514 add_to_builtins('django.template.defaultfilters')
1515Index: django/template/utils.py
1516===================================================================
1517--- django/template/utils.py    (revision 0)
1518+++ django/template/utils.py    (revision 0)
1519@@ -0,0 +1,111 @@
1520+import re
1521+
1522+from django.template import Node, NodeList, TokenSyntaxError
1523+
1524+class EmptyNode(Node):
1525+    def render(self, context):
1526+        return u''
1527+       
1528+class ConditionalNode(Node):
1529+    def __init__(self, nodelist_true, nodelist_false):
1530+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1531+
1532+    def __iter__(self):
1533+        for node in self.nodelist_true:
1534+            yield node
1535+        for node in self.nodelist_false:
1536+            yield node
1537+
1538+    def get_nodes_by_type(self, nodetype):
1539+        nodes = []
1540+        if isinstance(self, nodetype):
1541+            nodes.append(self)
1542+        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
1543+        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
1544+        return nodes
1545+       
1546+    def check_condition(self, context):
1547+        return False
1548+       
1549+    def render(self, context):
1550+        if self.check_condition(context):
1551+            return self.nodelist_true.render(context)
1552+        else:
1553+            return self.nodelist_false.render(context)       
1554+
1555+def parse_conditional_nodelists(parser, name):
1556+    end_tag = 'end' + name
1557+    nodelist_true = parser.parse(('else', end_tag))
1558+    token = parser.next_token()
1559+    if token.contents == 'else':
1560+        nodelist_false = parser.parse((end_tag,))
1561+        parser.delete_first_token()
1562+    else:
1563+        nodelist_false = NodeList()
1564+    return nodelist_true, nodelist_false
1565+
1566+def parse_args_and_kwargs(self):
1567+    args = []
1568+    kwargs = {}
1569+    while True:
1570+        name = self.pop_name()
1571+        if name and self.pop_lexem('='):
1572+            try:
1573+                kwargs[name] = self.parse_expression()
1574+            except TokenSyntaxError:
1575+                raise TemplateSyntaxError, "expected expression in kwargs"
1576+        else:
1577+            if name:
1578+                self.pushback()
1579+            try:
1580+                args.append(self.parse_expression())
1581+            except TokenSyntaxError:
1582+                break
1583+        if not self.pop_lexem(','):
1584+            break
1585+    return args, kwargs
1586+
1587+def parse_as(bits):
1588+    if bits.pop_lexem('as'):
1589+        name = bits.pop_name()
1590+        if name:
1591+            return name
1592+    raise bits.exception
1593+
1594+def parse_context_map(bits):
1595+    context_map = {}
1596+    try:
1597+        while True:
1598+            expr = bits.parse_expression()
1599+            name = parse_as(bits)
1600+            context_map[name] = expr
1601+            if not bits.pop_lexem(','):
1602+                break
1603+    except TokenSyntaxError:
1604+        bits.expected("context map")
1605+    return context_map
1606+
1607+def resolve_args_and_kwargs(args, kwargs, context):
1608+       resolved_args = [arg.resolve(context, True) for arg in args]
1609+       resolved_kwargs = {}
1610+       for name in kwargs:
1611+               resolved_kwargs[name] = kwargs[name].resolve(context, True)
1612+       return resolved_args, resolved_kwargs
1613+   
1614+def render_with_context_map(renderable, context_map, context):
1615+    """
1616+    renderable: a NodeList or Template
1617+    context_map: a dict mapping strings to FilterExpressions
1618+    context: a Context object
1619+   
1620+    """
1621+    if context_map is None:
1622+        return renderable.render(context)
1623+    d = {}
1624+    for name in context_map:
1625+        d[name] = context_map[name].resolve_safe(context)
1626+    context.update(d)
1627+    output = renderable.render(context)
1628+    context.pop()
1629+    return output
1630+
1631Index: django/template/expressions.py
1632===================================================================
1633--- django/template/expressions.py      (revision 0)
1634+++ django/template/expressions.py      (revision 0)
1635@@ -0,0 +1,116 @@
1636+from django.conf import settings
1637+from django.utils.encoding import force_unicode
1638+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
1639+
1640+__all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression')
1641+
1642+VARIABLE_ATTRIBUTE_SEPARATOR = '.'
1643+
1644+class LookupError(Exception):
1645+    def __init__(self, var, msg, params=()):
1646+        self.var = var
1647+        self.msg = msg
1648+        self.params = params
1649+
1650+    def __str__(self):
1651+        return unicode(self).encode('utf-8')
1652+
1653+    def __unicode__(self):
1654+        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
1655+
1656+
1657+class Expression(object):
1658+    def resolve_safe(self, context, default=None):
1659+        try:
1660+            return self.resolve(context)
1661+        except LookupError:
1662+            return default
1663+
1664+    def resolve(self, context):
1665+        pass
1666+
1667+class Literal(Expression):
1668+    def __init__(self, value):
1669+        self.value = value
1670+
1671+    def resolve(self, context):
1672+        return self.value
1673+       
1674+class Lookup(Expression):
1675+    def __init__(self, var):
1676+        self.var = var
1677+        self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
1678+
1679+    def __str__(self):
1680+        return self.var
1681+       
1682+    def resolve(self, context):
1683+        current = context
1684+        for bit in self.lookups:
1685+            try: # dictionary lookup
1686+                current = current[bit]
1687+            except (TypeError, AttributeError, KeyError):
1688+                try: # attribute lookup
1689+                    current = getattr(current, bit)
1690+                    if callable(current):
1691+                        if getattr(current, 'alters_data', False):
1692+                            current = settings.TEMPLATE_STRING_IF_INVALID
1693+                        else:
1694+                            try: # method call (assuming no args required)
1695+                                current = current()
1696+                            except TypeError: # arguments *were* required
1697+                                # GOTCHA: This will also catch any TypeError
1698+                                # raised in the function itself.
1699+                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
1700+                            except Exception, e:
1701+                                if getattr(e, 'silent_variable_failure', False):
1702+                                    current = settings.TEMPLATE_STRING_IF_INVALID
1703+                                else:
1704+                                    raise
1705+                except (TypeError, AttributeError):
1706+                    try: # list-index lookup
1707+                        current = current[int(bit)]
1708+                    except (IndexError, # list index out of range
1709+                            ValueError, # invalid literal for int()
1710+                            KeyError,   # current is a dict without `int(bit)` key
1711+                            TypeError,  # unsubscriptable object
1712+                            ):
1713+                        raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
1714+                except Exception, e:
1715+                    if getattr(e, 'silent_variable_failure', False):
1716+                        current = settings.TEMPLATE_STRING_IF_INVALID
1717+                    else:
1718+                        raise
1719+
1720+        return current
1721+
1722+class FilterExpression(Expression):
1723+    def __init__(self, root, filters):
1724+        self.root = root
1725+        self.filters = filters
1726+
1727+    def resolve(self, context):
1728+        try:
1729+            obj = self.root.resolve(context)
1730+        except LookupError:
1731+            if not self.filters:
1732+                raise
1733+            obj = settings.TEMPLATE_STRING_IF_INVALID
1734+        for func, args in self.filters:
1735+            arg_vals = []         
1736+            for arg in args:
1737+                arg_vals.append(arg.resolve(context))
1738+            if getattr(func, 'needs_autoescape', False):
1739+                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
1740+            else:
1741+                new_obj = func(obj, *arg_vals)
1742+            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
1743+                obj = mark_safe(new_obj)
1744+            elif isinstance(obj, EscapeData):
1745+                obj = mark_for_escaping(new_obj)
1746+            else:
1747+                obj = new_obj
1748+        return obj
1749+
1750+    def __str__(self):
1751+        return str(self.root)+'|<filtered>'
1752Index: django/template/defaulttags.py
1753===================================================================
1754--- django/template/defaulttags.py      (revision 7884)
1755+++ django/template/defaulttags.py      (working copy)
1756@@ -9,10 +9,12 @@
1757     from django.utils.itercompat import reversed     # Python 2.3 fallback
1758 
1759 from django.template import Node, NodeList, Template, Context, Variable
1760-from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
1761-from django.template import get_library, Library, InvalidTemplateLibrary
1762+from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError
1763+from django.template.library import get_library, Library, InvalidTemplateLibrary
1764+from django.template.compiler import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
1765+from django.template.utils import EmptyNode, ConditionalNode, render_with_context_map, parse_context_map, parse_conditional_nodelists, parse_as, parse_args_and_kwargs
1766 from django.conf import settings
1767-from django.utils.encoding import smart_str, smart_unicode
1768+from django.utils.encoding import smart_str, force_unicode
1769 from django.utils.itercompat import groupby
1770 from django.utils.safestring import mark_safe
1771 
1772@@ -33,17 +35,15 @@
1773         else:
1774             return output
1775 
1776-class CommentNode(Node):
1777-    def render(self, context):
1778-        return ''
1779+class CommentNode(EmptyNode): pass
1780 
1781 class CycleNode(Node):
1782-    def __init__(self, cyclevars, variable_name=None):
1783-        self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
1784+    def __init__(self, cyclevals, variable_name=None):
1785+        self.cycle_iter = itertools_cycle(cyclevals)
1786         self.variable_name = variable_name
1787 
1788     def render(self, context):
1789-        value = self.cycle_iter.next().resolve(context)
1790+        value = self.cycle_iter.next().resolve_safe(context)
1791         if self.variable_name:
1792             context[self.variable_name] = value
1793         return value
1794@@ -62,31 +62,27 @@
1795 
1796     def render(self, context):
1797         output = self.nodelist.render(context)
1798-        # Apply filters.
1799         context.update({'var': output})
1800-        filtered = self.filter_expr.resolve(context)
1801+        filtered = self.filter_expr.resolve_safe(context, default='')
1802         context.pop()
1803         return filtered
1804 
1805 class FirstOfNode(Node):
1806-    def __init__(self, vars):
1807-        self.vars = map(Variable, vars)
1808+    def __init__(self, vals):
1809+        self.vals = vals
1810 
1811     def render(self, context):
1812-        for var in self.vars:
1813-            try:
1814-                value = var.resolve(context)
1815-            except VariableDoesNotExist:
1816-                continue
1817+        for val in self.vals:
1818+            value = val.resolve_safe(context)
1819             if value:
1820-                return smart_unicode(value)
1821+                return value
1822         return u''
1823 
1824 class ForNode(Node):
1825-    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
1826+    def __init__(self, loopvars, sequence, is_reversed, nodelist):
1827         self.loopvars, self.sequence = loopvars, sequence
1828         self.is_reversed = is_reversed
1829-        self.nodelist_loop = nodelist_loop
1830+        self.nodelist = nodelist
1831 
1832     def __repr__(self):
1833         reversed_text = self.is_reversed and ' reversed' or ''
1834@@ -98,24 +94,14 @@
1835         for node in self.nodelist_loop:
1836             yield node
1837 
1838-    def get_nodes_by_type(self, nodetype):
1839-        nodes = []
1840-        if isinstance(self, nodetype):
1841-            nodes.append(self)
1842-        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
1843-        return nodes
1844-
1845     def render(self, context):
1846-        nodelist = NodeList()
1847+        result = []
1848         if 'forloop' in context:
1849             parentloop = context['forloop']
1850         else:
1851             parentloop = {}
1852         context.push()
1853-        try:
1854-            values = self.sequence.resolve(context, True)
1855-        except VariableDoesNotExist:
1856-            values = []
1857+        values = self.sequence.resolve_safe(context, default=[])
1858         if values is None:
1859             values = []
1860         if not hasattr(values, '__len__'):
1861@@ -144,8 +130,8 @@
1862                 context.update(dict(zip(self.loopvars, item)))
1863             else:
1864                 context[self.loopvars[0]] = item
1865-            for node in self.nodelist_loop:
1866-                nodelist.append(node.render(context))
1867+            for node in self.nodelist:
1868+                result.append(node.render(context))
1869             if unpack:
1870                 # The loop variables were pushed on to the context so pop them
1871                 # off again. This is necessary because the tag lets the length
1872@@ -154,13 +140,13 @@
1873                 # context.
1874                 context.pop()
1875         context.pop()
1876-        return nodelist.render(context)
1877+        return mark_safe(''.join([force_unicode(b) for b in result]))
1878 
1879 class IfChangedNode(Node):
1880-    def __init__(self, nodelist, *varlist):
1881+    def __init__(self, nodelist, *vallist):
1882         self.nodelist = nodelist
1883         self._last_seen = None
1884-        self._varlist = map(Variable, varlist)
1885+        self._vallist = vallist
1886         self._id = str(id(self))
1887 
1888     def render(self, context):
1889@@ -168,10 +154,10 @@
1890             self._last_seen = None
1891             context['forloop'][self._id] = 1
1892         try:
1893-            if self._varlist:
1894+            if self._vallist:
1895                 # Consider multiple parameters.  This automatically behaves
1896                 # like an OR evaluation of the multiple variables.
1897-                compare_to = [var.resolve(context) for var in self._varlist]
1898+                compare_to = [var.resolve(context) for var in self._vallist]
1899             else:
1900                 compare_to = self.nodelist.render(context)
1901         except VariableDoesNotExist:
1902@@ -188,82 +174,50 @@
1903         else:
1904             return ''
1905 
1906-class IfEqualNode(Node):
1907-    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
1908-        self.var1, self.var2 = Variable(var1), Variable(var2)
1909-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1910+class IfEqualNode(ConditionalNode):
1911+    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
1912+        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
1913+        self.val1, self.val2 = val1, val2       
1914         self.negate = negate
1915 
1916     def __repr__(self):
1917         return "<IfEqualNode>"
1918 
1919-    def render(self, context):
1920-        try:
1921-            val1 = self.var1.resolve(context)
1922-        except VariableDoesNotExist:
1923-            val1 = None
1924-        try:
1925-            val2 = self.var2.resolve(context)
1926-        except VariableDoesNotExist:
1927-            val2 = None
1928-        if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
1929-            return self.nodelist_true.render(context)
1930-        return self.nodelist_false.render(context)
1931+    def check_condition(self, context):
1932+        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
1933+        return self.negate == (val1 != val2)
1934 
1935-class IfNode(Node):
1936+class IfNode(ConditionalNode):
1937     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
1938-        self.bool_exprs = bool_exprs
1939-        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
1940+        super(IfNode, self).__init__(nodelist_true, nodelist_false)
1941+        self.bool_exprs = bool_exprs       
1942         self.link_type = link_type
1943 
1944     def __repr__(self):
1945         return "<If node>"
1946 
1947-    def __iter__(self):
1948-        for node in self.nodelist_true:
1949-            yield node
1950-        for node in self.nodelist_false:
1951-            yield node
1952-
1953-    def get_nodes_by_type(self, nodetype):
1954-        nodes = []
1955-        if isinstance(self, nodetype):
1956-            nodes.append(self)
1957-        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
1958-        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
1959-        return nodes
1960-
1961-    def render(self, context):
1962-        if self.link_type == IfNode.LinkTypes.or_:
1963-            for ifnot, bool_expr in self.bool_exprs:
1964-                try:
1965-                    value = bool_expr.resolve(context, True)
1966-                except VariableDoesNotExist:
1967-                    value = None
1968-                if (value and not ifnot) or (ifnot and not value):
1969-                    return self.nodelist_true.render(context)
1970-            return self.nodelist_false.render(context)
1971+    def check_condition(self, context):
1972+        if self.link_type == 'or':
1973+            for negated, bool_expr in self.bool_exprs:
1974+                value = bool_expr.resolve_safe(context, default=False)
1975+                if bool(value) != negated:
1976+                    return True
1977+            return False           
1978         else:
1979-            for ifnot, bool_expr in self.bool_exprs:
1980-                try:
1981-                    value = bool_expr.resolve(context, True)
1982-                except VariableDoesNotExist:
1983-                    value = None
1984-                if not ((value and not ifnot) or (ifnot and not value)):
1985-                    return self.nodelist_false.render(context)
1986-            return self.nodelist_true.render(context)
1987+            for negated, bool_expr in self.bool_exprs:
1988+                value = bool_expr.resolve_safe(context, default=False)
1989+                if bool(value) == negated:
1990+                    return False
1991+            return True
1992+       
1993 
1994-    class LinkTypes:
1995-        and_ = 0,
1996-        or_ = 1
1997-
1998 class RegroupNode(Node):
1999     def __init__(self, target, expression, var_name):
2000         self.target, self.expression = target, expression
2001         self.var_name = var_name
2002 
2003     def render(self, context):
2004-        obj_list = self.target.resolve(context, True)
2005+        obj_list = self.target.resolve_safe(context)
2006         if obj_list == None:
2007             # target variable wasn't found in context; fail silently.
2008             context[self.var_name] = []
2009@@ -273,7 +227,7 @@
2010         context[self.var_name] = [
2011             {'grouper': key, 'list': list(val)}
2012             for key, val in
2013-            groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
2014+            groupby(obj_list, lambda v, f=self.expression.resolve: f(v))
2015         ]
2016         return ''
2017 
2018@@ -310,9 +264,7 @@
2019                     return '' # Fail silently for invalid included templates.
2020         return output
2021 
2022-class LoadNode(Node):
2023-    def render(self, context):
2024-        return ''
2025+class LoadNode(EmptyNode): pass
2026 
2027 class NowNode(Node):
2028     def __init__(self, format_string):
2029@@ -322,7 +274,7 @@
2030         from datetime import datetime
2031         from django.utils.dateformat import DateFormat
2032         df = DateFormat(datetime.now())
2033-        return df.format(self.format_string)
2034+        return df.format(self.format_string.resolve_safe(context))
2035 
2036 class SpacelessNode(Node):
2037     def __init__(self, nodelist):
2038@@ -357,8 +309,8 @@
2039 
2040     def render(self, context):
2041         from django.core.urlresolvers import reverse, NoReverseMatch
2042-        args = [arg.resolve(context) for arg in self.args]
2043-        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
2044+        args = [arg.resolve_safe(context) for arg in self.args]
2045+        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context))
2046                        for k, v in self.kwargs.items()])
2047         try:
2048             return reverse(self.view_name, args=args, kwargs=kwargs)
2049@@ -391,21 +343,15 @@
2050         return str(int(round(ratio)))
2051 
2052 class WithNode(Node):
2053-    def __init__(self, var, name, nodelist):
2054-        self.var = var
2055-        self.name = name
2056+    def __init__(self, context_map, nodelist):
2057+        self.context_map = context_map
2058         self.nodelist = nodelist
2059 
2060     def __repr__(self):
2061         return "<WithNode>"
2062 
2063     def render(self, context):
2064-        val = self.var.resolve(context)
2065-        context.push()
2066-        context[self.name] = val
2067-        output = self.nodelist.render(context)
2068-        context.pop()
2069-        return output
2070+        return render_with_context_map(self.nodelist, self.context_map, context)
2071 
2072 #@register.tag
2073 def autoescape(parser, token):
2074@@ -418,8 +364,7 @@
2075     arg = args[1]
2076     if arg not in (u'on', u'off'):
2077         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
2078-    nodelist = parser.parse(('endautoescape',))
2079-    parser.delete_first_token()
2080+    nodelist = parser.parse_nodelist(('endautoescape',))
2081     return AutoEscapeControlNode((arg == 'on'), nodelist)
2082 autoescape = register.tag(autoescape)
2083 
2084@@ -489,12 +434,14 @@
2085 
2086     if len(args) > 4 and args[-2] == 'as':
2087         name = args[-1]
2088-        node = CycleNode(args[1:-2], name)
2089+        values = [parser.compile_filter(arg) for arg in args[1:-2]]
2090+        node = CycleNode(values, name)
2091         if not hasattr(parser, '_namedCycleNodes'):
2092             parser._namedCycleNodes = {}
2093         parser._namedCycleNodes[name] = node
2094     else:
2095-        node = CycleNode(args[1:])
2096+        values = [parser.compile_filter(arg) for arg in args[1:]]
2097+        node = CycleNode(values)
2098     return node
2099 cycle = register.tag(cycle)
2100 
2101@@ -527,12 +474,11 @@
2102         {% endfilter %}
2103     """
2104     _, rest = token.contents.split(None, 1)
2105-    filter_expr = parser.compile_filter("var|%s" % (rest))
2106+    filter_expr = parser.compile_filter("var|%s" % rest)
2107     for func, unused in filter_expr.filters:
2108         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
2109             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
2110-    nodelist = parser.parse(('endfilter',))
2111-    parser.delete_first_token()
2112+    nodelist = parser.parse_nodelist(('endfilter',))
2113     return FilterNode(filter_expr, nodelist)
2114 do_filter = register.tag("filter", do_filter)
2115 
2116@@ -565,11 +511,10 @@
2117         {% firstof var1 var2 var3 "fallback value" %}
2118 
2119     """
2120-    bits = token.split_contents()[1:]
2121-    if len(bits) < 1:
2122-        raise TemplateSyntaxError("'firstof' statement requires at least one"
2123-                                  " argument")
2124-    return FirstOfNode(bits)
2125+    bits = parser.token_stream(token)
2126+    expressions = bits.parse_expression_list(minimum=1)
2127+    bits.assert_consumed()
2128+    return FirstOfNode(expressions)
2129 firstof = register.tag(firstof)
2130 
2131 #@register.tag(name="for")
2132@@ -612,42 +557,37 @@
2133         ==========================  ================================================
2134 
2135     """
2136-    bits = token.contents.split()
2137-    if len(bits) < 4:
2138-        raise TemplateSyntaxError("'for' statements should have at least four"
2139-                                  " words: %s" % token.contents)
2140-
2141-    is_reversed = bits[-1] == 'reversed'
2142-    in_index = is_reversed and -3 or -2
2143-    if bits[in_index] != 'in':
2144-        raise TemplateSyntaxError("'for' statements should use the format"
2145-                                  " 'for x in y': %s" % token.contents)
2146-
2147-    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
2148-    for var in loopvars:
2149-        if not var or ' ' in var:
2150-            raise TemplateSyntaxError("'for' tag received an invalid argument:"
2151-                                      " %s" % token.contents)
2152-
2153-    sequence = parser.compile_filter(bits[in_index+1])
2154-    nodelist_loop = parser.parse(('endfor',))
2155-    parser.delete_first_token()
2156-    return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
2157+    bits = parser.token_stream(token)
2158+    loopvars = []
2159+    while True:
2160+        var = bits.pop_name()
2161+        if var:
2162+            loopvars.append(var)
2163+            if not bits.pop_lexem(','):
2164+                break
2165+        else:
2166+            break
2167+    if not loopvars:
2168+        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
2169+       
2170+    if not bits.pop_lexem('in'):
2171+        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
2172+    try:   
2173+        sequence = bits.parse_expression()
2174+    except TokenSyntaxError:
2175+        raise bits.expected("expression")
2176+    reversed = bits.pop_lexem('reversed')
2177+    bits.assert_consumed()
2178+    nodelist = parser.parse_nodelist(('endfor',))
2179+    return ForNode(loopvars, sequence, reversed, nodelist)
2180 do_for = register.tag("for", do_for)
2181 
2182 def do_ifequal(parser, token, negate):
2183-    bits = list(token.split_contents())
2184-    if len(bits) != 3:
2185-        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
2186-    end_tag = 'end' + bits[0]
2187-    nodelist_true = parser.parse(('else', end_tag))
2188-    token = parser.next_token()
2189-    if token.contents == 'else':
2190-        nodelist_false = parser.parse((end_tag,))
2191-        parser.delete_first_token()
2192-    else:
2193-        nodelist_false = NodeList()
2194-    return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
2195+    bits = parser.token_stream(token)
2196+    val1, val2 = bits.parse_expression_list(count=2)
2197+    bits.assert_consumed()
2198+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
2199+    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
2200 
2201 #@register.tag
2202 def ifequal(parser, token):
2203@@ -737,39 +677,36 @@
2204             {% endif %}
2205         {% endif %}
2206     """
2207-    bits = token.contents.split()
2208-    del bits[0]
2209-    if not bits:
2210-        raise TemplateSyntaxError("'if' statement requires at least one argument")
2211-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
2212-    bitstr = ' '.join(bits)
2213-    boolpairs = bitstr.split(' and ')
2214+    bits = parser.token_stream(token)
2215+    link_type = None
2216+    link = None
2217     boolvars = []
2218-    if len(boolpairs) == 1:
2219-        link_type = IfNode.LinkTypes.or_
2220-        boolpairs = bitstr.split(' or ')
2221-    else:
2222-        link_type = IfNode.LinkTypes.and_
2223-        if ' or ' in bitstr:
2224-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
2225-    for boolpair in boolpairs:
2226-        if ' ' in boolpair:
2227-            try:
2228-                not_, boolvar = boolpair.split()
2229-            except ValueError:
2230-                raise TemplateSyntaxError, "'if' statement improperly formatted"
2231-            if not_ != 'not':
2232-                raise TemplateSyntaxError, "Expected 'not' in if statement"
2233-            boolvars.append((True, parser.compile_filter(boolvar)))
2234+    while True:
2235+        negated = False
2236+        if bits.pop_lexem('not'):
2237+            negated = True
2238+        try:
2239+            expr = bits.parse_expression()
2240+        except TokenSyntaxError:
2241+            if link:
2242+                raise TemplateSyntaxError("'if' statement improperly formatted")
2243+            else:
2244+                raise TemplateSyntaxError("'if' statement requires at least one argument")
2245+        boolvars.append((negated, expr))
2246+        link = bits.pop_name()
2247+        if not link:
2248+            break
2249+        if link_type:
2250+            if link_type != link:
2251+                raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'")
2252         else:
2253-            boolvars.append((False, parser.compile_filter(boolpair)))
2254-    nodelist_true = parser.parse(('else', 'endif'))
2255-    token = parser.next_token()
2256-    if token.contents == 'else':
2257-        nodelist_false = parser.parse(('endif',))
2258-        parser.delete_first_token()
2259-    else:
2260-        nodelist_false = NodeList()
2261+            if not link in ('and', 'or'):
2262+                raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link)       
2263+            link_type = link
2264+    bits.assert_consumed()
2265+   
2266+    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')
2267+   
2268     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
2269 do_if = register.tag("if", do_if)
2270 
2271@@ -802,10 +739,11 @@
2272                 {% endifchanged %}
2273             {% endfor %}
2274     """
2275-    bits = token.contents.split()
2276-    nodelist = parser.parse(('endifchanged',))
2277-    parser.delete_first_token()
2278-    return IfChangedNode(nodelist, *bits[1:])
2279+    bits = parser.token_stream(token)
2280+    nodelist = parser.parse_nodelist(('endifchanged',))
2281+    values = bits.parse_expression_list()
2282+    bits.assert_consumed()
2283+    return IfChangedNode(nodelist, *values)
2284 ifchanged = register.tag(ifchanged)
2285 
2286 #@register.tag
2287@@ -872,10 +810,12 @@
2288 
2289         It is {% now "jS F Y H:i" %}
2290     """
2291-    bits = token.contents.split('"')
2292-    if len(bits) != 3:
2293-        raise TemplateSyntaxError, "'now' statement takes one argument"
2294-    format_string = bits[1]
2295+    bits = parser.token_stream(token)
2296+    try:
2297+        format_string = bits.parse_expression()
2298+    except TokenSyntaxError:
2299+        bits.expected("expression")
2300+    bits.assert_consumed()
2301     return NowNode(format_string)
2302 now = register.tag(now)
2303 
2304@@ -926,20 +866,23 @@
2305         {% regroup people|dictsort:"gender" by gender as grouped %}
2306 
2307     """
2308-    firstbits = token.contents.split(None, 3)
2309-    if len(firstbits) != 4:
2310-        raise TemplateSyntaxError, "'regroup' tag takes five arguments"
2311-    target = parser.compile_filter(firstbits[1])
2312-    if firstbits[2] != 'by':
2313-        raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
2314-    lastbits_reversed = firstbits[3][::-1].split(None, 2)
2315-    if lastbits_reversed[1][::-1] != 'as':
2316-        raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
2317-                                  " be 'as'")
2318-
2319-    expression = parser.compile_filter(lastbits_reversed[2][::-1])
2320-
2321-    var_name = lastbits_reversed[0][::-1]
2322+    bits = parser.token_stream(token)
2323+    try:
2324+        target = bits.parse_expression()
2325+    except TokenSyntaxError:
2326+        bits.expected("expression")
2327+   
2328+    if not bits.pop_lexem('by'):
2329+        raise bits.expected("'by'")
2330+    try:
2331+        expression = bits.parse_expression()
2332+    except TokenSyntaxError:
2333+        raise TemplateSyntaxError()
2334+    try:
2335+        var_name = parse_as(bits)
2336+    except TokenSyntaxError:
2337+        raise bits.expected("as <name>")
2338+    bits.assert_consumed()
2339     return RegroupNode(target, expression, var_name)
2340 regroup = register.tag(regroup)
2341 
2342@@ -968,9 +911,7 @@
2343             </strong>
2344         {% endspaceless %}
2345     """
2346-    nodelist = parser.parse(('endspaceless',))
2347-    parser.delete_first_token()
2348-    return SpacelessNode(nodelist)
2349+    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
2350 spaceless = register.tag(spaceless)
2351 
2352 #@register.tag
2353@@ -1038,21 +979,14 @@
2354 
2355     The URL will look like ``/clients/client/123/``.
2356     """
2357-    bits = token.contents.split(' ', 2)
2358-    if len(bits) < 2:
2359-        raise TemplateSyntaxError("'%s' takes at least one argument"
2360-                                  " (path to a view)" % bits[0])
2361-    args = []
2362-    kwargs = {}
2363-    if len(bits) > 2:
2364-        for arg in bits[2].split(','):
2365-            if '=' in arg:
2366-                k, v = arg.split('=', 1)
2367-                k = k.strip()
2368-                kwargs[k] = parser.compile_filter(v)
2369-            else:
2370-                args.append(parser.compile_filter(arg))
2371-    return URLNode(bits[1], args, kwargs)
2372+    bits = parser.token_stream(token)
2373+    try:
2374+        view = bits.parse_string(bare=True)
2375+    except TokenSyntaxError:
2376+        raise bits.expected("viewname")
2377+    args, kwargs = parse_args_and_kwargs(bits)
2378+    bits.assert_consumed()
2379+    return URLNode(view, args, kwargs)
2380 url = register.tag(url)
2381 
2382 #@register.tag
2383@@ -1084,7 +1018,7 @@
2384 #@register.tag
2385 def do_with(parser, token):
2386     """
2387-    Adds a value to the context (inside of this block) for caching and easy
2388+    Adds values to the context (inside of this block) for caching and easy
2389     access.
2390 
2391     For example::
2392@@ -1092,14 +1026,15 @@
2393         {% with person.some_sql_method as total %}
2394             {{ total }} object{{ total|pluralize }}
2395         {% endwith %}
2396+       
2397+        {% with person.some_sql_method as total, person.get_full_name as full_name %}
2398+            {{ full_name }}: {{ total }} object{{ total|pluralize }}
2399+        {% endwith %}
2400+       
2401     """
2402-    bits = list(token.split_contents())
2403-    if len(bits) != 4 or bits[2] != "as":
2404-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
2405-                                  bits[0])
2406-    var = parser.compile_filter(bits[1])
2407-    name = bits[3]
2408-    nodelist = parser.parse(('endwith',))
2409-    parser.delete_first_token()
2410-    return WithNode(var, name, nodelist)
2411+    bits = parser.token_stream(token)
2412+    context_map = parse_context_map(bits)
2413+    bits.assert_consumed()
2414+    nodelist = parser.parse_nodelist(('endwith',))
2415+    return WithNode(context_map, nodelist)
2416 do_with = register.tag('with', do_with)
2417Index: django/template/compat.py
2418===================================================================
2419--- django/template/compat.py   (revision 0)
2420+++ django/template/compat.py   (revision 0)
2421@@ -0,0 +1,123 @@
2422+import warnings
2423+from django.template.expressions import Expression, LookupError
2424+from django.template.compiler import TemplateSyntaxError
2425+from django.template.nodes import ExpressionNode
2426+
2427+VariableDoesNotExist = LookupError
2428+VariableNode = ExpressionNode
2429+
2430+class Variable(Expression):
2431+    def __init__(self, var):
2432+        warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2)   
2433+        self.var = var
2434+        from django.template.compiler import TokenStream
2435+        stream = TokenStream(None, var)
2436+        self.expression = stream.parse_value()
2437+        stream.assert_consumed("Invalid variable: %s" % var)
2438+
2439+    def resolve(self, context):
2440+        return self.expression.resolve(context)
2441+
2442+    def __repr__(self):
2443+        return "<%s: %r>" % (self.__class__.__name__, self.var)
2444+
2445+    def __str__(self):
2446+        return self.var
2447+       
2448+
2449+def resolve_variable(path, context):
2450+    """
2451+    Returns the resolved variable, which may contain attribute syntax, within
2452+    the given context.
2453+
2454+    Deprecated.
2455+    """
2456+    warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2)
2457+    from django.template.compiler import TokenStream
2458+    stream = TokenStream(None, path)
2459+    val = stream.parse_value()
2460+    stream.assert_consumed("Invalid variable: %s" % path)   
2461+    return val.resolve(context)
2462+
2463+
2464+class TokenParser(object):
2465+    """
2466+    Subclass this and implement the top() method to parse a template line. When
2467+    instantiating the parser, pass in the line from the Django template parser.
2468+
2469+    The parser's "tagname" instance-variable stores the name of the tag that
2470+    the filter was called with.
2471+    """
2472+    def __init__(self, subject):
2473+        self.subject = subject
2474+        self.pointer = 0
2475+        self.backout = []
2476+        self.tagname = self.tag()
2477+
2478+    def top(self):
2479+        "Overload this method to do the actual parsing and return the result."
2480+        raise NotImplementedError()
2481+
2482+    def more(self):
2483+        "Returns True if there is more stuff in the tag."
2484+        return self.pointer < len(self.subject)
2485+
2486+    def back(self):
2487+        "Undoes the last microparser. Use this for lookahead and backtracking."
2488+        if not len(self.backout):
2489+            raise TemplateSyntaxError("back called without some previous parsing")
2490+        self.pointer = self.backout.pop()
2491+
2492+    def tag(self):
2493+        "A microparser that just returns the next tag from the line."
2494+        subject = self.subject
2495+        i = self.pointer
2496+        if i >= len(subject):
2497+            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
2498+        p = i
2499+        while i < len(subject) and subject[i] not in (' ', '\t'):
2500+            i += 1
2501+        s = subject[p:i]
2502+        while i < len(subject) and subject[i] in (' ', '\t'):
2503+            i += 1
2504+        self.backout.append(self.pointer)
2505+        self.pointer = i
2506+        return s
2507+
2508+    def value(self):
2509+        "A microparser that parses for a value: some string constant or variable name."
2510+        subject = self.subject
2511+        i = self.pointer
2512+        if i >= len(subject):
2513+            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
2514+        if subject[i] in ('"', "'"):
2515+            p = i
2516+            i += 1
2517+            while i < len(subject) and subject[i] != subject[p]:
2518+                i += 1
2519+            if i >= len(subject):
2520+                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
2521+            i += 1
2522+            res = subject[p:i]
2523+            while i < len(subject) and subject[i] in (' ', '\t'):
2524+                i += 1
2525+            self.backout.append(self.pointer)
2526+            self.pointer = i
2527+            return res
2528+        else:
2529+            p = i
2530+            while i < len(subject) and subject[i] not in (' ', '\t'):
2531+                if subject[i] in ('"', "'"):
2532+                    c = subject[i]
2533+                    i += 1
2534+                    while i < len(subject) and subject[i] != c:
2535+                        i += 1
2536+                    if i >= len(subject):
2537+                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
2538+                i += 1
2539+            s = subject[p:i]
2540+            while i < len(subject) and subject[i] in (' ', '\t'):
2541+                i += 1
2542+            self.backout.append(self.pointer)
2543+            self.pointer = i
2544+            return s
2545Index: django/template/context.py
2546===================================================================
2547--- django/template/context.py  (revision 7884)
2548+++ django/template/context.py  (working copy)
2549@@ -9,10 +9,12 @@
2550 
2551 class Context(object):
2552     "A stack container for variable context"
2553-    def __init__(self, dict_=None, autoescape=True):
2554+    def __init__(self, dict_=None, autoescape=True, loader=None):
2555         dict_ = dict_ or {}
2556         self.dicts = [dict_]
2557         self.autoescape = autoescape
2558+        self.template_cache = {}
2559+        self.loader = loader
2560 
2561     def __repr__(self):
2562         return repr(self.dicts)
2563@@ -65,6 +67,25 @@
2564         self.dicts = [other_dict] + self.dicts
2565         return other_dict
2566 
2567+    def get_template(self, template_name):
2568+        if not template_name in self.template_cache:
2569+            if self.loader is None:
2570+                from django.template import loader
2571+                tpl = loader.get_template(template_name)
2572+            else:
2573+                tpl = self.loader.get_template(template_name)
2574+            self.template_cache[template_name] = tpl
2575+        return self.template_cache[template_name]
2576+
2577+    def select_template(self, template_name_list):
2578+        from django.template import TemplateDoesNotExist
2579+        for template_name in template_name_list:
2580+            try:
2581+                return self.get_template(template_name)
2582+            except TemplateDoesNotExist:
2583+                continue
2584+        raise TemplateDoesNotExist, ', '.join(template_name_list)
2585+
2586 # This is a function rather than module-level procedural code because we only
2587 # want it to execute if somebody uses RequestContext.
2588 def get_standard_processors():
2589@@ -93,8 +114,8 @@
2590     Additional processors can be specified as a list of callables
2591     using the "processors" keyword argument.
2592     """
2593-    def __init__(self, request, dict=None, processors=None):
2594-        Context.__init__(self, dict)
2595+    def __init__(self, request, dict=None, processors=None, loader=None):
2596+        Context.__init__(self, dict, loader=loader)
2597         if processors is None:
2598             processors = ()
2599         else:
2600Index: django/template/loader_tags.py
2601===================================================================
2602--- django/template/loader_tags.py      (revision 7884)
2603+++ django/template/loader_tags.py      (working copy)
2604@@ -1,6 +1,6 @@
2605-from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
2606-from django.template import Library, Node, TextNode
2607-from django.template.loader import get_template, get_template_from_string, find_template_source
2608+from django.template import TemplateSyntaxError, Variable, Library, Node, TextNode
2609+from django.template.utils import render_with_context_map, parse_context_map
2610+from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
2611 from django.conf import settings
2612 from django.utils.safestring import mark_safe
2613 
2614@@ -39,33 +39,24 @@
2615 class ExtendsNode(Node):
2616     must_be_first = True
2617 
2618-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
2619+    def __init__(self, nodelist, parent_name):
2620         self.nodelist = nodelist
2621-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
2622-        self.template_dirs = template_dirs
2623+        self.parent_name = parent_name
2624 
2625     def __repr__(self):
2626-        if self.parent_name_expr:
2627-            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
2628         return '<ExtendsNode: extends "%s">' % self.parent_name
2629 
2630     def get_parent(self, context):
2631-        if self.parent_name_expr:
2632-            self.parent_name = self.parent_name_expr.resolve(context)
2633-        parent = self.parent_name
2634+        parent = self.parent_name.resolve_safe(context)
2635         if not parent:
2636             error_msg = "Invalid template name in 'extends' tag: %r." % parent
2637-            if self.parent_name_expr:
2638-                error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
2639             raise TemplateSyntaxError, error_msg
2640         if hasattr(parent, 'render'):
2641             return parent # parent is a Template object
2642         try:
2643-            source, origin = find_template_source(parent, self.template_dirs)
2644+            return context.get_template(parent)
2645         except TemplateDoesNotExist:
2646             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
2647-        else:
2648-            return get_template_from_string(source, origin, parent)
2649 
2650     def render(self, context):
2651         compiled_parent = self.get_parent(context)
2652@@ -96,31 +87,16 @@
2653                 parent_block.nodelist = block_node.nodelist
2654         return compiled_parent.render(context)
2655 
2656-class ConstantIncludeNode(Node):
2657-    def __init__(self, template_path):
2658-        try:
2659-            t = get_template(template_path)
2660-            self.template = t
2661-        except:
2662-            if settings.TEMPLATE_DEBUG:
2663-                raise
2664-            self.template = None
2665-
2666-    def render(self, context):
2667-        if self.template:
2668-            return self.template.render(context)
2669-        else:
2670-            return ''
2671-
2672 class IncludeNode(Node):
2673-    def __init__(self, template_name):
2674-        self.template_name = Variable(template_name)
2675+    def __init__(self, template_name, context_map=None):
2676+        self.template_name = template_name
2677+        self.context_map = context_map
2678 
2679     def render(self, context):
2680         try:
2681-            template_name = self.template_name.resolve(context)
2682-            t = get_template(template_name)
2683-            return t.render(context)
2684+            template_name = self.template_name.resolve_safe(context)
2685+            tpl = context.get_template(template_name)           
2686+            return render_with_context_map(tpl, self.context_map, context)
2687         except TemplateSyntaxError, e:
2688             if settings.TEMPLATE_DEBUG:
2689                 raise
2690@@ -161,31 +137,30 @@
2691     bits = token.contents.split()
2692     if len(bits) != 2:
2693         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
2694-    parent_name, parent_name_expr = None, None
2695-    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
2696-        parent_name = bits[1][1:-1]
2697-    else:
2698-        parent_name_expr = parser.compile_filter(bits[1])
2699+    parent_name = parser.compile_filter(bits[1])
2700     nodelist = parser.parse()
2701     if nodelist.get_nodes_by_type(ExtendsNode):
2702         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
2703-    return ExtendsNode(nodelist, parent_name, parent_name_expr)
2704+    return ExtendsNode(nodelist, parent_name)
2705 
2706 def do_include(parser, token):
2707     """
2708     Loads a template and renders it with the current context.
2709+    Optionally takes a "with value as name (, value as name)*" clause.
2710 
2711     Example::
2712 
2713         {% include "foo/some_include" %}
2714+        {% include "foo" with value as name %}
2715+        {% include "foo" with value as name, bar as baz %}
2716     """
2717-    bits = token.contents.split()
2718-    if len(bits) != 2:
2719-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
2720-    path = bits[1]
2721-    if path[0] in ('"', "'") and path[-1] == path[0]:
2722-        return ConstantIncludeNode(path[1:-1])
2723-    return IncludeNode(bits[1])
2724+    bits = parser.token_stream(token)
2725+    template_name = bits.parse_expression()
2726+    context_map = None
2727+    if bits.pop_lexem('with'):
2728+        context_map = parse_context_map(bits)
2729+    bits.assert_consumed()
2730+    return IncludeNode(template_name, context_map)   
2731 
2732 register.tag('block', do_block)
2733 register.tag('extends', do_extends)
2734Index: django/template/library.py
2735===================================================================
2736--- django/template/library.py  (revision 0)
2737+++ django/template/library.py  (revision 0)
2738@@ -0,0 +1,193 @@
2739+import re
2740+from inspect import getargspec
2741+from django.conf import settings
2742+from django.template.context import Context
2743+from django.template.nodes import Node
2744+from django.template.compiler import TemplateSyntaxError
2745+from django.utils.itercompat import is_iterable
2746+from django.utils.functional import curry
2747+
2748+__all__ = ('InvalidTemplateLibrary', 'Library', 'get_library', 'add_to_builtins')
2749+
2750+# global dictionary of libraries that have been loaded using get_library
2751+libraries = {}
2752+# global list of libraries to load by default for a new parser
2753+builtins = []
2754+
2755+class InvalidTemplateLibrary(Exception):
2756+    pass
2757+
2758+def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False):
2759+    "Returns a template.Node subclass."
2760+    bits = token.split_contents()[1:]
2761+    bmax = len(params)
2762+    def_len = defaults and len(defaults) or 0
2763+    bmin = bmax - def_len
2764+    stream = TokenStream(token.contents)
2765+    args = stream.parse_expression_list()
2766+    stream.assert_comsumed()
2767+    if(len(bits) < bmin or len(bits) > bmax):
2768+        if bmin == bmax:
2769+            message = "%s takes %s arguments" % (name, bmin)
2770+        else:
2771+            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
2772+        raise TemplateSyntaxError(message)
2773+    if takes_context:
2774+        node_class = curry(node_class, takes_context=takes_context)
2775+    if takes_nodelist:
2776+        nodelist = parser.parse(('end%s' % name,))
2777+        parser.delete_first_token()
2778+        node_class = curry(node_class, nodelist=nodelist)     
2779+    return node_class(args)
2780+
2781+class Library(object):
2782+    def __init__(self):
2783+        self.filters = {}
2784+        self.tags = {}
2785+
2786+    def tag(self, name=None, compile_function=None):
2787+        if name == None and compile_function == None:
2788+            # @register.tag()
2789+            return self.tag_function
2790+        elif name != None and compile_function == None:
2791+            if(callable(name)):
2792+                # @register.tag
2793+                return self.tag_function(name)
2794+            else:
2795+                # @register.tag('somename') or @register.tag(name='somename')
2796+                def dec(func):
2797+                    return self.tag(name, func)
2798+                return dec
2799+        elif name != None and compile_function != None:
2800+            # register.tag('somename', somefunc)
2801+            self.tags[name] = compile_function
2802+            return compile_function
2803+        else:
2804+            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
2805+
2806+    def tag_function(self,func):
2807+        self.tags[getattr(func, "_decorated_function", func).__name__] = func
2808+        return func
2809+
2810+    def filter(self, name=None, filter_func=None):
2811+        if name == None and filter_func == None:
2812+            # @register.filter()
2813+            return self.filter_function
2814+        elif filter_func == None:
2815+            if(callable(name)):
2816+                # @register.filter
2817+                return self.filter_function(name)
2818+            else:
2819+                # @register.filter('somename') or @register.filter(name='somename')
2820+                def dec(func):
2821+                    return self.filter(name, func)
2822+                return dec
2823+        elif name != None and filter_func != None:
2824+            # register.filter('somename', somefunc)
2825+            self.filters[name] = filter_func
2826+            return filter_func
2827+        else:
2828+            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
2829+
2830+    def filter_function(self, func):
2831+        self.filters[getattr(func, "_decorated_function", func).__name__] = func
2832+        return func
2833+
2834+    def simple_tag(self, compile_function=None, takes_context=None, takes_nodelist=None):
2835+        def dec(func):
2836+            params, xx, xxx, defaults = getargspec(func)
2837+            if takes_context and takes_nodelist:
2838+                if params[0] == 'context' and params[1] == 'nodelist':
2839+                    params = params[2:]
2840+                else:
2841+                    raise TemplateSyntaxError("Any tag function decorated both with takes_context=True and with takes_nodelist=True must have a first argument of 'context', and a second argument of 'nodelist'")
2842+            elif takes_nodelist:
2843+                if params[0] == 'nodelist':
2844+                    params = params[1:]
2845+                else:
2846+                    raise TemplateSyntaxError("Any tag function decorated with takes_nodelist=True must have a first argument of 'nodelist'")
2847+            elif takes_context:
2848+                if params[0] == 'context':
2849+                    params = params[1:]
2850+                else:
2851+                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
2852+
2853+            class SimpleNode(Node):
2854+                def __init__(self, args, takes_context=False, nodelist=None):
2855+                    self.args = args
2856+                    self.takes_context = takes_context
2857+                    if nodelist is not None:
2858+                        # Only save the 'nodelist' attribute if it's not None, so that it is picked by the Node.get_nodes_by_type() method.
2859+                        self.nodelist = nodelist
2860+
2861+                def render(self, context):
2862+                    func_args = []
2863+                    if self.takes_context:
2864+                        func_args.append(context)
2865+                    if hasattr(self, 'nodelist'):
2866+                        func_args.append(self.nodelist)
2867+                    func_args += [arg.resolve_safe(context) for arg in self.args]
2868+                    return func(*func_args)
2869+
2870+            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode, takes_nodelist=takes_nodelist, takes_context=takes_context)
2871+            compile_func.__doc__ = func.__doc__
2872+            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
2873+            return func
2874+       
2875+        if callable(compile_function):
2876+            # @register.simple_tag
2877+            return dec(compile_function)
2878+        return dec
2879+
2880+    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
2881+        def dec(func):
2882+            params, xx, xxx, defaults = getargspec(func)
2883+            if takes_context:
2884+                if params[0] == 'context':
2885+                    params = params[1:]
2886+                else:
2887+                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
2888+
2889+            class InclusionNode(Node):
2890+                def __init__(self, args):
2891+                    self.args = args
2892+
2893+                def render(self, context):
2894+                    resolved_vars = [arg.resolve_safe(context) for arg in self.args]
2895+                    if takes_context:
2896+                        args = [context] + resolved_vars
2897+                    else:
2898+                        args = resolved_vars
2899+
2900+                    dict = func(*args)
2901+
2902+                    if not getattr(self, 'nodelist', False):
2903+                        if not isinstance(file_name, basestring) and is_iterable(file_name):
2904+                            t = context.select_template(file_name)
2905+                        else:
2906+                            t = context.get_template(file_name)
2907+                        self.nodelist = t.nodelist
2908+                    return self.nodelist.render(context_class(dict, autoescape=context.autoescape))
2909+
2910+            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
2911+            compile_func.__doc__ = func.__doc__
2912+            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
2913+            return func
2914+        return dec
2915+
2916+def get_library(module_name):
2917+    lib = libraries.get(module_name, None)
2918+    if not lib:
2919+        try:
2920+            mod = __import__(module_name, {}, {}, [''])
2921+        except ImportError, e:
2922+            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
2923+        try:
2924+            lib = mod.register
2925+            libraries[module_name] = lib
2926+        except AttributeError:
2927+            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
2928+    return lib
2929+
2930+def add_to_builtins(module_name):
2931+    builtins.append(get_library(module_name))
2932Index: django/template/debug.py
2933===================================================================
2934--- django/template/debug.py    (revision 7884)
2935+++ django/template/debug.py    (working copy)
2936@@ -1,4 +1,5 @@
2937-from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
2938+from django.template.compiler import Lexer, Parser, tag_re
2939+from django.template import NodeList, ExpressionNode, TemplateSyntaxError
2940 from django.utils.encoding import force_unicode
2941 from django.utils.html import escape
2942 from django.utils.safestring import SafeData, EscapeData
2943@@ -50,7 +51,7 @@
2944         return DebugNodeList()
2945 
2946     def create_variable_node(self, contents):
2947-        return DebugVariableNode(contents)
2948+        return DebugExpressionNode(contents)
2949 
2950     def extend_nodelist(self, nodelist, node, token):
2951         node.source = token.source
2952@@ -81,17 +82,11 @@
2953             raise wrapped
2954         return result
2955 
2956-class DebugVariableNode(VariableNode):
2957+class DebugExpressionNode(ExpressionNode):
2958     def render(self, context):
2959         try:
2960-            output = force_unicode(self.filter_expression.resolve(context))
2961+            return super(DebugExpressionNode, self).render(context)
2962         except TemplateSyntaxError, e:
2963             if not hasattr(e, 'source'):
2964                 e.source = self.source
2965             raise
2966-        except UnicodeDecodeError:
2967-            return ''
2968-        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
2969-            return escape(output)
2970-        else:
2971-            return output
2972Index: django/template/loader.py
2973===================================================================
2974--- django/template/loader.py   (revision 7884)
2975+++ django/template/loader.py   (working copy)
2976@@ -21,11 +21,14 @@
2977 # installed, because pkg_resources is necessary to read eggs.
2978 
2979 from django.core.exceptions import ImproperlyConfigured
2980-from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
2981+from django.template import Origin, Template, Context, add_to_builtins
2982 from django.conf import settings
2983 
2984 template_source_loaders = None
2985 
2986+class TemplateDoesNotExist(Exception):
2987+    pass
2988+
2989 class LoaderOrigin(Origin):
2990     def __init__(self, display_name, loader, name, dirs):
2991         super(LoaderOrigin, self).__init__(display_name)
2992Index: django/templatetags/i18n.py
2993===================================================================
2994--- django/templatetags/i18n.py (revision 7884)
2995+++ django/templatetags/i18n.py (working copy)
2996@@ -1,8 +1,7 @@
2997 import re
2998 
2999-from django.template import Node, Variable, VariableNode
3000-from django.template import TemplateSyntaxError, TokenParser, Library
3001-from django.template import TOKEN_TEXT, TOKEN_VAR
3002+from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library
3003+from django.template.compiler import TOKEN_TEXT, TOKEN_VAR
3004 from django.utils import translation
3005 from django.utils.encoding import force_unicode
3006 
3007@@ -35,7 +34,7 @@
3008 
3009 class TranslateNode(Node):
3010     def __init__(self, value, noop):
3011-        self.value = Variable(value)
3012+        self.value = value
3013         self.noop = noop
3014 
3015     def render(self, context):
3016@@ -171,18 +170,13 @@
3017     the variable ``variable``. Make sure that the string
3018     in there is something that is in the .po file.
3019     """
3020-    class TranslateParser(TokenParser):
3021-        def top(self):
3022-            value = self.value()
3023-            if self.more():
3024-                if self.tag() == 'noop':
3025-                    noop = True
3026-                else:
3027-                    raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
3028-            else:
3029-                noop = False
3030-            return (value, noop)
3031-    value, noop = TranslateParser(token.contents).top()
3032+    bits = parser.token_stream(token)
3033+    try:
3034+        value = bits.parse_expression()
3035+    except TokenSyntaxError:
3036+        bits.expected("expression")
3037+    noop = bits.pop_lexem('noop')
3038+    bits.assert_consumed()
3039     return TranslateNode(value, noop)
3040 
3041 def do_block_translate(parser, token):
3042Index: django/templatetags/cache.py
3043===================================================================
3044--- django/templatetags/cache.py        (revision 7884)
3045+++ django/templatetags/cache.py        (working copy)
3046@@ -1,5 +1,4 @@
3047-from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
3048-from django.template import resolve_variable
3049+from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError
3050 from django.core.cache import cache
3051 from django.utils.encoding import force_unicode
3052 
3053@@ -8,7 +7,7 @@
3054 class CacheNode(Node):
3055     def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
3056         self.nodelist = nodelist
3057-        self.expire_time_var = Variable(expire_time_var)
3058+        self.expire_time_var = expire_time_var
3059         self.fragment_name = fragment_name
3060         self.vary_on = vary_on
3061 
3062@@ -22,7 +21,7 @@
3063         except (ValueError, TypeError):
3064             raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
3065         # Build a unicode key for this fragment and all vary-on's.
3066-        cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on])
3067+        cache_key = u':'.join([self.fragment_name] + [force_unicode(var.resolve_safe(context)) for var in self.vary_on])
3068         value = cache.get(cache_key)
3069         if value is None:
3070             value = self.nodelist.render(context)
3071@@ -50,11 +49,17 @@
3072 
3073     Each unique set of arguments will result in a unique cache entry.
3074     """
3075-    nodelist = parser.parse(('endcache',))
3076-    parser.delete_first_token()
3077-    tokens = token.contents.split()
3078-    if len(tokens) < 3:
3079-        raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
3080-    return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
3081+    nodelist = parser.parse_nodelist(('endcache',))
3082+    bits = parser.token_stream(token)
3083+    try:
3084+        expire_time = bits.parse_expression()
3085+    except TokenSyntaxError:
3086+        bits.expected("expression")
3087+    name = bits.pop_name()
3088+    if not name:
3089+        raise TemplateSyntaxError, "'cache' requires a fragment name"   
3090+    vary_on = bits.parse_expression_list()
3091+    bits.assert_consumed()   
3092+    return CacheNode(nodelist, expire_time, name, vary_on)
3093 
3094 register.tag('cache', do_cache)