Ticket #7806: tplrf.2.diff

File tplrf.2.diff, 116.9 KB (added by Johannes Dollinger, 16 years ago)
  • django/template/nodes.py

     
     1from django.utils.encoding import force_unicode
     2from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     3from django.utils.html import escape
     4from django.template.expressions import LookupError
     5from django.conf import settings
     6
     7class Node(object):
     8    # Set this to True for nodes that must be first in the template (although
     9    # they can be preceded by text nodes.
     10    must_be_first = False
     11
     12    def render(self, context):
     13        "Return the node rendered as a string"
     14        pass
     15       
     16    def __repr__(self):
     17        return "<%s>" % self.__class__.__name__
     18
     19    def __iter__(self):
     20        yield self
     21
     22    def get_nodes_by_type(self, nodetype):
     23        "Return a list of all nodes (within this node and its nodelist) of the given type"
     24        nodes = []
     25        if isinstance(self, nodetype):
     26            nodes.append(self)
     27        if hasattr(self, 'nodelist'):
     28            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
     29        return nodes
     30
     31class NodeList(list):
     32    # Set to True the first time a non-TextNode is inserted by
     33    # extend_nodelist().
     34    contains_nontext = False
     35
     36    def render(self, context):
     37        bits = []
     38        for node in self:
     39            if isinstance(node, Node):
     40                bits.append(self.render_node(node, context))
     41            else:
     42                bits.append(node)
     43        return mark_safe(''.join([force_unicode(b) for b in bits]))
     44
     45    def get_nodes_by_type(self, nodetype):
     46        "Return a list of all nodes of the given type"
     47        nodes = []
     48        for node in self:
     49            nodes.extend(node.get_nodes_by_type(nodetype))
     50        return nodes
     51
     52    def render_node(self, node, context):
     53        return node.render(context)
     54
     55class TextNode(Node):
     56    def __init__(self, s):
     57        self.s = s
     58
     59    def __repr__(self):
     60        return "<Text Node: '%s'>" % self.s[:25]
     61
     62    def render(self, context):
     63        return self.s
     64
     65class ExpressionNode(Node):
     66    def __init__(self, expression):
     67        self.expression = expression
     68
     69    def __repr__(self):
     70        return "<Variable Node: %s>" % self.expression
     71
     72    def render(self, context):
     73        try:
     74            output = force_unicode(self.expression.resolve(context))
     75        except LookupError:
     76            if settings.TEMPLATE_STRING_IF_INVALID:
     77                from django.template import invalid_var_format_string
     78                if invalid_var_format_string is None:
     79                    invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
     80                if invalid_var_format_string:
     81                    return settings.TEMPLATE_STRING_IF_INVALID % self.expression
     82                return settings.TEMPLATE_STRING_IF_INVALID
     83            else:
     84                return ''
     85        except UnicodeDecodeError:
     86            # Unicode conversion can fail sometimes for reasons out of our
     87            # control (e.g. exception rendering). In that case, we fail quietly.
     88            return ''       
     89        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
     90            output = escape(output)
     91        return output
  • django/template/compiler.py

     
     1import re
     2from inspect import getargspec
     3from django.conf import settings
     4from django.template.context import Context, RequestContext, ContextPopException
     5from django.template.expressions import Expression, Literal, Lookup, FilterExpression, VARIABLE_ATTRIBUTE_SEPARATOR
     6from django.template.nodes import Node, NodeList, ExpressionNode, TextNode
     7from django.utils.encoding import smart_unicode, smart_str
     8from django.utils.functional import wraps
     9from django.utils.safestring import mark_safe
     10from django.utils.text import smart_split
     11from django.utils.translation import ugettext
     12
     13__all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream')
     14
     15TOKEN_TEXT = 0
     16TOKEN_VAR = 1
     17TOKEN_BLOCK = 2
     18TOKEN_COMMENT = 3
     19
     20# template syntax constants
     21FILTER_SEPARATOR = '|'
     22FILTER_ARGUMENT_SEPARATOR = ':'
     23BLOCK_TAG_START = '{%'
     24BLOCK_TAG_END = '%}'
     25VARIABLE_TAG_START = '{{'
     26VARIABLE_TAG_END = '}}'
     27COMMENT_TAG_START = '{#'
     28COMMENT_TAG_END = '#}'
     29SINGLE_BRACE_START = '{'
     30SINGLE_BRACE_END = '}'
     31
     32ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
     33
     34# what to report as the origin for templates that come from non-loader sources
     35# (e.g. strings)
     36UNKNOWN_SOURCE="&lt;unknown source&gt;"
     37
     38# match a variable or block tag and capture the entire tag, including start/end delimiters
     39tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
     40                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
     41                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
     42
     43class TemplateSyntaxError(Exception):
     44    def __str__(self):
     45        try:
     46            import cStringIO as StringIO
     47        except ImportError:
     48            import StringIO
     49        output = StringIO.StringIO()
     50        output.write(Exception.__str__(self))
     51        # Check if we wrapped an exception and print that too.
     52        if hasattr(self, 'exc_info'):
     53            import traceback
     54            output.write('\n\nOriginal ')
     55            e = self.exc_info
     56            traceback.print_exception(e[0], e[1], e[2], 500, output)
     57        return output.getvalue()
     58
     59class TemplateEncodingError(Exception):
     60    pass
     61
     62class Origin(object):
     63    def __init__(self, name):
     64        self.name = name
     65
     66    def reload(self):
     67        raise NotImplementedError
     68
     69    def __str__(self):
     70        return self.name
     71
     72class StringOrigin(Origin):
     73    def __init__(self, source):
     74        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
     75        self.source = source
     76
     77    def reload(self):
     78        return self.source
     79
     80class Template(object):
     81    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
     82        try:
     83            template_string = smart_unicode(template_string)
     84        except UnicodeDecodeError:
     85            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
     86        if settings.TEMPLATE_DEBUG and origin is None:
     87            origin = StringOrigin(template_string)
     88        self.nodelist = compile_string(template_string, origin)
     89        self.name = name
     90
     91    def __iter__(self):
     92        for node in self.nodelist:
     93            for subnode in node:
     94                yield subnode
     95
     96    def render(self, context):
     97        "Display stage -- can be called many times"
     98        return self.nodelist.render(context)
     99
     100def compile_string(template_string, origin):
     101    "Compiles template_string into NodeList ready for rendering"
     102    if settings.TEMPLATE_DEBUG:
     103        from debug import DebugLexer, DebugParser
     104        lexer_class, parser_class = DebugLexer, DebugParser
     105    else:
     106        lexer_class, parser_class = Lexer, Parser
     107    lexer = lexer_class(template_string, origin)
     108    parser = parser_class(lexer.tokenize())
     109    return parser.parse()
     110
     111class Token(object):
     112    def __init__(self, token_type, contents):
     113        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
     114        self.token_type, self.contents = token_type, contents
     115
     116    def __str__(self):
     117        return '<%s token: "%s...">' % \
     118            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
     119            self.contents[:20].replace('\n', ''))
     120
     121    def split_contents(self):
     122        return list(smart_split(self.contents))
     123
     124class Lexer(object):
     125    def __init__(self, template_string, origin):
     126        self.template_string = template_string
     127        self.origin = origin
     128
     129    def tokenize(self):
     130        "Return a list of tokens from a given template_string."
     131        in_tag = False
     132        result = []
     133        for bit in tag_re.split(self.template_string):
     134            if bit:
     135                result.append(self.create_token(bit, in_tag))
     136            in_tag = not in_tag
     137        return result
     138
     139    def create_token(self, token_string, in_tag):
     140        """
     141        Convert the given token string into a new Token object and return it.
     142        If in_tag is True, we are processing something that matched a tag,
     143        otherwise it should be treated as a literal string.
     144        """
     145        if in_tag:
     146            if token_string.startswith(VARIABLE_TAG_START):
     147                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
     148            elif token_string.startswith(BLOCK_TAG_START):
     149                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
     150            elif token_string.startswith(COMMENT_TAG_START):
     151                token = Token(TOKEN_COMMENT, '')
     152        else:
     153            token = Token(TOKEN_TEXT, token_string)
     154        return token
     155
     156class Parser(object):
     157    def __init__(self, tokens):
     158        self.tokens = tokens
     159        self.tags = {}
     160        self.filters = {}
     161        from django.template.library import builtins
     162        for lib in builtins:
     163            self.add_library(lib)
     164
     165    def parse(self, parse_until=None):
     166        if parse_until is None: parse_until = []
     167        nodelist = self.create_nodelist()
     168        while self.tokens:
     169            token = self.next_token()
     170            if token.token_type == TOKEN_TEXT:
     171                self.extend_nodelist(nodelist, TextNode(token.contents), token)
     172            elif token.token_type == TOKEN_VAR:
     173                if not token.contents:
     174                    self.empty_variable(token)
     175                filter_expression = self.compile_filter(token.contents)
     176                var_node = self.create_variable_node(filter_expression)
     177                self.extend_nodelist(nodelist, var_node,token)
     178            elif token.token_type == TOKEN_BLOCK:
     179                if token.contents in parse_until:
     180                    # put token back on token list so calling code knows why it terminated
     181                    self.prepend_token(token)
     182                    return nodelist
     183                try:
     184                    command = token.contents.split()[0]
     185                except IndexError:
     186                    self.empty_block_tag(token)
     187                # execute callback function for this tag and append resulting node
     188                self.enter_command(command, token)
     189                try:
     190                    compile_func = self.tags[command]
     191                except KeyError:
     192                    self.invalid_block_tag(token, command)
     193                try:
     194                    compiled_result = compile_func(self, token)
     195                except TemplateSyntaxError, e:
     196                    if not self.compile_function_error(token, e):
     197                        raise
     198                self.extend_nodelist(nodelist, compiled_result, token)
     199                self.exit_command()
     200        if parse_until:
     201            self.unclosed_block_tag(parse_until)
     202        return nodelist
     203
     204    def parse_nodelist(self, parse_until=None):
     205        nodelist = self.parse(parse_until=parse_until)
     206        self.delete_first_token()
     207        return nodelist
     208
     209    def skip_past(self, endtag):
     210        while self.tokens:
     211            token = self.next_token()
     212            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
     213                return
     214        self.unclosed_block_tag([endtag])
     215
     216    def create_variable_node(self, expression):   
     217        return ExpressionNode(expression)
     218
     219    def create_nodelist(self):
     220        return NodeList()
     221
     222    def extend_nodelist(self, nodelist, node, token):
     223        if node.must_be_first and nodelist:
     224            try:
     225                if nodelist.contains_nontext:
     226                    raise AttributeError
     227            except AttributeError:
     228                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
     229        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
     230            nodelist.contains_nontext = True
     231        nodelist.append(node)
     232
     233    def enter_command(self, command, token):
     234        pass
     235
     236    def exit_command(self):
     237        pass
     238
     239    def error(self, token, msg):
     240        return TemplateSyntaxError(msg)
     241
     242    def empty_variable(self, token):
     243        raise self.error(token, "Empty variable tag")
     244
     245    def empty_block_tag(self, token):
     246        raise self.error(token, "Empty block tag")
     247
     248    def invalid_block_tag(self, token, command):
     249        raise self.error(token, "Invalid block tag: '%s'" % command)
     250
     251    def unclosed_block_tag(self, parse_until):
     252        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
     253
     254    def compile_function_error(self, token, e):
     255        pass
     256
     257    def next_token(self):
     258        return self.tokens.pop(0)
     259
     260    def prepend_token(self, token):
     261        self.tokens.insert(0, token)
     262
     263    def delete_first_token(self):
     264        del self.tokens[0]
     265
     266    def add_library(self, lib):
     267        self.tags.update(lib.tags)
     268        self.filters.update(lib.filters)
     269
     270    def token_stream(self, token):
     271        return TokenStream(self, token)
     272
     273    def compile_filter(self, token):
     274        stream = TokenStream(self, token)
     275        expr = stream.parse_expression(required=True)
     276        stream.assert_consumed("invalid filter expression")
     277        return expr
     278
     279    def find_filter(self, filter_name):
     280        if filter_name in self.filters:
     281            return self.filters[filter_name]
     282        else:
     283            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
     284
     285def filter_args_check(name, func, provided):
     286    provided = list(provided)
     287    plen = len(provided)
     288    # Check to see if a decorator is providing the real function.
     289    func = getattr(func, '_decorated_function', func)
     290    args, varargs, varkw, defaults = getargspec(func)
     291   
     292    if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)):
     293        return True
     294   
     295    # First argument is filter input.
     296    args.pop(0)
     297    if defaults:
     298        nondefs = args[:-len(defaults)]
     299    else:
     300        nondefs = args
     301    # Args without defaults must be provided.
     302    try:
     303        for arg in nondefs:
     304            provided.pop(0)
     305    except IndexError:
     306        # Not enough
     307        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     308
     309    # Defaults can be overridden.
     310    defaults = defaults and list(defaults) or []
     311    try:
     312        for parg in provided:
     313            defaults.pop(0)
     314    except IndexError:
     315        # Too many.
     316        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     317
     318    return True
     319
     320
     321bit_re = re.compile(r"""
     322    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
     323    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
     324    |(?P<name>[\w.]+) # keyword or variable
     325    |(?P<char>\S) # punctuation
     326""", re.VERBOSE)
     327
     328class TokenSyntaxError(Exception):
     329    pass
     330
     331def token_stream_parser(func):
     332    def wrapper(self, required=False, *args, **kwargs):
     333        mark = self.offset
     334        try:
     335            return func(self, *args, **kwargs)
     336        except TokenSyntaxError:
     337            if required:
     338                #FIXME: hack
     339                self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:]))
     340            self.offset = mark
     341            raise
     342    return wraps(func)(wrapper)
     343   
     344class TokenStream(object):
     345    exception = TokenSyntaxError()
     346    def __init__(self, parser, source):
     347        self.parser = parser
     348        self.source = source
     349        self.offset = 0
     350        self.name = None
     351        if isinstance(source, Token):
     352            bits = source.contents.split(None, 1)               
     353            self.source = len(bits) == 2 and bits[1] or ''
     354            self.name = bits[0]
     355        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]           
     356
     357    def peek(self):
     358        return self.tokens[self.offset]
     359       
     360    def consumed(self):
     361        return self.offset == len(self.tokens)
     362           
     363    def pop(self):
     364        if self.offset == len(self.tokens):
     365            raise self.exception
     366        next = self.tokens[self.offset]
     367        self.offset += 1       
     368        return next
     369   
     370    def pop_lexem(self, lexem):
     371        if self.offset == len(self.tokens):
     372            return False
     373        _, next = self.tokens[self.offset]
     374        if next == lexem:
     375            self.offset += 1
     376            return True
     377        return False
     378       
     379    def pop_name(self):
     380        if self.offset == len(self.tokens):
     381            return None
     382        tokentype, lexem = self.tokens[self.offset]
     383        if tokentype == 'name':
     384            self.offset += 1
     385            return lexem
     386        return None
     387       
     388    def pushback(self):
     389        self.offset -= 1
     390       
     391    def assert_consumed(self, msg=None):
     392        if self.offset != len(self.tokens):
     393            raise TemplateSyntaxError, (msg or "unmatched input") + repr(self.tokens[self.offset:])
     394
     395    def expected(self, what):
     396        if self.consumed():
     397            found = "<EOT>"
     398        else:
     399            found = "<%s> %s" % self.tokens[self.offset]
     400        raise TemplateSyntaxError, "expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace'))
     401
     402
     403    #@token_stream_parser
     404    def parse_string(self, bare=False):
     405        tokentype, lexem = self.pop()
     406        if tokentype == 'string_literal':
     407            return lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
     408        if bare and tokentype == 'name':
     409            return lexem
     410        raise self.exception
     411    parse_string = token_stream_parser(parse_string)
     412
     413
     414    #@token_stream_parser
     415    def parse_int(self):
     416        token_type, lexem = self.pop()
     417        if token_type == 'numeric_literal':
     418            try:
     419                return int(lexem)
     420            except ValueError:
     421                pass
     422        raise self.exception
     423    parse_int = token_stream_parser(parse_int)
     424
     425
     426    #@token_stream_parser
     427    def parse_value(self):
     428        translate = False
     429        if self.pop_lexem('_'):
     430            if not self.pop_lexem('('):
     431                raise self.exception
     432            translate = True
     433        tokentype, lexem = self.pop()
     434   
     435        if tokentype == 'char':
     436            raise self.exception
     437   
     438        if tokentype == 'name':
     439            if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_':
     440                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem)
     441            value = Lookup(lexem)       
     442        elif tokentype == 'string_literal':
     443            value = lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1]
     444            value = Literal(mark_safe(value))
     445        elif tokentype == 'numeric_literal':
     446            try:
     447                value = float(lexem)
     448            except ValueError:
     449                raise self.exception
     450            if '.' not in lexem and 'e' not in lexem.lower():
     451                value = int(value)
     452            #FIXME: this causes a test failure: `ifequal-numeric07`
     453            if lexem.endswith('.'):
     454                raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem           
     455            value = Literal(value)
     456        if translate:
     457            if not self.pop_lexem(')'):
     458                raise self.exception
     459            value = FilterExpression(value, [(ugettext, ())])       
     460        return value
     461    parse_value = token_stream_parser(parse_value)
     462
     463
     464    #@token_stream_parser
     465    def parse_filter(self):
     466        if not self.pop_lexem('|'):
     467            raise self.exception
     468        name = self.pop_name()
     469        if not name:
     470            raise self.exception
     471        args = []
     472        if self.pop_lexem(':'):
     473            args.append(self.parse_value())
     474        func = self.parser.find_filter(name)
     475        filter_args_check(name, func, args)
     476        return func, args
     477    parse_filter = token_stream_parser(parse_filter)
     478
     479
     480    #@token_stream_parser
     481    def parse_expression(self):
     482        var = self.parse_value()
     483        filters = []
     484        try:
     485            while True:
     486                filters.append(self.parse_filter())
     487        except TokenSyntaxError:
     488            pass
     489        if filters:
     490            return FilterExpression(var, filters)
     491        return var
     492    parse_expression = token_stream_parser(parse_expression)
     493
     494
     495    #@token_stream_parser
     496    def parse_expression_list(self, minimum=0, maximum=None, count=None):
     497        expressions = []
     498        if count:
     499            minimum = count
     500            maximum = count
     501        try:
     502            while True:
     503                if len(expressions) == maximum:
     504                    break           
     505                expressions.append(self.parse_expression())
     506        except TokenSyntaxError:
     507            pass
     508        if len(expressions) < minimum:
     509            self.expected("expression")
     510        return expressions
     511    parse_expression_list = token_stream_parser(parse_expression_list)
     512
     513
     514def token_stream_parsed(func):
     515    def decorator(parser, token):
     516        bits = parser.token_stream(token)
     517        result = func(parser, bits)
     518        bits.assert_consumed()
     519        return result   
     520    return wraps(func)(decorator)
     521
  • django/template/__init__.py

     
    1212Node objects.
    1313
    1414Each Node is responsible for creating some sort of output -- e.g. simple text
    15 (TextNode), variable values in a given context (VariableNode), results of basic
     15(TextNode), variable values in a given context (ExpressionNode), results of basic
    1616logic (IfNode), results of looping (ForNode), or anything else. The core Node
    17 types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
     17types are TextNode, ExpressionNode, IfNode and ForNode, but plugin modules can
    1818define their own custom node types.
    1919
    2020Each Node has a render() method, which takes a Context and returns a string of
     
    4848>>> t.render(c)
    4949u'<html></html>'
    5050"""
    51 import re
    52 from inspect import getargspec
    53 from django.conf import settings
     51
    5452from django.template.context import Context, RequestContext, ContextPopException
    55 from django.utils.itercompat import is_iterable
    56 from django.utils.functional import curry, Promise
    57 from django.utils.text import smart_split
    58 from django.utils.encoding import smart_unicode, force_unicode
    59 from django.utils.translation import ugettext as _
    60 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
    61 from django.utils.html import escape
     53from django.template.nodes import Node, NodeList, TextNode, ExpressionNode
     54from django.template.compiler import Origin, StringOrigin, Template, \
     55    TemplateSyntaxError, compile_string, TokenSyntaxError
     56from django.template.library import Library, InvalidTemplateLibrary, \
     57    get_library, add_to_builtins, libraries
     58from django.template.expressions import Expression, LookupError
    6259
    63 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
     60#backcompat
     61from django.template.compat import Variable, VariableDoesNotExist, VariableNode, \
     62    resolve_variable, TokenParser
     63from django.template.loader import TemplateDoesNotExist
    6464
    65 TOKEN_TEXT = 0
    66 TOKEN_VAR = 1
    67 TOKEN_BLOCK = 2
    68 TOKEN_COMMENT = 3
     65__all__ = (
     66    'Template', 'TemplateSyntaxError', 'compile_string', 'Origin', 'TokenSyntaxError',
     67    'Context', 'RequestContext',
     68    'Expression', 'LookupError',
     69    #FIXME: should these be public? 'Literal', 'Lookup', 'FilterExpression'
     70    'Library', 'InvalidTemplateLibrary', 'get_library', 'add_to_builtins', 'libraries',
     71    #backcompat
     72    'Variable', 'VariableDoesNotExist', 'resolve_variable', 'TemplateDoesNotExist', 'TokenParser', 'VariableNode',
     73)
    6974
    70 # template syntax constants
    71 FILTER_SEPARATOR = '|'
    72 FILTER_ARGUMENT_SEPARATOR = ':'
    73 VARIABLE_ATTRIBUTE_SEPARATOR = '.'
    74 BLOCK_TAG_START = '{%'
    75 BLOCK_TAG_END = '%}'
    76 VARIABLE_TAG_START = '{{'
    77 VARIABLE_TAG_END = '}}'
    78 COMMENT_TAG_START = '{#'
    79 COMMENT_TAG_END = '#}'
    80 SINGLE_BRACE_START = '{'
    81 SINGLE_BRACE_END = '}'
    82 
    83 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
    84 
    85 # what to report as the origin for templates that come from non-loader sources
    86 # (e.g. strings)
    87 UNKNOWN_SOURCE="&lt;unknown source&gt;"
    88 
    89 # match a variable or block tag and capture the entire tag, including start/end delimiters
    90 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    91                                           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
    92                                           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
    93 
    94 # global dictionary of libraries that have been loaded using get_library
    95 libraries = {}
    96 # global list of libraries to load by default for a new parser
    97 builtins = []
    98 
    9975# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
    10076# uninitialised.
    10177invalid_var_format_string = None
    10278
    103 class TemplateSyntaxError(Exception):
    104     def __str__(self):
    105         try:
    106             import cStringIO as StringIO
    107         except ImportError:
    108             import StringIO
    109         output = StringIO.StringIO()
    110         output.write(Exception.__str__(self))
    111         # Check if we wrapped an exception and print that too.
    112         if hasattr(self, 'exc_info'):
    113             import traceback
    114             output.write('\n\nOriginal ')
    115             e = self.exc_info
    116             traceback.print_exception(e[0], e[1], e[2], 500, output)
    117         return output.getvalue()
    118 
    119 class TemplateDoesNotExist(Exception):
    120     pass
    121 
    122 class TemplateEncodingError(Exception):
    123     pass
    124 
    125 class VariableDoesNotExist(Exception):
    126 
    127     def __init__(self, msg, params=()):
    128         self.msg = msg
    129         self.params = params
    130 
    131     def __str__(self):
    132         return unicode(self).encode('utf-8')
    133 
    134     def __unicode__(self):
    135         return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
    136 
    137 class InvalidTemplateLibrary(Exception):
    138     pass
    139 
    140 class Origin(object):
    141     def __init__(self, name):
    142         self.name = name
    143 
    144     def reload(self):
    145         raise NotImplementedError
    146 
    147     def __str__(self):
    148         return self.name
    149 
    150 class StringOrigin(Origin):
    151     def __init__(self, source):
    152         super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
    153         self.source = source
    154 
    155     def reload(self):
    156         return self.source
    157 
    158 class Template(object):
    159     def __init__(self, template_string, origin=None, name='<Unknown Template>'):
    160         try:
    161             template_string = smart_unicode(template_string)
    162         except UnicodeDecodeError:
    163             raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
    164         if settings.TEMPLATE_DEBUG and origin is None:
    165             origin = StringOrigin(template_string)
    166         self.nodelist = compile_string(template_string, origin)
    167         self.name = name
    168 
    169     def __iter__(self):
    170         for node in self.nodelist:
    171             for subnode in node:
    172                 yield subnode
    173 
    174     def render(self, context):
    175         "Display stage -- can be called many times"
    176         return self.nodelist.render(context)
    177 
    178 def compile_string(template_string, origin):
    179     "Compiles template_string into NodeList ready for rendering"
    180     if settings.TEMPLATE_DEBUG:
    181         from debug import DebugLexer, DebugParser
    182         lexer_class, parser_class = DebugLexer, DebugParser
    183     else:
    184         lexer_class, parser_class = Lexer, Parser
    185     lexer = lexer_class(template_string, origin)
    186     parser = parser_class(lexer.tokenize())
    187     return parser.parse()
    188 
    189 class Token(object):
    190     def __init__(self, token_type, contents):
    191         # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
    192         self.token_type, self.contents = token_type, contents
    193 
    194     def __str__(self):
    195         return '<%s token: "%s...">' % \
    196             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
    197             self.contents[:20].replace('\n', ''))
    198 
    199     def split_contents(self):
    200         return list(smart_split(self.contents))
    201 
    202 class Lexer(object):
    203     def __init__(self, template_string, origin):
    204         self.template_string = template_string
    205         self.origin = origin
    206 
    207     def tokenize(self):
    208         "Return a list of tokens from a given template_string."
    209         in_tag = False
    210         result = []
    211         for bit in tag_re.split(self.template_string):
    212             if bit:
    213                 result.append(self.create_token(bit, in_tag))
    214             in_tag = not in_tag
    215         return result
    216 
    217     def create_token(self, token_string, in_tag):
    218         """
    219         Convert the given token string into a new Token object and return it.
    220         If in_tag is True, we are processing something that matched a tag,
    221         otherwise it should be treated as a literal string.
    222         """
    223         if in_tag:
    224             if token_string.startswith(VARIABLE_TAG_START):
    225                 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
    226             elif token_string.startswith(BLOCK_TAG_START):
    227                 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
    228             elif token_string.startswith(COMMENT_TAG_START):
    229                 token = Token(TOKEN_COMMENT, '')
    230         else:
    231             token = Token(TOKEN_TEXT, token_string)
    232         return token
    233 
    234 class Parser(object):
    235     def __init__(self, tokens):
    236         self.tokens = tokens
    237         self.tags = {}
    238         self.filters = {}
    239         for lib in builtins:
    240             self.add_library(lib)
    241 
    242     def parse(self, parse_until=None):
    243         if parse_until is None: parse_until = []
    244         nodelist = self.create_nodelist()
    245         while self.tokens:
    246             token = self.next_token()
    247             if token.token_type == TOKEN_TEXT:
    248                 self.extend_nodelist(nodelist, TextNode(token.contents), token)
    249             elif token.token_type == TOKEN_VAR:
    250                 if not token.contents:
    251                     self.empty_variable(token)
    252                 filter_expression = self.compile_filter(token.contents)
    253                 var_node = self.create_variable_node(filter_expression)
    254                 self.extend_nodelist(nodelist, var_node,token)
    255             elif token.token_type == TOKEN_BLOCK:
    256                 if token.contents in parse_until:
    257                     # put token back on token list so calling code knows why it terminated
    258                     self.prepend_token(token)
    259                     return nodelist
    260                 try:
    261                     command = token.contents.split()[0]
    262                 except IndexError:
    263                     self.empty_block_tag(token)
    264                 # execute callback function for this tag and append resulting node
    265                 self.enter_command(command, token)
    266                 try:
    267                     compile_func = self.tags[command]
    268                 except KeyError:
    269                     self.invalid_block_tag(token, command)
    270                 try:
    271                     compiled_result = compile_func(self, token)
    272                 except TemplateSyntaxError, e:
    273                     if not self.compile_function_error(token, e):
    274                         raise
    275                 self.extend_nodelist(nodelist, compiled_result, token)
    276                 self.exit_command()
    277         if parse_until:
    278             self.unclosed_block_tag(parse_until)
    279         return nodelist
    280 
    281     def skip_past(self, endtag):
    282         while self.tokens:
    283             token = self.next_token()
    284             if token.token_type == TOKEN_BLOCK and token.contents == endtag:
    285                 return
    286         self.unclosed_block_tag([endtag])
    287 
    288     def create_variable_node(self, filter_expression):
    289         return VariableNode(filter_expression)
    290 
    291     def create_nodelist(self):
    292         return NodeList()
    293 
    294     def extend_nodelist(self, nodelist, node, token):
    295         if node.must_be_first and nodelist:
    296             try:
    297                 if nodelist.contains_nontext:
    298                     raise AttributeError
    299             except AttributeError:
    300                 raise TemplateSyntaxError("%r must be the first tag in the template." % node)
    301         if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
    302             nodelist.contains_nontext = True
    303         nodelist.append(node)
    304 
    305     def enter_command(self, command, token):
    306         pass
    307 
    308     def exit_command(self):
    309         pass
    310 
    311     def error(self, token, msg):
    312         return TemplateSyntaxError(msg)
    313 
    314     def empty_variable(self, token):
    315         raise self.error(token, "Empty variable tag")
    316 
    317     def empty_block_tag(self, token):
    318         raise self.error(token, "Empty block tag")
    319 
    320     def invalid_block_tag(self, token, command):
    321         raise self.error(token, "Invalid block tag: '%s'" % command)
    322 
    323     def unclosed_block_tag(self, parse_until):
    324         raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
    325 
    326     def compile_function_error(self, token, e):
    327         pass
    328 
    329     def next_token(self):
    330         return self.tokens.pop(0)
    331 
    332     def prepend_token(self, token):
    333         self.tokens.insert(0, token)
    334 
    335     def delete_first_token(self):
    336         del self.tokens[0]
    337 
    338     def add_library(self, lib):
    339         self.tags.update(lib.tags)
    340         self.filters.update(lib.filters)
    341 
    342     def compile_filter(self, token):
    343         "Convenient wrapper for FilterExpression"
    344         return FilterExpression(token, self)
    345 
    346     def find_filter(self, filter_name):
    347         if filter_name in self.filters:
    348             return self.filters[filter_name]
    349         else:
    350             raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
    351 
    352 class TokenParser(object):
    353     """
    354     Subclass this and implement the top() method to parse a template line. When
    355     instantiating the parser, pass in the line from the Django template parser.
    356 
    357     The parser's "tagname" instance-variable stores the name of the tag that
    358     the filter was called with.
    359     """
    360     def __init__(self, subject):
    361         self.subject = subject
    362         self.pointer = 0
    363         self.backout = []
    364         self.tagname = self.tag()
    365 
    366     def top(self):
    367         "Overload this method to do the actual parsing and return the result."
    368         raise NotImplementedError()
    369 
    370     def more(self):
    371         "Returns True if there is more stuff in the tag."
    372         return self.pointer < len(self.subject)
    373 
    374     def back(self):
    375         "Undoes the last microparser. Use this for lookahead and backtracking."
    376         if not len(self.backout):
    377             raise TemplateSyntaxError("back called without some previous parsing")
    378         self.pointer = self.backout.pop()
    379 
    380     def tag(self):
    381         "A microparser that just returns the next tag from the line."
    382         subject = self.subject
    383         i = self.pointer
    384         if i >= len(subject):
    385             raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
    386         p = i
    387         while i < len(subject) and subject[i] not in (' ', '\t'):
    388             i += 1
    389         s = subject[p:i]
    390         while i < len(subject) and subject[i] in (' ', '\t'):
    391             i += 1
    392         self.backout.append(self.pointer)
    393         self.pointer = i
    394         return s
    395 
    396     def value(self):
    397         "A microparser that parses for a value: some string constant or variable name."
    398         subject = self.subject
    399         i = self.pointer
    400         if i >= len(subject):
    401             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
    402         if subject[i] in ('"', "'"):
    403             p = i
    404             i += 1
    405             while i < len(subject) and subject[i] != subject[p]:
    406                 i += 1
    407             if i >= len(subject):
    408                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
    409             i += 1
    410             res = subject[p:i]
    411             while i < len(subject) and subject[i] in (' ', '\t'):
    412                 i += 1
    413             self.backout.append(self.pointer)
    414             self.pointer = i
    415             return res
    416         else:
    417             p = i
    418             while i < len(subject) and subject[i] not in (' ', '\t'):
    419                 if subject[i] in ('"', "'"):
    420                     c = subject[i]
    421                     i += 1
    422                     while i < len(subject) and subject[i] != c:
    423                         i += 1
    424                     if i >= len(subject):
    425                         raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
    426                 i += 1
    427             s = subject[p:i]
    428             while i < len(subject) and subject[i] in (' ', '\t'):
    429                 i += 1
    430             self.backout.append(self.pointer)
    431             self.pointer = i
    432             return s
    433 
    434 filter_raw_string = r"""
    435 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
    436 ^"(?P<constant>%(str)s)"|
    437 ^(?P<var>[%(var_chars)s]+)|
    438  (?:%(filter_sep)s
    439      (?P<filter_name>\w+)
    440          (?:%(arg_sep)s
    441              (?:
    442               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
    443               "(?P<constant_arg>%(str)s)"|
    444               (?P<var_arg>[%(var_chars)s]+)
    445              )
    446          )?
    447  )""" % {
    448     'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
    449     'var_chars': "\w\." ,
    450     'filter_sep': re.escape(FILTER_SEPARATOR),
    451     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    452     'i18n_open' : re.escape("_("),
    453     'i18n_close' : re.escape(")"),
    454   }
    455 
    456 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
    457 filter_re = re.compile(filter_raw_string, re.UNICODE)
    458 
    459 class FilterExpression(object):
    460     """
    461     Parses a variable token and its optional filters (all as a single string),
    462     and return a list of tuples of the filter name and arguments.
    463     Sample:
    464         >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    465         >>> p = Parser('')
    466         >>> fe = FilterExpression(token, p)
    467         >>> len(fe.filters)
    468         2
    469         >>> fe.var
    470         <Variable: 'variable'>
    471 
    472     This class should never be instantiated outside of the
    473     get_filters_from_token helper function.
    474     """
    475     def __init__(self, token, parser):
    476         self.token = token
    477         matches = filter_re.finditer(token)
    478         var = None
    479         filters = []
    480         upto = 0
    481         for match in matches:
    482             start = match.start()
    483             if upto != start:
    484                 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
    485                                            (token[:upto], token[upto:start], token[start:]))
    486             if var == None:
    487                 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
    488                 if i18n_constant:
    489                     var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
    490                 elif constant:
    491                     var = '"%s"' % constant.replace(r'\"', '"')
    492                 upto = match.end()
    493                 if var == None:
    494                     raise TemplateSyntaxError("Could not find variable at start of %s" % token)
    495                 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    496                     raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
    497             else:
    498                 filter_name = match.group("filter_name")
    499                 args = []
    500                 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
    501                 if i18n_arg:
    502                     args.append((False, _(i18n_arg.replace(r'\"', '"'))))
    503                 elif constant_arg is not None:
    504                     args.append((False, constant_arg.replace(r'\"', '"')))
    505                 elif var_arg:
    506                     args.append((True, Variable(var_arg)))
    507                 filter_func = parser.find_filter(filter_name)
    508                 self.args_check(filter_name,filter_func, args)
    509                 filters.append( (filter_func,args))
    510                 upto = match.end()
    511         if upto != len(token):
    512             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
    513         self.filters = filters
    514         self.var = Variable(var)
    515 
    516     def resolve(self, context, ignore_failures=False):
    517         try:
    518             obj = self.var.resolve(context)
    519         except VariableDoesNotExist:
    520             if ignore_failures:
    521                 obj = None
    522             else:
    523                 if settings.TEMPLATE_STRING_IF_INVALID:
    524                     global invalid_var_format_string
    525                     if invalid_var_format_string is None:
    526                         invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
    527                     if invalid_var_format_string:
    528                         return settings.TEMPLATE_STRING_IF_INVALID % self.var
    529                     return settings.TEMPLATE_STRING_IF_INVALID
    530                 else:
    531                     obj = settings.TEMPLATE_STRING_IF_INVALID
    532         for func, args in self.filters:
    533             arg_vals = []
    534             for lookup, arg in args:
    535                 if not lookup:
    536                     arg_vals.append(mark_safe(arg))
    537                 else:
    538                     arg_vals.append(arg.resolve(context))
    539             if getattr(func, 'needs_autoescape', False):
    540                 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
    541             else:
    542                 new_obj = func(obj, *arg_vals)
    543             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
    544                 obj = mark_safe(new_obj)
    545             elif isinstance(obj, EscapeData):
    546                 obj = mark_for_escaping(new_obj)
    547             else:
    548                 obj = new_obj
    549         return obj
    550 
    551     def args_check(name, func, provided):
    552         provided = list(provided)
    553         plen = len(provided)
    554         # Check to see if a decorator is providing the real function.
    555         func = getattr(func, '_decorated_function', func)
    556         args, varargs, varkw, defaults = getargspec(func)
    557         # First argument is filter input.
    558         args.pop(0)
    559         if defaults:
    560             nondefs = args[:-len(defaults)]
    561         else:
    562             nondefs = args
    563         # Args without defaults must be provided.
    564         try:
    565             for arg in nondefs:
    566                 provided.pop(0)
    567         except IndexError:
    568             # Not enough
    569             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
    570 
    571         # Defaults can be overridden.
    572         defaults = defaults and list(defaults) or []
    573         try:
    574             for parg in provided:
    575                 defaults.pop(0)
    576         except IndexError:
    577             # Too many.
    578             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
    579 
    580         return True
    581     args_check = staticmethod(args_check)
    582 
    583     def __str__(self):
    584         return self.token
    585 
    586 def resolve_variable(path, context):
    587     """
    588     Returns the resolved variable, which may contain attribute syntax, within
    589     the given context.
    590 
    591     Deprecated; use the Variable class instead.
    592     """
    593     return Variable(path).resolve(context)
    594 
    595 class Variable(object):
    596     """
    597     A template variable, resolvable against a given context. The variable may be
    598     a hard-coded string (if it begins and ends with single or double quote
    599     marks)::
    600 
    601         >>> c = {'article': {'section':u'News'}}
    602         >>> Variable('article.section').resolve(c)
    603         u'News'
    604         >>> Variable('article').resolve(c)
    605         {'section': u'News'}
    606         >>> class AClass: pass
    607         >>> c = AClass()
    608         >>> c.article = AClass()
    609         >>> c.article.section = u'News'
    610         >>> Variable('article.section').resolve(c)
    611         u'News'
    612 
    613     (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
    614     """
    615 
    616     def __init__(self, var):
    617         self.var = var
    618         self.literal = None
    619         self.lookups = None
    620         self.translate = False
    621 
    622         try:
    623             # First try to treat this variable as a number.
    624             #
    625             # Note that this could cause an OverflowError here that we're not
    626             # catching. Since this should only happen at compile time, that's
    627             # probably OK.
    628             self.literal = float(var)
    629 
    630             # So it's a float... is it an int? If the original value contained a
    631             # dot or an "e" then it was a float, not an int.
    632             if '.' not in var and 'e' not in var.lower():
    633                 self.literal = int(self.literal)
    634 
    635             # "2." is invalid
    636             if var.endswith('.'):
    637                 raise ValueError
    638 
    639         except ValueError:
    640             # A ValueError means that the variable isn't a number.
    641             if var.startswith('_(') and var.endswith(')'):
    642                 # The result of the lookup should be translated at rendering
    643                 # time.
    644                 self.translate = True
    645                 var = var[2:-1]
    646             # If it's wrapped with quotes (single or double), then
    647             # we're also dealing with a literal.
    648             if var[0] in "\"'" and var[0] == var[-1]:
    649                 self.literal = mark_safe(var[1:-1])
    650             else:
    651                 # Otherwise we'll set self.lookups so that resolve() knows we're
    652                 # dealing with a bonafide variable
    653                 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
    654 
    655     def resolve(self, context):
    656         """Resolve this variable against a given context."""
    657         if self.lookups is not None:
    658             # We're dealing with a variable that needs to be resolved
    659             value = self._resolve_lookup(context)
    660         else:
    661             # We're dealing with a literal, so it's already been "resolved"
    662             value = self.literal
    663         if self.translate:
    664             return _(value)
    665         return value
    666 
    667     def __repr__(self):
    668         return "<%s: %r>" % (self.__class__.__name__, self.var)
    669 
    670     def __str__(self):
    671         return self.var
    672 
    673     def _resolve_lookup(self, context):
    674         """
    675         Performs resolution of a real variable (i.e. not a literal) against the
    676         given context.
    677 
    678         As indicated by the method's name, this method is an implementation
    679         detail and shouldn't be called by external code. Use Variable.resolve()
    680         instead.
    681         """
    682         current = context
    683         for bit in self.lookups:
    684             try: # dictionary lookup
    685                 current = current[bit]
    686             except (TypeError, AttributeError, KeyError):
    687                 try: # attribute lookup
    688                     current = getattr(current, bit)
    689                     if callable(current):
    690                         if getattr(current, 'alters_data', False):
    691                             current = settings.TEMPLATE_STRING_IF_INVALID
    692                         else:
    693                             try: # method call (assuming no args required)
    694                                 current = current()
    695                             except TypeError: # arguments *were* required
    696                                 # GOTCHA: This will also catch any TypeError
    697                                 # raised in the function itself.
    698                                 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
    699                             except Exception, e:
    700                                 if getattr(e, 'silent_variable_failure', False):
    701                                     current = settings.TEMPLATE_STRING_IF_INVALID
    702                                 else:
    703                                     raise
    704                 except (TypeError, AttributeError):
    705                     try: # list-index lookup
    706                         current = current[int(bit)]
    707                     except (IndexError, # list index out of range
    708                             ValueError, # invalid literal for int()
    709                             KeyError,   # current is a dict without `int(bit)` key
    710                             TypeError,  # unsubscriptable object
    711                             ):
    712                         raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
    713                 except Exception, e:
    714                     if getattr(e, 'silent_variable_failure', False):
    715                         current = settings.TEMPLATE_STRING_IF_INVALID
    716                     else:
    717                         raise
    718 
    719         return current
    720 
    721 class Node(object):
    722     # Set this to True for nodes that must be first in the template (although
    723     # they can be preceded by text nodes.
    724     must_be_first = False
    725 
    726     def render(self, context):
    727         "Return the node rendered as a string"
    728         pass
    729 
    730     def __iter__(self):
    731         yield self
    732 
    733     def get_nodes_by_type(self, nodetype):
    734         "Return a list of all nodes (within this node and its nodelist) of the given type"
    735         nodes = []
    736         if isinstance(self, nodetype):
    737             nodes.append(self)
    738         if hasattr(self, 'nodelist'):
    739             nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
    740         return nodes
    741 
    742 class NodeList(list):
    743     # Set to True the first time a non-TextNode is inserted by
    744     # extend_nodelist().
    745     contains_nontext = False
    746 
    747     def render(self, context):
    748         bits = []
    749         for node in self:
    750             if isinstance(node, Node):
    751                 bits.append(self.render_node(node, context))
    752             else:
    753                 bits.append(node)
    754         return mark_safe(''.join([force_unicode(b) for b in bits]))
    755 
    756     def get_nodes_by_type(self, nodetype):
    757         "Return a list of all nodes of the given type"
    758         nodes = []
    759         for node in self:
    760             nodes.extend(node.get_nodes_by_type(nodetype))
    761         return nodes
    762 
    763     def render_node(self, node, context):
    764         return node.render(context)
    765 
    766 class TextNode(Node):
    767     def __init__(self, s):
    768         self.s = s
    769 
    770     def __repr__(self):
    771         return "<Text Node: '%s'>" % self.s[:25]
    772 
    773     def render(self, context):
    774         return self.s
    775 
    776 class VariableNode(Node):
    777     def __init__(self, filter_expression):
    778         self.filter_expression = filter_expression
    779 
    780     def __repr__(self):
    781         return "<Variable Node: %s>" % self.filter_expression
    782 
    783     def render(self, context):
    784         try:
    785             output = force_unicode(self.filter_expression.resolve(context))
    786         except UnicodeDecodeError:
    787             # Unicode conversion can fail sometimes for reasons out of our
    788             # control (e.g. exception rendering). In that case, we fail quietly.
    789             return ''
    790         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
    791             return force_unicode(escape(output))
    792         else:
    793             return force_unicode(output)
    794 
    795 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    796     "Returns a template.Node subclass."
    797     bits = token.split_contents()[1:]
    798     bmax = len(params)
    799     def_len = defaults and len(defaults) or 0
    800     bmin = bmax - def_len
    801     if(len(bits) < bmin or len(bits) > bmax):
    802         if bmin == bmax:
    803             message = "%s takes %s arguments" % (name, bmin)
    804         else:
    805             message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    806         raise TemplateSyntaxError(message)
    807     return node_class(bits)
    808 
    809 class Library(object):
    810     def __init__(self):
    811         self.filters = {}
    812         self.tags = {}
    813 
    814     def tag(self, name=None, compile_function=None):
    815         if name == None and compile_function == None:
    816             # @register.tag()
    817             return self.tag_function
    818         elif name != None and compile_function == None:
    819             if(callable(name)):
    820                 # @register.tag
    821                 return self.tag_function(name)
    822             else:
    823                 # @register.tag('somename') or @register.tag(name='somename')
    824                 def dec(func):
    825                     return self.tag(name, func)
    826                 return dec
    827         elif name != None and compile_function != None:
    828             # register.tag('somename', somefunc)
    829             self.tags[name] = compile_function
    830             return compile_function
    831         else:
    832             raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
    833 
    834     def tag_function(self,func):
    835         self.tags[getattr(func, "_decorated_function", func).__name__] = func
    836         return func
    837 
    838     def filter(self, name=None, filter_func=None):
    839         if name == None and filter_func == None:
    840             # @register.filter()
    841             return self.filter_function
    842         elif filter_func == None:
    843             if(callable(name)):
    844                 # @register.filter
    845                 return self.filter_function(name)
    846             else:
    847                 # @register.filter('somename') or @register.filter(name='somename')
    848                 def dec(func):
    849                     return self.filter(name, func)
    850                 return dec
    851         elif name != None and filter_func != None:
    852             # register.filter('somename', somefunc)
    853             self.filters[name] = filter_func
    854             return filter_func
    855         else:
    856             raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
    857 
    858     def filter_function(self, func):
    859         self.filters[getattr(func, "_decorated_function", func).__name__] = func
    860         return func
    861 
    862     def simple_tag(self,func):
    863         params, xx, xxx, defaults = getargspec(func)
    864 
    865         class SimpleNode(Node):
    866             def __init__(self, vars_to_resolve):
    867                 self.vars_to_resolve = map(Variable, vars_to_resolve)
    868 
    869             def render(self, context):
    870                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    871                 return func(*resolved_vars)
    872 
    873         compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
    874         compile_func.__doc__ = func.__doc__
    875         self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    876         return func
    877 
    878     def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    879         def dec(func):
    880             params, xx, xxx, defaults = getargspec(func)
    881             if takes_context:
    882                 if params[0] == 'context':
    883                     params = params[1:]
    884                 else:
    885                     raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
    886 
    887             class InclusionNode(Node):
    888                 def __init__(self, vars_to_resolve):
    889                     self.vars_to_resolve = map(Variable, vars_to_resolve)
    890 
    891                 def render(self, context):
    892                     resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    893                     if takes_context:
    894                         args = [context] + resolved_vars
    895                     else:
    896                         args = resolved_vars
    897 
    898                     dict = func(*args)
    899 
    900                     if not getattr(self, 'nodelist', False):
    901                         from django.template.loader import get_template, select_template
    902                         if not isinstance(file_name, basestring) and is_iterable(file_name):
    903                             t = select_template(file_name)
    904                         else:
    905                             t = get_template(file_name)
    906                         self.nodelist = t.nodelist
    907                     return self.nodelist.render(context_class(dict,
    908                             autoescape=context.autoescape))
    909 
    910             compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
    911             compile_func.__doc__ = func.__doc__
    912             self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    913             return func
    914         return dec
    915 
    916 def get_library(module_name):
    917     lib = libraries.get(module_name, None)
    918     if not lib:
    919         try:
    920             mod = __import__(module_name, {}, {}, [''])
    921         except ImportError, e:
    922             raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
    923         try:
    924             lib = mod.register
    925             libraries[module_name] = lib
    926         except AttributeError:
    927             raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
    928     return lib
    929 
    930 def add_to_builtins(module_name):
    931     builtins.append(get_library(module_name))
    932 
    93379add_to_builtins('django.template.defaulttags')
    93480add_to_builtins('django.template.defaultfilters')
  • django/template/utils.py

     
     1import re
     2from django.template import Node, NodeList, TokenSyntaxError
     3
     4class EmptyNode(Node):
     5    def render(self, context):
     6        return u''
     7       
     8class ConditionalNode(Node):
     9    def __init__(self, nodelist_true, nodelist_false):
     10        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     11
     12    def __iter__(self):
     13        for node in self.nodelist_true:
     14            yield node
     15        for node in self.nodelist_false:
     16            yield node
     17
     18    def get_nodes_by_type(self, nodetype):
     19        nodes = []
     20        if isinstance(self, nodetype):
     21            nodes.append(self)
     22        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
     23        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
     24        return nodes
     25       
     26    def check_condition(self, context):
     27        return False
     28       
     29    def render(self, context):
     30        if self.check_condition(context):
     31            return self.nodelist_true.render(context)
     32        else:
     33            return self.nodelist_false.render(context)       
     34
     35def parse_conditional_nodelists(parser, name):
     36    end_tag = 'end' + name
     37    nodelist_true = parser.parse(('else', end_tag))
     38    token = parser.next_token()
     39    if token.contents == 'else':
     40        nodelist_false = parser.parse((end_tag,))
     41        parser.delete_first_token()
     42    else:
     43        nodelist_false = NodeList()
     44    return nodelist_true, nodelist_false
     45
     46def parse_args_and_kwargs(self):
     47    args = []
     48    kwargs = {}
     49    while True:
     50        name = self.pop_name()
     51        if name and self.pop_lexem('='):
     52            try:
     53                kwargs[name] = self.parse_expression()
     54            except TokenSyntaxError:
     55                raise TemplateSyntaxError, "expected expression in kwargs"
     56        else:
     57            if name:
     58                self.pushback()
     59            try:
     60                args.append(self.parse_expression())
     61            except TokenSyntaxError:
     62                break
     63        if not self.pop_lexem(','):
     64            break
     65    return args, kwargs
     66
     67def parse_as(bits):
     68    if bits.pop_lexem('as'):
     69        name = bits.pop_name()
     70        if name:
     71            return name
     72    raise bits.exception
     73
     74def resolve_args_and_kwargs(args, kwargs, context):
     75        resolved_args = [arg.resolve(context, True) for arg in args]
     76        resolved_kwargs = {}
     77        for name in kwargs:
     78                resolved_kwargs[name] = kwargs[name].resolve(context, True)
     79        return resolved_args, resolved_kwargs
     80   
  • django/template/expressions.py

     
     1from django.conf import settings
     2from django.utils.encoding import force_unicode
     3from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     4
     5__all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression')
     6
     7VARIABLE_ATTRIBUTE_SEPARATOR = '.'
     8
     9class LookupError(Exception):
     10    def __init__(self, var, msg, params=()):
     11        self.var = var
     12        self.msg = msg
     13        self.params = params
     14
     15    def __str__(self):
     16        return unicode(self).encode('utf-8')
     17
     18    def __unicode__(self):
     19        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
     20
     21
     22class Expression(object):
     23    def resolve_safe(self, context, default=None):
     24        try:
     25            return self.resolve(context)
     26        except LookupError:
     27            return default
     28
     29    def resolve(self, context):
     30        pass
     31
     32class Literal(Expression):
     33    def __init__(self, value):
     34        self.value = value
     35
     36    def resolve(self, context):
     37        return self.value
     38       
     39class Lookup(Expression):
     40    def __init__(self, var):
     41        self.var = var
     42        self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
     43
     44    def __str__(self):
     45        return self.var
     46       
     47    def resolve(self, context):
     48        current = context
     49        for bit in self.lookups:
     50            try: # dictionary lookup
     51                current = current[bit]
     52            except (TypeError, AttributeError, KeyError):
     53                try: # attribute lookup
     54                    current = getattr(current, bit)
     55                    if callable(current):
     56                        if getattr(current, 'alters_data', False):
     57                            current = settings.TEMPLATE_STRING_IF_INVALID
     58                        else:
     59                            try: # method call (assuming no args required)
     60                                current = current()
     61                            except TypeError: # arguments *were* required
     62                                # GOTCHA: This will also catch any TypeError
     63                                # raised in the function itself.
     64                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     65                            except Exception, e:
     66                                if getattr(e, 'silent_variable_failure', False):
     67                                    current = settings.TEMPLATE_STRING_IF_INVALID
     68                                else:
     69                                    raise
     70                except (TypeError, AttributeError):
     71                    try: # list-index lookup
     72                        current = current[int(bit)]
     73                    except (IndexError, # list index out of range
     74                            ValueError, # invalid literal for int()
     75                            KeyError,   # current is a dict without `int(bit)` key
     76                            TypeError,  # unsubscriptable object
     77                            ):
     78                        raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
     79                except Exception, e:
     80                    if getattr(e, 'silent_variable_failure', False):
     81                        current = settings.TEMPLATE_STRING_IF_INVALID
     82                    else:
     83                        raise
     84
     85        return current
     86
     87class FilterExpression(Expression):
     88    def __init__(self, root, filters):
     89        self.root = root
     90        self.filters = filters
     91
     92    def resolve(self, context):
     93        try:
     94            obj = self.root.resolve(context)
     95        except LookupError:
     96            if not self.filters:
     97                raise
     98            obj = settings.TEMPLATE_STRING_IF_INVALID
     99        for func, args in self.filters:
     100            arg_vals = []         
     101            for arg in args:
     102                arg_vals.append(arg.resolve(context))
     103            if getattr(func, 'needs_autoescape', False):
     104                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
     105            else:
     106                new_obj = func(obj, *arg_vals)
     107            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     108                obj = mark_safe(new_obj)
     109            elif isinstance(obj, EscapeData):
     110                obj = mark_for_escaping(new_obj)
     111            else:
     112                obj = new_obj
     113        return obj
     114
     115    def __str__(self):
     116        return str(self.root)+'|<filtered>'
  • django/template/defaulttags.py

     
    88except NameError:
    99    from django.utils.itercompat import reversed     # Python 2.3 fallback
    1010
    11 from django.template import Node, NodeList, Template, Context, Variable
    12 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
     11from django.template import Node, NodeList, Template, Context
     12from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError
    1313from django.template import get_library, Library, InvalidTemplateLibrary
     14from django.template.compiler import token_stream_parsed, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
     15from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs
    1416from django.conf import settings
    15 from django.utils.encoding import smart_str, smart_unicode
     17from django.utils.encoding import smart_str, force_unicode
    1618from django.utils.itercompat import groupby
    1719from django.utils.safestring import mark_safe
    1820
     
    3335        else:
    3436            return output
    3537
    36 class CommentNode(Node):
    37     def render(self, context):
    38         return ''
     38class CommentNode(EmptyNode): pass
    3939
    4040class CycleNode(Node):
    41     def __init__(self, cyclevars, variable_name=None):
    42         self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
     41    def __init__(self, cyclevals, variable_name=None):
     42        self.cycle_iter = itertools_cycle(cyclevals)
    4343        self.variable_name = variable_name
    4444
    4545    def render(self, context):
    46         value = self.cycle_iter.next().resolve(context)
     46        value = self.cycle_iter.next().resolve_safe(context)
    4747        if self.variable_name:
    4848            context[self.variable_name] = value
    4949        return value
     
    6262
    6363    def render(self, context):
    6464        output = self.nodelist.render(context)
    65         # Apply filters.
    6665        context.update({'var': output})
    67         filtered = self.filter_expr.resolve(context)
     66        filtered = self.filter_expr.resolve_safe(context, default='')
    6867        context.pop()
    6968        return filtered
    7069
    7170class FirstOfNode(Node):
    72     def __init__(self, vars):
    73         self.vars = map(Variable, vars)
     71    def __init__(self, vals):
     72        self.vals = vals
    7473
    7574    def render(self, context):
    76         for var in self.vars:
    77             try:
    78                 value = var.resolve(context)
    79             except VariableDoesNotExist:
    80                 continue
     75        for val in self.vals:
     76            value = val.resolve_safe(context)
    8177            if value:
    82                 return smart_unicode(value)
     78                return value
    8379        return u''
    8480
    8581class ForNode(Node):
    86     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
     82    def __init__(self, loopvars, sequence, is_reversed, nodelist):
    8783        self.loopvars, self.sequence = loopvars, sequence
    8884        self.is_reversed = is_reversed
    89         self.nodelist_loop = nodelist_loop
     85        self.nodelist = nodelist
    9086
    9187    def __repr__(self):
    9288        reversed_text = self.is_reversed and ' reversed' or ''
    9389        return "<For Node: for %s in %s, tail_len: %d%s>" % \
    94             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
     90            (', '.join(self.loopvars), self.sequence, len(self.nodelist),
    9591             reversed_text)
    9692
    9793    def __iter__(self):
    98         for node in self.nodelist_loop:
     94        for node in self.nodelist:
    9995            yield node
    10096
    101     def get_nodes_by_type(self, nodetype):
    102         nodes = []
    103         if isinstance(self, nodetype):
    104             nodes.append(self)
    105         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
    106         return nodes
    107 
    10897    def render(self, context):
    109         nodelist = NodeList()
     98        result = []
    11099        if 'forloop' in context:
    111100            parentloop = context['forloop']
    112101        else:
    113102            parentloop = {}
    114103        context.push()
    115         try:
    116             values = self.sequence.resolve(context, True)
    117         except VariableDoesNotExist:
    118             values = []
     104        values = self.sequence.resolve_safe(context, default=[])
    119105        if values is None:
    120106            values = []
    121107        if not hasattr(values, '__len__'):
     
    144130                context.update(dict(zip(self.loopvars, item)))
    145131            else:
    146132                context[self.loopvars[0]] = item
    147             for node in self.nodelist_loop:
    148                 nodelist.append(node.render(context))
     133            for node in self.nodelist:
     134                result.append(node.render(context))
    149135            if unpack:
    150136                # The loop variables were pushed on to the context so pop them
    151137                # off again. This is necessary because the tag lets the length
     
    154140                # context.
    155141                context.pop()
    156142        context.pop()
    157         return nodelist.render(context)
     143        return mark_safe(''.join([force_unicode(b) for b in result]))
    158144
    159145class IfChangedNode(Node):
    160     def __init__(self, nodelist, *varlist):
     146    def __init__(self, nodelist, *vallist):
    161147        self.nodelist = nodelist
    162148        self._last_seen = None
    163         self._varlist = map(Variable, varlist)
     149        self._vallist = vallist
    164150        self._id = str(id(self))
    165151
    166152    def render(self, context):
     
    168154            self._last_seen = None
    169155            context['forloop'][self._id] = 1
    170156        try:
    171             if self._varlist:
     157            if self._vallist:
    172158                # Consider multiple parameters.  This automatically behaves
    173159                # like an OR evaluation of the multiple variables.
    174                 compare_to = [var.resolve(context) for var in self._varlist]
     160                compare_to = [var.resolve(context) for var in self._vallist]
    175161            else:
    176162                compare_to = self.nodelist.render(context)
    177         except VariableDoesNotExist:
     163        except LookupError:
    178164            compare_to = None
    179165
    180166        if  compare_to != self._last_seen:
     
    188174        else:
    189175            return ''
    190176
    191 class IfEqualNode(Node):
    192     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    193         self.var1, self.var2 = Variable(var1), Variable(var2)
    194         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     177
     178class IfEqualNode(ConditionalNode):
     179    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
     180        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
     181        self.val1, self.val2 = val1, val2       
    195182        self.negate = negate
    196183
    197     def __repr__(self):
    198         return "<IfEqualNode>"
     184    def check_condition(self, context):
     185        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
     186        return self.negate == (val1 != val2)
    199187
    200     def render(self, context):
    201         try:
    202             val1 = self.var1.resolve(context)
    203         except VariableDoesNotExist:
    204             val1 = None
    205         try:
    206             val2 = self.var2.resolve(context)
    207         except VariableDoesNotExist:
    208             val2 = None
    209         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    210             return self.nodelist_true.render(context)
    211         return self.nodelist_false.render(context)
    212188
    213 class IfNode(Node):
     189class IfNode(ConditionalNode):
    214190    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
    215         self.bool_exprs = bool_exprs
    216         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     191        super(IfNode, self).__init__(nodelist_true, nodelist_false)
     192        self.bool_exprs = bool_exprs       
    217193        self.link_type = link_type
    218194
    219     def __repr__(self):
    220         return "<If node>"
    221 
    222     def __iter__(self):
    223         for node in self.nodelist_true:
    224             yield node
    225         for node in self.nodelist_false:
    226             yield node
    227 
    228     def get_nodes_by_type(self, nodetype):
    229         nodes = []
    230         if isinstance(self, nodetype):
    231             nodes.append(self)
    232         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    233         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    234         return nodes
    235 
    236     def render(self, context):
    237         if self.link_type == IfNode.LinkTypes.or_:
    238             for ifnot, bool_expr in self.bool_exprs:
    239                 try:
    240                     value = bool_expr.resolve(context, True)
    241                 except VariableDoesNotExist:
    242                     value = None
    243                 if (value and not ifnot) or (ifnot and not value):
    244                     return self.nodelist_true.render(context)
    245             return self.nodelist_false.render(context)
     195    def check_condition(self, context):
     196        if self.link_type == 'or':
     197            for negated, bool_expr in self.bool_exprs:
     198                value = bool_expr.resolve_safe(context, default=False)
     199                if bool(value) != negated:
     200                    return True
     201            return False           
    246202        else:
    247             for ifnot, bool_expr in self.bool_exprs:
    248                 try:
    249                     value = bool_expr.resolve(context, True)
    250                 except VariableDoesNotExist:
    251                     value = None
    252                 if not ((value and not ifnot) or (ifnot and not value)):
    253                     return self.nodelist_false.render(context)
    254             return self.nodelist_true.render(context)
     203            for negated, bool_expr in self.bool_exprs:
     204                value = bool_expr.resolve_safe(context, default=False)
     205                if bool(value) == negated:
     206                    return False
     207            return True
     208       
    255209
    256     class LinkTypes:
    257         and_ = 0,
    258         or_ = 1
    259 
    260210class RegroupNode(Node):
    261211    def __init__(self, target, expression, var_name):
    262212        self.target, self.expression = target, expression
    263213        self.var_name = var_name
    264214
    265215    def render(self, context):
    266         obj_list = self.target.resolve(context, True)
     216        obj_list = self.target.resolve_safe(context)
    267217        if obj_list == None:
    268218            # target variable wasn't found in context; fail silently.
    269219            context[self.var_name] = []
     
    273223        context[self.var_name] = [
    274224            {'grouper': key, 'list': list(val)}
    275225            for key, val in
    276             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
     226            groupby(obj_list, lambda v, f=self.expression.resolve_safe: f(v))
    277227        ]
    278228        return ''
    279229
     
    310260                    return '' # Fail silently for invalid included templates.
    311261        return output
    312262
    313 class LoadNode(Node):
    314     def render(self, context):
    315         return ''
     263class LoadNode(EmptyNode): pass
    316264
    317265class NowNode(Node):
    318266    def __init__(self, format_string):
     
    322270        from datetime import datetime
    323271        from django.utils.dateformat import DateFormat
    324272        df = DateFormat(datetime.now())
    325         return df.format(self.format_string)
     273        return df.format(self.format_string.resolve_safe(context))
    326274
    327275class SpacelessNode(Node):
    328276    def __init__(self, nodelist):
     
    357305
    358306    def render(self, context):
    359307        from django.core.urlresolvers import reverse, NoReverseMatch
    360         args = [arg.resolve(context) for arg in self.args]
    361         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
     308        args = [arg.resolve_safe(context) for arg in self.args]
     309        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context))
    362310                       for k, v in self.kwargs.items()])
    363311        try:
    364312            return reverse(self.view_name, args=args, kwargs=kwargs)
     
    380328        try:
    381329            value = self.val_expr.resolve(context)
    382330            maxvalue = self.max_expr.resolve(context)
    383         except VariableDoesNotExist:
     331        except LookupError:
    384332            return ''
    385333        try:
    386             value = float(value)
    387             maxvalue = float(maxvalue)
    388             ratio = (value / maxvalue) * int(self.max_width)
     334            ratio = (float(value) / float(maxvalue)) * int(self.max_width)
    389335        except (ValueError, ZeroDivisionError):
    390336            return ''
    391337        return str(int(round(ratio)))
    392338
    393339class WithNode(Node):
    394     def __init__(self, var, name, nodelist):
    395         self.var = var
    396         self.name = name
     340    def __init__(self, expr, name, nodelist):
     341        self.expr = expr
     342        self.name = name       
    397343        self.nodelist = nodelist
    398344
    399     def __repr__(self):
    400         return "<WithNode>"
    401 
    402345    def render(self, context):
    403         val = self.var.resolve(context)
    404346        context.push()
    405         context[self.name] = val
     347        context[self.name] = self.expr.resolve_safe(context)
    406348        output = self.nodelist.render(context)
    407349        context.pop()
    408350        return output
     
    418360    arg = args[1]
    419361    if arg not in (u'on', u'off'):
    420362        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    421     nodelist = parser.parse(('endautoescape',))
    422     parser.delete_first_token()
     363    nodelist = parser.parse_nodelist(('endautoescape',))
    423364    return AutoEscapeControlNode((arg == 'on'), nodelist)
    424365autoescape = register.tag(autoescape)
    425366
     
    489430
    490431    if len(args) > 4 and args[-2] == 'as':
    491432        name = args[-1]
    492         node = CycleNode(args[1:-2], name)
     433        values = [parser.compile_filter(arg) for arg in args[1:-2]]
     434        node = CycleNode(values, name)
    493435        if not hasattr(parser, '_namedCycleNodes'):
    494436            parser._namedCycleNodes = {}
    495437        parser._namedCycleNodes[name] = node
    496438    else:
    497         node = CycleNode(args[1:])
     439        values = [parser.compile_filter(arg) for arg in args[1:]]
     440        node = CycleNode(values)
    498441    return node
    499442cycle = register.tag(cycle)
    500443
     
    526469            This text will be HTML-escaped, and will appear in lowercase.
    527470        {% endfilter %}
    528471    """
    529     _, rest = token.contents.split(None, 1)
    530     filter_expr = parser.compile_filter("var|%s" % (rest))
    531     for func, unused in filter_expr.filters:
     472    name, filter_ = token.contents.split(None, 1)       
     473    bits = parser.token_stream("var|%s" % filter_)
     474    try:
     475        filter_expr = bits.parse_expression()
     476    except TokenSyntaxError:
     477        raise TemplateSyntaxError("'%s' requires a valid filter chain, got: '%s'" % name, filter_)
     478    for func, args in filter_expr.filters:
    532479        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    533480            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    534     nodelist = parser.parse(('endfilter',))
    535     parser.delete_first_token()
     481    nodelist = parser.parse_nodelist(('endfilter',))
    536482    return FilterNode(filter_expr, nodelist)
    537483do_filter = register.tag("filter", do_filter)
    538484
    539485#@register.tag
    540 def firstof(parser, token):
     486#@token_stream_parsed
     487def firstof(parser, bits):
    541488    """
    542489    Outputs the first variable passed that is not False.
    543490
     
    565512        {% firstof var1 var2 var3 "fallback value" %}
    566513
    567514    """
    568     bits = token.split_contents()[1:]
    569     if len(bits) < 1:
    570         raise TemplateSyntaxError("'firstof' statement requires at least one"
    571                                   " argument")
    572     return FirstOfNode(bits)
    573 firstof = register.tag(firstof)
     515    expressions = bits.parse_expression_list(minimum=1)
     516    return FirstOfNode(expressions)
     517firstof = register.tag(token_stream_parsed(firstof))
    574518
    575519#@register.tag(name="for")
    576 def do_for(parser, token):
     520#@token_stream_parsed
     521def do_for(parser, bits):
    577522    """
    578523    Loops over each item in an array.
    579524
     
    612557        ==========================  ================================================
    613558
    614559    """
    615     bits = token.contents.split()
    616     if len(bits) < 4:
    617         raise TemplateSyntaxError("'for' statements should have at least four"
    618                                   " words: %s" % token.contents)
     560    loopvars = []
     561    while True:
     562        var = bits.pop_name()
     563        if not var:
     564            break
     565        loopvars.append(var)
     566        if not bits.pop_lexem(','):
     567            break
     568    if not loopvars:
     569        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
     570       
     571    if not bits.pop_lexem('in'):
     572        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
     573    sequence = bits.parse_expression(required=True)
     574    reversed = bits.pop_lexem('reversed')
     575    nodelist = parser.parse_nodelist(('endfor',))
     576    return ForNode(loopvars, sequence, reversed, nodelist)
     577do_for = register.tag("for", token_stream_parsed(do_for))
    619578
    620     is_reversed = bits[-1] == 'reversed'
    621     in_index = is_reversed and -3 or -2
    622     if bits[in_index] != 'in':
    623         raise TemplateSyntaxError("'for' statements should use the format"
    624                                   " 'for x in y': %s" % token.contents)
     579def do_ifequal(parser, bits, negate):
     580    val1, val2 = bits.parse_expression_list(count=2)
     581    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     582    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    625583
    626     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    627     for var in loopvars:
    628         if not var or ' ' in var:
    629             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    630                                       " %s" % token.contents)
    631 
    632     sequence = parser.compile_filter(bits[in_index+1])
    633     nodelist_loop = parser.parse(('endfor',))
    634     parser.delete_first_token()
    635     return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
    636 do_for = register.tag("for", do_for)
    637 
    638 def do_ifequal(parser, token, negate):
    639     bits = list(token.split_contents())
    640     if len(bits) != 3:
    641         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
    642     end_tag = 'end' + bits[0]
    643     nodelist_true = parser.parse(('else', end_tag))
    644     token = parser.next_token()
    645     if token.contents == 'else':
    646         nodelist_false = parser.parse((end_tag,))
    647         parser.delete_first_token()
    648     else:
    649         nodelist_false = NodeList()
    650     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
    651 
    652584#@register.tag
    653 def ifequal(parser, token):
     585#@token_stream_parsed
     586def ifequal(parser, bits):
    654587    """
    655588    Outputs the contents of the block if the two arguments equal each other.
    656589
     
    666599            ...
    667600        {% endifnotequal %}
    668601    """
    669     return do_ifequal(parser, token, False)
    670 ifequal = register.tag(ifequal)
     602    return do_ifequal(parser, bits, False)
     603ifequal = register.tag(token_stream_parsed(ifequal))
    671604
    672605#@register.tag
    673 def ifnotequal(parser, token):
     606#@token_stream_parsed
     607def ifnotequal(parser, bits):
    674608    """
    675609    Outputs the contents of the block if the two arguments are not equal.
    676610    See ifequal.
    677611    """
    678     return do_ifequal(parser, token, True)
    679 ifnotequal = register.tag(ifnotequal)
     612    return do_ifequal(parser, bits, True)
     613ifnotequal = register.tag(token_stream_parsed(ifnotequal))
    680614
    681615#@register.tag(name="if")
    682 def do_if(parser, token):
     616#@token_stream_parsed
     617def do_if(parser, bits):
    683618    """
    684619    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    685620    (i.e., exists, is not empty, and is not a false boolean value), the
     
    737672            {% endif %}
    738673        {% endif %}
    739674    """
    740     bits = token.contents.split()
    741     del bits[0]
    742     if not bits:
    743         raise TemplateSyntaxError("'if' statement requires at least one argument")
    744     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    745     bitstr = ' '.join(bits)
    746     boolpairs = bitstr.split(' and ')
     675    link_type = None
     676    link = None
    747677    boolvars = []
    748     if len(boolpairs) == 1:
    749         link_type = IfNode.LinkTypes.or_
    750         boolpairs = bitstr.split(' or ')
    751     else:
    752         link_type = IfNode.LinkTypes.and_
    753         if ' or ' in bitstr:
    754             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
    755     for boolpair in boolpairs:
    756         if ' ' in boolpair:
    757             try:
    758                 not_, boolvar = boolpair.split()
    759             except ValueError:
    760                 raise TemplateSyntaxError, "'if' statement improperly formatted"
    761             if not_ != 'not':
    762                 raise TemplateSyntaxError, "Expected 'not' in if statement"
    763             boolvars.append((True, parser.compile_filter(boolvar)))
     678    while True:
     679        negated = False
     680        if bits.pop_lexem('not'):
     681            negated = True
     682        try:
     683            expr = bits.parse_expression()
     684        except TokenSyntaxError:
     685            if link:
     686                raise TemplateSyntaxError("'if' statement improperly formatted")
     687            else:
     688                raise TemplateSyntaxError("'if' statement requires at least one argument")
     689        boolvars.append((negated, expr))
     690        link = bits.pop_name()
     691        if not link:
     692            break
     693        if link_type:
     694            if link_type != link:
     695                raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'")
    764696        else:
    765             boolvars.append((False, parser.compile_filter(boolpair)))
    766     nodelist_true = parser.parse(('else', 'endif'))
    767     token = parser.next_token()
    768     if token.contents == 'else':
    769         nodelist_false = parser.parse(('endif',))
    770         parser.delete_first_token()
    771     else:
    772         nodelist_false = NodeList()
     697            if not link in ('and', 'or'):
     698                raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link)       
     699            link_type = link
     700   
     701    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')   
    773702    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    774 do_if = register.tag("if", do_if)
     703do_if = register.tag("if", token_stream_parsed(do_if))
    775704
    776705#@register.tag
    777 def ifchanged(parser, token):
     706#@token_stream_parsed
     707def ifchanged(parser, bits):
    778708    """
    779709    Checks if a value has changed from the last iteration of a loop.
    780710
     
    802732                {% endifchanged %}
    803733            {% endfor %}
    804734    """
    805     bits = token.contents.split()
    806     nodelist = parser.parse(('endifchanged',))
    807     parser.delete_first_token()
    808     return IfChangedNode(nodelist, *bits[1:])
    809 ifchanged = register.tag(ifchanged)
     735    nodelist = parser.parse_nodelist(('endifchanged',))
     736    values = bits.parse_expression_list()
     737    return IfChangedNode(nodelist, *values)
     738ifchanged = register.tag(token_stream_parsed(ifchanged))
    810739
    811740#@register.tag
    812741def ssi(parser, token):
     
    861790load = register.tag(load)
    862791
    863792#@register.tag
    864 def now(parser, token):
     793#@token_stream_parsed
     794def now(parser, bits):
    865795    """
    866796    Displays the date, formatted according to the given string.
    867797
     
    872802
    873803        It is {% now "jS F Y H:i" %}
    874804    """
    875     bits = token.contents.split('"')
    876     if len(bits) != 3:
    877         raise TemplateSyntaxError, "'now' statement takes one argument"
    878     format_string = bits[1]
     805    format_string = bits.parse_expression(required=True)
    879806    return NowNode(format_string)
    880 now = register.tag(now)
     807now = register.tag(token_stream_parsed(now))
    881808
    882809#@register.tag
    883 def regroup(parser, token):
     810#@token_stream_parsed
     811def regroup(parser, bits):
    884812    """
    885813    Regroups a list of alike objects by a common attribute.
    886814
     
    926854        {% regroup people|dictsort:"gender" by gender as grouped %}
    927855
    928856    """
    929     firstbits = token.contents.split(None, 3)
    930     if len(firstbits) != 4:
    931         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
    932     target = parser.compile_filter(firstbits[1])
    933     if firstbits[2] != 'by':
    934         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    935     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    936     if lastbits_reversed[1][::-1] != 'as':
    937         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    938                                   " be 'as'")
    939 
    940     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    941 
    942     var_name = lastbits_reversed[0][::-1]
     857    target = bits.parse_expression(required=True)   
     858    if not bits.pop_lexem('by'):
     859        raise bits.expected("'by'")
     860    expression = bits.parse_expression(required=True)
     861    try:
     862        var_name = parse_as(bits)
     863    except TokenSyntaxError:
     864        raise bits.expected("as <name>")
    943865    return RegroupNode(target, expression, var_name)
    944 regroup = register.tag(regroup)
     866regroup = register.tag(token_stream_parsed(regroup))
    945867
    946868def spaceless(parser, token):
    947869    """
     
    968890            </strong>
    969891        {% endspaceless %}
    970892    """
    971     nodelist = parser.parse(('endspaceless',))
    972     parser.delete_first_token()
    973     return SpacelessNode(nodelist)
     893    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
    974894spaceless = register.tag(spaceless)
    975895
    976896#@register.tag
     
    1007927    return TemplateTagNode(tag)
    1008928templatetag = register.tag(templatetag)
    1009929
    1010 def url(parser, token):
     930#@register.tag
     931#@token_stream_parsed
     932def url(parser, bits):
    1011933    """
    1012934    Returns an absolute URL matching given view with its parameters.
    1013935
     
    1038960
    1039961    The URL will look like ``/clients/client/123/``.
    1040962    """
    1041     bits = token.contents.split(' ', 2)
    1042     if len(bits) < 2:
    1043         raise TemplateSyntaxError("'%s' takes at least one argument"
    1044                                   " (path to a view)" % bits[0])
    1045     args = []
    1046     kwargs = {}
    1047     if len(bits) > 2:
    1048         for arg in bits[2].split(','):
    1049             if '=' in arg:
    1050                 k, v = arg.split('=', 1)
    1051                 k = k.strip()
    1052                 kwargs[k] = parser.compile_filter(v)
    1053             else:
    1054                 args.append(parser.compile_filter(arg))
    1055     return URLNode(bits[1], args, kwargs)
    1056 url = register.tag(url)
     963    view = bits.parse_string(bare=True, required=True)
     964    args, kwargs = parse_args_and_kwargs(bits)
     965    return URLNode(view, args, kwargs)
     966url = register.tag(token_stream_parsed(url))
    1057967
    1058968#@register.tag
    1059 def widthratio(parser, token):
     969#@token_stream_parsed
     970def widthratio(parser, bits):
    1060971    """
    1061972    For creating bar charts and such, this tag calculates the ratio of a given
    1062973    value to a maximum value, and then applies that ratio to a constant.
     
    1069980    the above example will be 88 pixels wide (because 175/200 = .875;
    1070981    .875 * 100 = 87.5 which is rounded up to 88).
    1071982    """
    1072     bits = token.contents.split()
    1073     if len(bits) != 4:
    1074         raise TemplateSyntaxError("widthratio takes three arguments")
    1075     tag, this_value_expr, max_value_expr, max_width = bits
    1076     try:
    1077         max_width = int(max_width)
    1078     except ValueError:
    1079         raise TemplateSyntaxError("widthratio final argument must be an integer")
    1080     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1081                           parser.compile_filter(max_value_expr), max_width)
    1082 widthratio = register.tag(widthratio)
     983    this_value_expr, max_value_expr = bits.parse_expression_list(count=2)
     984    max_width = bits.parse_int(required=True)
     985    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
     986widthratio = register.tag(token_stream_parsed(widthratio))
    1083987
    1084988#@register.tag
    1085 def do_with(parser, token):
     989#@token_stream_parsed
     990def do_with(parser, bits):
    1086991    """
    1087     Adds a value to the context (inside of this block) for caching and easy
     992    Adds values to the context (inside of this block) for caching and easy
    1088993    access.
    1089994
    1090995    For example::
     
    1092997        {% with person.some_sql_method as total %}
    1093998            {{ total }} object{{ total|pluralize }}
    1094999        {% endwith %}
     1000       
     1001        {% with person.some_sql_method as total, person.get_full_name as full_name %}
     1002            {{ full_name }}: {{ total }} object{{ total|pluralize }}
     1003        {% endwith %}
     1004       
    10951005    """
    1096     bits = list(token.split_contents())
    1097     if len(bits) != 4 or bits[2] != "as":
    1098         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1099                                   bits[0])
    1100     var = parser.compile_filter(bits[1])
    1101     name = bits[3]
    1102     nodelist = parser.parse(('endwith',))
    1103     parser.delete_first_token()
    1104     return WithNode(var, name, nodelist)
    1105 do_with = register.tag('with', do_with)
     1006    try:
     1007        expr = bits.parse_expression()
     1008        name = parse_as(bits)
     1009    except TokenSyntaxError:
     1010        raise TemplateSyntaxError("%r expected format is 'value as name'" % bits.name)
     1011    nodelist = parser.parse_nodelist(('endwith',))
     1012    return WithNode(expr, name, nodelist)
     1013do_with = register.tag('with', token_stream_parsed(do_with))
  • django/template/compat.py

     
     1import warnings
     2from django.template.expressions import Expression, LookupError
     3from django.template.compiler import TemplateSyntaxError
     4from django.template.nodes import ExpressionNode
     5
     6VariableDoesNotExist = LookupError
     7VariableNode = ExpressionNode
     8
     9class Variable(Expression):
     10    def __init__(self, var):
     11        warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2)   
     12        self.var = var
     13        from django.template.compiler import TokenStream
     14        stream = TokenStream(None, var)
     15        self.expression = stream.parse_value()
     16        stream.assert_consumed("Invalid variable: %s" % var)
     17
     18    def resolve(self, context):
     19        return self.expression.resolve(context)
     20
     21    def __repr__(self):
     22        return "<%s: %r>" % (self.__class__.__name__, self.var)
     23
     24    def __str__(self):
     25        return self.var
     26       
     27
     28def resolve_variable(path, context):
     29    """
     30    Returns the resolved variable, which may contain attribute syntax, within
     31    the given context.
     32
     33    Deprecated.
     34    """
     35    warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2)
     36    from django.template.compiler import TokenStream
     37    stream = TokenStream(None, path)
     38    val = stream.parse_value()
     39    stream.assert_consumed("Invalid variable: %s" % path)   
     40    return val.resolve(context)
     41
     42
     43class TokenParser(object):
     44    """
     45    Subclass this and implement the top() method to parse a template line. When
     46    instantiating the parser, pass in the line from the Django template parser.
     47
     48    The parser's "tagname" instance-variable stores the name of the tag that
     49    the filter was called with.
     50    """
     51    def __init__(self, subject):
     52        self.subject = subject
     53        self.pointer = 0
     54        self.backout = []
     55        self.tagname = self.tag()
     56
     57    def top(self):
     58        "Overload this method to do the actual parsing and return the result."
     59        raise NotImplementedError()
     60
     61    def more(self):
     62        "Returns True if there is more stuff in the tag."
     63        return self.pointer < len(self.subject)
     64
     65    def back(self):
     66        "Undoes the last microparser. Use this for lookahead and backtracking."
     67        if not len(self.backout):
     68            raise TemplateSyntaxError("back called without some previous parsing")
     69        self.pointer = self.backout.pop()
     70
     71    def tag(self):
     72        "A microparser that just returns the next tag from the line."
     73        subject = self.subject
     74        i = self.pointer
     75        if i >= len(subject):
     76            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
     77        p = i
     78        while i < len(subject) and subject[i] not in (' ', '\t'):
     79            i += 1
     80        s = subject[p:i]
     81        while i < len(subject) and subject[i] in (' ', '\t'):
     82            i += 1
     83        self.backout.append(self.pointer)
     84        self.pointer = i
     85        return s
     86
     87    def value(self):
     88        "A microparser that parses for a value: some string constant or variable name."
     89        subject = self.subject
     90        i = self.pointer
     91        if i >= len(subject):
     92            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
     93        if subject[i] in ('"', "'"):
     94            p = i
     95            i += 1
     96            while i < len(subject) and subject[i] != subject[p]:
     97                i += 1
     98            if i >= len(subject):
     99                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     100            i += 1
     101            res = subject[p:i]
     102            while i < len(subject) and subject[i] in (' ', '\t'):
     103                i += 1
     104            self.backout.append(self.pointer)
     105            self.pointer = i
     106            return res
     107        else:
     108            p = i
     109            while i < len(subject) and subject[i] not in (' ', '\t'):
     110                if subject[i] in ('"', "'"):
     111                    c = subject[i]
     112                    i += 1
     113                    while i < len(subject) and subject[i] != c:
     114                        i += 1
     115                    if i >= len(subject):
     116                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     117                i += 1
     118            s = subject[p:i]
     119            while i < len(subject) and subject[i] in (' ', '\t'):
     120                i += 1
     121            self.backout.append(self.pointer)
     122            self.pointer = i
     123            return s
  • django/template/loader_tags.py

     
    1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    2 from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     1from django.template import TemplateSyntaxError, Library, Node, TextNode
     2from django.template.compiler import token_stream_parsed
     3from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
     4from django.template.expressions import Literal
    45from django.conf import settings
    56from django.utils.safestring import mark_safe
    67
     
    3940class ExtendsNode(Node):
    4041    must_be_first = True
    4142
    42     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
     43    def __init__(self, nodelist, parent_name, template_dirs=None):
    4344        self.nodelist = nodelist
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
     45        self.parent_name = parent_name
    4546        self.template_dirs = template_dirs
    4647
    4748    def __repr__(self):
    48         if self.parent_name_expr:
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    5049        return '<ExtendsNode: extends "%s">' % self.parent_name
    5150
    5251    def get_parent(self, context):
    53         if self.parent_name_expr:
    54             self.parent_name = self.parent_name_expr.resolve(context)
    55         parent = self.parent_name
     52        parent = self.parent_name.resolve_safe(context)
    5653        if not parent:
    5754            error_msg = "Invalid template name in 'extends' tag: %r." % parent
    58             if self.parent_name_expr:
    59                 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
    6055            raise TemplateSyntaxError, error_msg
    6156        if hasattr(parent, 'render'):
    6257            return parent # parent is a Template object
     
    114109
    115110class IncludeNode(Node):
    116111    def __init__(self, template_name):
    117         self.template_name = Variable(template_name)
     112        self.template_name = template_name
    118113
    119114    def render(self, context):
    120115        try:
    121             template_name = self.template_name.resolve(context)
     116            template_name = self.template_name.resolve_safe(context)
    122117            t = get_template(template_name)
    123118            return t.render(context)
    124119        except TemplateSyntaxError, e:
     
    128123        except:
    129124            return '' # Fail silently for invalid included templates.
    130125
     126
     127#@register.tag('block')
    131128def do_block(parser, token):
    132129    """
    133130    Define a block that can be overridden by child templates.
     
    147144    nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
    148145    parser.delete_first_token()
    149146    return BlockNode(block_name, nodelist)
     147register.tag('block', do_block)
    150148
    151 def do_extends(parser, token):
     149
     150#@register.tag('extends')
     151#@token_stream_parsed
     152def do_extends(parser, bits):
    152153    """
    153154    Signal that this template extends a parent template.
    154155
    155156    This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
    156157    uses the literal value "base" as the name of the parent template to extend,
    157     or ``{% extends variable %}`` uses the value of ``variable`` as either the
     158    or ``{% extends expression %}`` uses the value of ``expression`` as either the
    158159    name of the parent template to extend (if it evaluates to a string) or as
    159160    the parent tempate itelf (if it evaluates to a Template object).
    160161    """
    161     bits = token.contents.split()
    162     if len(bits) != 2:
    163         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
    164     parent_name, parent_name_expr = None, None
    165     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
    166         parent_name = bits[1][1:-1]
    167     else:
    168         parent_name_expr = parser.compile_filter(bits[1])
     162    parent_name = bits.parse_expression(required=True)
    169163    nodelist = parser.parse()
    170164    if nodelist.get_nodes_by_type(ExtendsNode):
    171         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
    172     return ExtendsNode(nodelist, parent_name, parent_name_expr)
     165        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits.name
     166    return ExtendsNode(nodelist, parent_name)
     167register.tag('extends', token_stream_parsed(do_extends))
    173168
    174 def do_include(parser, token):
     169
     170#@register.tag('include')
     171#@token_stream_parsed
     172def do_include(parser, bits):
    175173    """
    176174    Loads a template and renders it with the current context.
    177175
     
    179177
    180178        {% include "foo/some_include" %}
    181179    """
    182     bits = token.contents.split()
    183     if len(bits) != 2:
    184         raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
    185     path = bits[1]
    186     if path[0] in ('"', "'") and path[-1] == path[0]:
    187         return ConstantIncludeNode(path[1:-1])
    188     return IncludeNode(bits[1])
    189 
    190 register.tag('block', do_block)
    191 register.tag('extends', do_extends)
    192 register.tag('include', do_include)
     180    template_name = bits.parse_expression(required=True)
     181    if isinstance(template_name, Literal):
     182        # remove ConstantIncludeNode and this hack will be gone
     183        return ConstantIncludeNode(template_name.resolve(None))
     184    return IncludeNode(template_name)
     185register.tag('include', token_stream_parsed(do_include))
  • django/template/library.py

     
     1import re
     2from inspect import getargspec
     3from django.conf import settings
     4from django.template.context import Context
     5from django.template.nodes import Node
     6from django.template.compiler import TemplateSyntaxError
     7from django.utils.itercompat import is_iterable
     8from django.utils.functional import curry
     9
     10__all__ = ('InvalidTemplateLibrary', 'Library', 'get_library', 'add_to_builtins')
     11
     12# global dictionary of libraries that have been loaded using get_library
     13libraries = {}
     14# global list of libraries to load by default for a new parser
     15builtins = []
     16
     17class InvalidTemplateLibrary(Exception):
     18    pass
     19
     20def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False):
     21    "Returns a template.Node subclass."
     22    bits = parser.token_stream(token)
     23    bmax = len(params)
     24    def_len = defaults and len(defaults) or 0
     25    bmin = bmax - def_len
     26    args = stream.parse_expression_list(minimum=bmin, maximum=bmax)
     27    stream.assert_comsumed()
     28    if takes_context:
     29        node_class = curry(node_class, takes_context=takes_context)
     30    if takes_nodelist:
     31        nodelist = parser.parse_nodelist(('end%s' % name,))
     32        node_class = curry(node_class, nodelist=nodelist)     
     33    return node_class(args)
     34
     35class Library(object):
     36    def __init__(self):
     37        self.filters = {}
     38        self.tags = {}
     39
     40    def tag(self, name=None, compile_function=None):
     41        if name == None and compile_function == None:
     42            # @register.tag()
     43            return self.tag_function
     44        elif name != None and compile_function == None:
     45            if(callable(name)):
     46                # @register.tag
     47                return self.tag_function(name)
     48            else:
     49                # @register.tag('somename') or @register.tag(name='somename')
     50                def dec(func):
     51                    return self.tag(name, func)
     52                return dec
     53        elif name != None and compile_function != None:
     54            # register.tag('somename', somefunc)
     55            self.tags[name] = compile_function
     56            return compile_function
     57        else:
     58            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
     59
     60    def tag_function(self,func):
     61        self.tags[getattr(func, "_decorated_function", func).__name__] = func
     62        return func
     63
     64    def filter(self, name=None, filter_func=None):
     65        if name == None and filter_func == None:
     66            # @register.filter()
     67            return self.filter_function
     68        elif filter_func == None:
     69            if(callable(name)):
     70                # @register.filter
     71                return self.filter_function(name)
     72            else:
     73                # @register.filter('somename') or @register.filter(name='somename')
     74                def dec(func):
     75                    return self.filter(name, func)
     76                return dec
     77        elif name != None and filter_func != None:
     78            # register.filter('somename', somefunc)
     79            self.filters[name] = filter_func
     80            return filter_func
     81        else:
     82            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
     83
     84    def filter_function(self, func):
     85        self.filters[getattr(func, "_decorated_function", func).__name__] = func
     86        return func
     87
     88    def simple_tag(self, compile_function=None):
     89        def dec(func):
     90            params, xx, xxx, defaults = getargspec(func)
     91
     92            class SimpleNode(Node):
     93                def __init__(self, args):
     94                    self.args = args
     95
     96                def render(self, context):
     97                    return func(*[arg.resolve_safe(context) for arg in self.args])
     98
     99            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
     100            compile_func.__doc__ = func.__doc__
     101            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     102            return func
     103       
     104        if callable(compile_function):
     105            # @register.simple_tag
     106            return dec(compile_function)
     107        return dec
     108
     109    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
     110        def dec(func):
     111            params, xx, xxx, defaults = getargspec(func)
     112            if takes_context:
     113                if params[0] == 'context':
     114                    params = params[1:]
     115                else:
     116                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     117
     118            class InclusionNode(Node):
     119                def __init__(self, args):
     120                    self.args = args
     121
     122                def render(self, context):
     123                    resolved_vars = [arg.resolve_safe(context) for arg in self.args]
     124                    if takes_context:
     125                        args = [context] + resolved_vars
     126                    else:
     127                        args = resolved_vars
     128
     129                    dict = func(*args)
     130
     131                    if not getattr(self, 'nodelist', False):
     132                        from django.template.loader import get_template, select_template
     133                        if not isinstance(file_name, basestring) and is_iterable(file_name):
     134                            t = select_template(file_name)
     135                        else:
     136                            t = get_template(file_name)
     137                        self.nodelist = t.nodelist
     138                    return self.nodelist.render(context_class(dict, autoescape=context.autoescape))
     139
     140            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
     141            compile_func.__doc__ = func.__doc__
     142            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     143            return func
     144        return dec
     145
     146def get_library(module_name):
     147    lib = libraries.get(module_name, None)
     148    if not lib:
     149        try:
     150            mod = __import__(module_name, {}, {}, [''])
     151        except ImportError, e:
     152            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
     153        try:
     154            lib = mod.register
     155            libraries[module_name] = lib
     156        except AttributeError:
     157            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
     158    return lib
     159
     160def add_to_builtins(module_name):
     161    builtins.append(get_library(module_name))
  • django/template/debug.py

     
    1 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
     1from django.template.compiler import Lexer, Parser, tag_re
     2from django.template import NodeList, ExpressionNode, TemplateSyntaxError
    23from django.utils.encoding import force_unicode
    34from django.utils.html import escape
    45from django.utils.safestring import SafeData, EscapeData
     
    5051        return DebugNodeList()
    5152
    5253    def create_variable_node(self, contents):
    53         return DebugVariableNode(contents)
     54        return DebugExpressionNode(contents)
    5455
    5556    def extend_nodelist(self, nodelist, node, token):
    5657        node.source = token.source
     
    8182            raise wrapped
    8283        return result
    8384
    84 class DebugVariableNode(VariableNode):
     85class DebugExpressionNode(ExpressionNode):
    8586    def render(self, context):
    8687        try:
    87             output = force_unicode(self.filter_expression.resolve(context))
     88            return super(DebugExpressionNode, self).render(context)
    8889        except TemplateSyntaxError, e:
    8990            if not hasattr(e, 'source'):
    9091                e.source = self.source
    9192            raise
    92         except UnicodeDecodeError:
    93             return ''
    94         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
    95             return escape(output)
    96         else:
    97             return output
  • django/template/loader.py

     
    2121# installed, because pkg_resources is necessary to read eggs.
    2222
    2323from django.core.exceptions import ImproperlyConfigured
    24 from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
     24from django.template import Origin, Template, Context, add_to_builtins
    2525from django.conf import settings
    2626
    2727template_source_loaders = None
    2828
     29class TemplateDoesNotExist(Exception):
     30    pass
     31
    2932class LoaderOrigin(Origin):
    3033    def __init__(self, display_name, loader, name, dirs):
    3134        super(LoaderOrigin, self).__init__(display_name)
  • django/templatetags/i18n.py

     
    11import re
    22
    3 from django.template import Node, Variable, VariableNode
    4 from django.template import TemplateSyntaxError, TokenParser, Library
    5 from django.template import TOKEN_TEXT, TOKEN_VAR
     3from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library
     4from django.template.compiler import token_stream_parsed, TOKEN_TEXT, TOKEN_VAR
    65from django.utils import translation
    76from django.utils.encoding import force_unicode
    87
     
    3534
    3635class TranslateNode(Node):
    3736    def __init__(self, value, noop):
    38         self.value = Variable(value)
     37        self.value = value
    3938        self.noop = noop
    4039
    4140    def render(self, context):
     
    141140        raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
    142141    return GetCurrentLanguageBidiNode(args[2])
    143142
    144 def do_translate(parser, token):
     143
     144#@token_stream_parsed
     145def do_translate(parser, bits):
    145146    """
    146147    This will mark a string for translation and will
    147148    translate the string for the current language.
     
    171172    the variable ``variable``. Make sure that the string
    172173    in there is something that is in the .po file.
    173174    """
    174     class TranslateParser(TokenParser):
    175         def top(self):
    176             value = self.value()
    177             if self.more():
    178                 if self.tag() == 'noop':
    179                     noop = True
    180                 else:
    181                     raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
    182             else:
    183                 noop = False
    184             return (value, noop)
    185     value, noop = TranslateParser(token.contents).top()
     175    value = bits.parse_expression(required=True)
     176    noop = bits.pop_lexem('noop')
    186177    return TranslateNode(value, noop)
    187178
    188179def do_block_translate(parser, token):
     
    255246register.tag('get_available_languages', do_get_available_languages)
    256247register.tag('get_current_language', do_get_current_language)
    257248register.tag('get_current_language_bidi', do_get_current_language_bidi)
    258 register.tag('trans', do_translate)
     249register.tag('trans', token_stream_parsed(do_translate))
    259250register.tag('blocktrans', do_block_translate)
  • django/templatetags/cache.py

     
    1 from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
    2 from django.template import resolve_variable
     1from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError
     2from django.template.compiler import token_stream_parsed
    33from django.core.cache import cache
    44from django.utils.encoding import force_unicode
    55
     
    88class CacheNode(Node):
    99    def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
    1010        self.nodelist = nodelist
    11         self.expire_time_var = Variable(expire_time_var)
     11        self.expire_time_var = expire_time_var
    1212        self.fragment_name = fragment_name
    1313        self.vary_on = vary_on
    1414
     
    2222        except (ValueError, TypeError):
    2323            raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
    2424        # Build a unicode key for this fragment and all vary-on's.
    25         cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on])
     25        cache_key = u':'.join([self.fragment_name] + [force_unicode(var.resolve_safe(context)) for var in self.vary_on])
    2626        value = cache.get(cache_key)
    2727        if value is None:
    2828            value = self.nodelist.render(context)
    2929            cache.set(cache_key, value, expire_time)
    3030        return value
    3131
    32 def do_cache(parser, token):
     32def do_cache(parser, bits):
    3333    """
    3434    This will cache the contents of a template fragment for a given amount
    3535    of time.
     
    5050
    5151    Each unique set of arguments will result in a unique cache entry.
    5252    """
    53     nodelist = parser.parse(('endcache',))
    54     parser.delete_first_token()
    55     tokens = token.contents.split()
    56     if len(tokens) < 3:
    57         raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
    58     return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
     53    nodelist = parser.parse_nodelist(('endcache',))
     54    expire_time = bits.parse_expression(required=True)
     55    name = bits.pop_name()
     56    if not name:
     57        raise TemplateSyntaxError, "'cache' requires a fragment name"   
     58    vary_on = bits.parse_expression_list()
     59    return CacheNode(nodelist, expire_time, name, vary_on)
    5960
    60 register.tag('cache', do_cache)
     61register.tag('cache', token_stream_parsed(do_cache))
Back to Top