Ticket #7806: tplrf-r8970.diff

File tplrf-r8970.diff, 129.2 KB (added by Johannes Dollinger, 16 years ago)

with tests

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

     
     1import re
     2from django.template import Node, NodeList, TokenSyntaxError, TemplateSyntaxError
     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        elif self.nodelist_false:
     33            return self.nodelist_false.render(context)
     34        return u''
     35
     36def parse_conditional_nodelists(parser, name):
     37    end_tag = 'end' + name
     38    nodelist_true = parser.parse(('else', end_tag))
     39    token = parser.next_token()
     40    if token.contents == 'else':
     41        nodelist_false = parser.parse((end_tag,))
     42        parser.delete_first_token()
     43    else:
     44        nodelist_false = NodeList()
     45    return nodelist_true, nodelist_false
     46
     47def parse_args_and_kwargs(bits, until=()):
     48    args = []
     49    kwargs = {}
     50    while True:
     51        name = bits.pop_name()
     52        if name in until:
     53            bits.pushback()
     54            break
     55        if name and bits.pop_lexem('='):
     56            kwargs[name] = bits.parse_expression(required=True)
     57        else:
     58            if name:
     59                bits.pushback()
     60            try:
     61                args.append(bits.parse_expression())
     62            except TokenSyntaxError:
     63                break
     64        if not bits.pop_lexem(','):
     65            break
     66    return args, kwargs
     67
     68def parse_as(bits):
     69    if bits.pop_lexem('as'):
     70        name = bits.pop_name()
     71        if name:
     72            return name
     73    raise TokenSyntaxError
     74
     75def resolve_args_and_kwargs(args, kwargs, context):
     76        resolved_args = [arg.resolve(context, True) for arg in args]
     77        resolved_kwargs = {}
     78        for name in kwargs:
     79                resolved_kwargs[name] = kwargs[name].resolve(context, True)
     80        return resolved_args, resolved_kwargs
  • 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
     7class LookupError(Exception):
     8    def __init__(self, var, msg, params=()):
     9        self.var = var
     10        self.msg = msg
     11        self.params = params
     12
     13    def __str__(self):
     14        return unicode(self).encode('utf-8')
     15
     16    def __unicode__(self):
     17        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
     18
     19
     20class Expression(object):
     21    def resolve_safe(self, context, default=None):
     22        try:
     23            return self.resolve(context)
     24        except LookupError:
     25            return default
     26
     27    def resolve(self, context):
     28        pass
     29
     30class Literal(Expression):
     31    def __init__(self, value):
     32        self.value = value
     33
     34    def resolve(self, context):
     35        return self.value
     36       
     37class Lookup(Expression):
     38    def __init__(self, lookups, var=None):
     39        self.var = var
     40        self.lookups = lookups
     41
     42    def __str__(self):
     43        return "%s" % self.var
     44       
     45    def resolve(self, context):
     46        current = context
     47        for bit in self.lookups:
     48            try: # dictionary lookup
     49                current = current[bit]
     50            except (TypeError, AttributeError, KeyError):
     51                try: # attribute lookup
     52                    current = getattr(current, bit)
     53                    if callable(current):
     54                        if getattr(current, 'alters_data', False):
     55                            current = settings.TEMPLATE_STRING_IF_INVALID
     56                        else:
     57                            try: # method call (assuming no args required)
     58                                current = current()
     59                            except TypeError: # arguments *were* required
     60                                # GOTCHA: This will also catch any TypeError
     61                                # raised in the function itself.
     62                                current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     63                            except Exception, e:
     64                                if getattr(e, 'silent_variable_failure', False):
     65                                    current = settings.TEMPLATE_STRING_IF_INVALID
     66                                else:
     67                                    raise
     68                except (TypeError, AttributeError):
     69                    try: # list-index lookup
     70                        current = current[int(bit)]
     71                    except (IndexError, # list index out of range
     72                            ValueError, # invalid literal for int()
     73                            KeyError,   # current is a dict without `int(bit)` key
     74                            TypeError,  # unsubscriptable object
     75                            ):
     76                        raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
     77                except Exception, e:
     78                    if getattr(e, 'silent_variable_failure', False):
     79                        current = settings.TEMPLATE_STRING_IF_INVALID
     80                    else:
     81                        raise
     82
     83        return current
     84
     85class FilterExpression(Expression):
     86    def __init__(self, root, filters):
     87        self.root = root
     88        self.filters = filters
     89
     90    def resolve(self, context):
     91        try:
     92            obj = self.root.resolve(context)
     93        except LookupError:
     94            if not self.filters:
     95                raise
     96            obj = settings.TEMPLATE_STRING_IF_INVALID
     97        for func, args in self.filters:
     98            arg_vals = []         
     99            for arg in args:
     100                arg_vals.append(arg.resolve(context))
     101            if getattr(func, 'needs_autoescape', False):
     102                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
     103            else:
     104                new_obj = func(obj, *arg_vals)
     105            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     106                obj = mark_safe(new_obj)
     107            elif isinstance(obj, EscapeData):
     108                obj = mark_for_escaping(new_obj)
     109            else:
     110                obj = new_obj
     111        return obj
     112
     113    def __str__(self):
     114        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 uses_token_stream, 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
    159 class IfChangedNode(Node):
    160     def __init__(self, nodelist_true, nodelist_false, *varlist):
    161         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    162         self._last_seen = None
    163         self._varlist = map(Variable, varlist)
    164         self._id = str(id(self))
     145class IfChangedNode(ConditionalNode):
     146    def __init__(self, nodelist_true, nodelist_false, vallist):
     147        super(IfChangedNode, self).__init__(nodelist_true, nodelist_false)
     148        self.last_seen = None
     149        self.vallist = vallist
     150        self.id = str(id(self))
    165151
    166152    def render(self, context):
    167         if 'forloop' in context and self._id not in context['forloop']:
    168             self._last_seen = None
    169             context['forloop'][self._id] = 1
     153        if 'forloop' in context and self.id not in context['forloop']:
     154            self.last_seen = None
     155            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 = [expr.resolve(context) for expr in self.vallist]
    175161            else:
    176162                compare_to = self.nodelist_true.render(context)
    177         except VariableDoesNotExist:
     163        except LookupError:
    178164            compare_to = None
    179165
    180         if compare_to != self._last_seen:
    181             firstloop = (self._last_seen == None)
    182             self._last_seen = compare_to
     166        if  compare_to != self.last_seen:
     167            firstloop = (self.last_seen == None)
     168            self.last_seen = compare_to
    183169            context.push()
    184170            context['ifchanged'] = {'firstloop': firstloop}
    185171            content = self.nodelist_true.render(context)
     
    187173            return content
    188174        elif self.nodelist_false:
    189175            return self.nodelist_false.render(context)
    190         return ''
     176        return u''
    191177
    192 class IfEqualNode(Node):
    193     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    194         self.var1, self.var2 = Variable(var1), Variable(var2)
    195         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     178
     179class IfEqualNode(ConditionalNode):
     180    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
     181        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
     182        self.val1, self.val2 = val1, val2
    196183        self.negate = negate
    197184
    198     def __repr__(self):
    199         return "<IfEqualNode>"
     185    def check_condition(self, context):
     186        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
     187        return self.negate == (val1 != val2)
    200188
    201     def render(self, context):
    202         try:
    203             val1 = self.var1.resolve(context)
    204         except VariableDoesNotExist:
    205             val1 = None
    206         try:
    207             val2 = self.var2.resolve(context)
    208         except VariableDoesNotExist:
    209             val2 = None
    210         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    211             return self.nodelist_true.render(context)
    212         return self.nodelist_false.render(context)
    213189
    214 class IfNode(Node):
     190class IfNode(ConditionalNode):
    215191    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
    216         self.bool_exprs = bool_exprs
    217         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    218         self.link_type = link_type
     192        super(IfNode, self).__init__(nodelist_true, nodelist_false)
     193        self.bool_exprs, self.link_type = bool_exprs, link_type
    219194
    220     def __repr__(self):
    221         return "<If node>"
    222 
    223     def __iter__(self):
    224         for node in self.nodelist_true:
    225             yield node
    226         for node in self.nodelist_false:
    227             yield node
    228 
    229     def get_nodes_by_type(self, nodetype):
    230         nodes = []
    231         if isinstance(self, nodetype):
    232             nodes.append(self)
    233         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    234         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    235         return nodes
    236 
    237     def render(self, context):
    238         if self.link_type == IfNode.LinkTypes.or_:
    239             for ifnot, bool_expr in self.bool_exprs:
    240                 try:
    241                     value = bool_expr.resolve(context, True)
    242                 except VariableDoesNotExist:
    243                     value = None
    244                 if (value and not ifnot) or (ifnot and not value):
    245                     return self.nodelist_true.render(context)
    246             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
    247202        else:
    248             for ifnot, bool_expr in self.bool_exprs:
    249                 try:
    250                     value = bool_expr.resolve(context, True)
    251                 except VariableDoesNotExist:
    252                     value = None
    253                 if not ((value and not ifnot) or (ifnot and not value)):
    254                     return self.nodelist_false.render(context)
    255             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       
    256209
    257     class LinkTypes:
    258         and_ = 0,
    259         or_ = 1
    260 
    261210class RegroupNode(Node):
    262211    def __init__(self, target, expression, var_name):
    263212        self.target, self.expression = target, expression
    264213        self.var_name = var_name
    265214
    266215    def render(self, context):
    267         obj_list = self.target.resolve(context, True)
     216        obj_list = self.target.resolve_safe(context)
    268217        if obj_list == None:
    269218            # target variable wasn't found in context; fail silently.
    270219            context[self.var_name] = []
    271             return ''
     220            return u''
    272221        # List of dictionaries in the format:
    273222        # {'grouper': 'key', 'list': [list of contents]}.
    274223        context[self.var_name] = [
    275224            {'grouper': key, 'list': list(val)}
    276225            for key, val in
    277             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))
    278227        ]
    279         return ''
     228        return u''
    280229
    281230def include_is_allowed(filepath):
    282231    for root in settings.ALLOWED_INCLUDE_ROOTS:
     
    293242            if settings.DEBUG:
    294243                return "[Didn't have permission to include file]"
    295244            else:
    296                 return '' # Fail silently for invalid includes.
     245                return u'' # Fail silently for invalid includes.
    297246        try:
    298247            fp = open(self.filepath, 'r')
    299248            output = fp.read()
     
    308257                if settings.DEBUG:
    309258                    return "[Included template had syntax error: %s]" % e
    310259                else:
    311                     return '' # Fail silently for invalid included templates.
     260                    return u'' # Fail silently for invalid included templates.
    312261        return output
    313262
    314 class LoadNode(Node):
    315     def render(self, context):
    316         return ''
     263class LoadNode(EmptyNode): pass
    317264
    318265class NowNode(Node):
    319266    def __init__(self, format_string):
     
    323270        from datetime import datetime
    324271        from django.utils.dateformat import DateFormat
    325272        df = DateFormat(datetime.now())
    326         return df.format(self.format_string)
     273        return df.format(self.format_string.resolve_safe(context))
    327274
    328275class SpacelessNode(Node):
    329276    def __init__(self, nodelist):
     
    359306
    360307    def render(self, context):
    361308        from django.core.urlresolvers import reverse, NoReverseMatch
    362         args = [arg.resolve(context) for arg in self.args]
    363         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
     309        args = [arg.resolve_safe(context) for arg in self.args]
     310        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context))
    364311                       for k, v in self.kwargs.items()])
    365312       
    366313       
     
    382329                   
    383330        if self.asvar:
    384331            context[self.asvar] = url
    385             return ''
     332            return u''
    386333        else:
    387334            return url
    388335
     
    396343        try:
    397344            value = self.val_expr.resolve(context)
    398345            maxvalue = self.max_expr.resolve(context)
    399         except VariableDoesNotExist:
    400             return ''
     346        except LookupError:
     347            return u''
    401348        try:
    402             value = float(value)
    403             maxvalue = float(maxvalue)
    404             ratio = (value / maxvalue) * int(self.max_width)
     349            ratio = (float(value) / float(maxvalue)) * int(self.max_width)
    405350        except (ValueError, ZeroDivisionError):
    406             return ''
    407         return str(int(round(ratio)))
     351            return u''
     352        return unicode(int(round(ratio)))
    408353
    409354class WithNode(Node):
    410     def __init__(self, var, name, nodelist):
    411         self.var = var
    412         self.name = name
     355    def __init__(self, expr, name, nodelist):
     356        self.expr = expr
     357        self.name = name       
    413358        self.nodelist = nodelist
    414359
    415     def __repr__(self):
    416         return "<WithNode>"
    417 
    418360    def render(self, context):
    419         val = self.var.resolve(context)
    420361        context.push()
    421         context[self.name] = val
     362        context[self.name] = self.expr.resolve_safe(context)
    422363        output = self.nodelist.render(context)
    423364        context.pop()
    424365        return output
     
    434375    arg = args[1]
    435376    if arg not in (u'on', u'off'):
    436377        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    437     nodelist = parser.parse(('endautoescape',))
    438     parser.delete_first_token()
     378    nodelist = parser.parse_nodelist(('endautoescape',))
    439379    return AutoEscapeControlNode((arg == 'on'), nodelist)
    440380autoescape = register.tag(autoescape)
    441381
     
    493433        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
    494434        # case.
    495435        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
    496 
     436   
    497437    if len(args) == 2:
    498438        # {% cycle foo %} case.
    499439        name = args[1]
     
    505445
    506446    if len(args) > 4 and args[-2] == 'as':
    507447        name = args[-1]
    508         node = CycleNode(args[1:-2], name)
     448        values = [parser.compile_filter(arg) for arg in args[1:-2]]
     449        node = CycleNode(values, name)
    509450        if not hasattr(parser, '_namedCycleNodes'):
    510451            parser._namedCycleNodes = {}
    511452        parser._namedCycleNodes[name] = node
    512453    else:
    513         node = CycleNode(args[1:])
     454        values = [parser.compile_filter(arg) for arg in args[1:]]
     455        node = CycleNode(values)
    514456    return node
    515457cycle = register.tag(cycle)
    516458
     
    542484            This text will be HTML-escaped, and will appear in lowercase.
    543485        {% endfilter %}
    544486    """
    545     _, rest = token.contents.split(None, 1)
    546     filter_expr = parser.compile_filter("var|%s" % (rest))
    547     for func, unused in filter_expr.filters:
     487    name, filter_ = token.contents.split(None, 1)       
     488    bits = parser.token_stream("var|%s" % filter_)
     489    try:
     490        filter_expr = bits.parse_expression()
     491    except TokenSyntaxError:
     492        raise TemplateSyntaxError("'%s' requires a valid filter chain, got: '%s'" % name, filter_)
     493    for func, args in filter_expr.filters:
    548494        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    549495            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    550     nodelist = parser.parse(('endfilter',))
    551     parser.delete_first_token()
     496    nodelist = parser.parse_nodelist(('endfilter',))
    552497    return FilterNode(filter_expr, nodelist)
    553498do_filter = register.tag("filter", do_filter)
    554499
    555500#@register.tag
    556 def firstof(parser, token):
     501#@uses_token_stream
     502def firstof(parser, bits):
    557503    """
    558504    Outputs the first variable passed that is not False.
    559505
     
    581527        {% firstof var1 var2 var3 "fallback value" %}
    582528
    583529    """
    584     bits = token.split_contents()[1:]
    585     if len(bits) < 1:
    586         raise TemplateSyntaxError("'firstof' statement requires at least one"
    587                                   " argument")
    588     return FirstOfNode(bits)
    589 firstof = register.tag(firstof)
     530    expressions = bits.parse_expression_list(minimum=1)
     531    return FirstOfNode(expressions)
     532firstof = register.tag(uses_token_stream(firstof))
    590533
    591534#@register.tag(name="for")
    592 def do_for(parser, token):
     535#@uses_token_stream
     536def do_for(parser, bits):
    593537    """
    594538    Loops over each item in an array.
    595539
     
    628572        ==========================  ================================================
    629573
    630574    """
    631     bits = token.contents.split()
    632     if len(bits) < 4:
    633         raise TemplateSyntaxError("'for' statements should have at least four"
    634                                   " words: %s" % token.contents)
     575    loopvars = []
     576    while True:
     577        var = bits.pop_name()
     578        if not var:
     579            break
     580        loopvars.append(var)
     581        if not bits.pop_lexem(','):
     582            break
     583    if not loopvars:
     584        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
     585       
     586    if not bits.pop_lexem('in'):
     587        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
     588    sequence = bits.parse_expression(required=True)
     589    reversed = bits.pop_lexem('reversed')
     590    nodelist = parser.parse_nodelist(('endfor',))
     591    return ForNode(loopvars, sequence, reversed, nodelist)
     592do_for = register.tag("for", uses_token_stream(do_for))
    635593
    636     is_reversed = bits[-1] == 'reversed'
    637     in_index = is_reversed and -3 or -2
    638     if bits[in_index] != 'in':
    639         raise TemplateSyntaxError("'for' statements should use the format"
    640                                   " 'for x in y': %s" % token.contents)
     594def do_ifequal(parser, bits, negate):
     595    val1, val2 = bits.parse_expression_list(count=2)
     596    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     597    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    641598
    642     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    643     for var in loopvars:
    644         if not var or ' ' in var:
    645             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    646                                       " %s" % token.contents)
    647 
    648     sequence = parser.compile_filter(bits[in_index+1])
    649     nodelist_loop = parser.parse(('endfor',))
    650     parser.delete_first_token()
    651     return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
    652 do_for = register.tag("for", do_for)
    653 
    654 def do_ifequal(parser, token, negate):
    655     bits = list(token.split_contents())
    656     if len(bits) != 3:
    657         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
    658     end_tag = 'end' + bits[0]
    659     nodelist_true = parser.parse(('else', end_tag))
    660     token = parser.next_token()
    661     if token.contents == 'else':
    662         nodelist_false = parser.parse((end_tag,))
    663         parser.delete_first_token()
    664     else:
    665         nodelist_false = NodeList()
    666     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
    667 
    668599#@register.tag
    669 def ifequal(parser, token):
     600#@uses_token_stream
     601def ifequal(parser, bits):
    670602    """
    671603    Outputs the contents of the block if the two arguments equal each other.
    672604
     
    682614            ...
    683615        {% endifnotequal %}
    684616    """
    685     return do_ifequal(parser, token, False)
    686 ifequal = register.tag(ifequal)
     617    return do_ifequal(parser, bits, False)
     618ifequal = register.tag(uses_token_stream(ifequal))
    687619
    688620#@register.tag
    689 def ifnotequal(parser, token):
     621#@uses_token_stream
     622def ifnotequal(parser, bits):
    690623    """
    691624    Outputs the contents of the block if the two arguments are not equal.
    692625    See ifequal.
    693626    """
    694     return do_ifequal(parser, token, True)
    695 ifnotequal = register.tag(ifnotequal)
     627    return do_ifequal(parser, bits, True)
     628ifnotequal = register.tag(uses_token_stream(ifnotequal))
    696629
    697630#@register.tag(name="if")
    698 def do_if(parser, token):
     631#@uses_token_stream
     632def do_if(parser, bits):
    699633    """
    700634    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    701635    (i.e., exists, is not empty, and is not a false boolean value), the
     
    753687            {% endif %}
    754688        {% endif %}
    755689    """
    756     bits = token.contents.split()
    757     del bits[0]
    758     if not bits:
    759         raise TemplateSyntaxError("'if' statement requires at least one argument")
    760     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    761     bitstr = ' '.join(bits)
    762     boolpairs = bitstr.split(' and ')
     690    link_type = None
     691    link = None
    763692    boolvars = []
    764     if len(boolpairs) == 1:
    765         link_type = IfNode.LinkTypes.or_
    766         boolpairs = bitstr.split(' or ')
    767     else:
    768         link_type = IfNode.LinkTypes.and_
    769         if ' or ' in bitstr:
    770             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
    771     for boolpair in boolpairs:
    772         if ' ' in boolpair:
    773             try:
    774                 not_, boolvar = boolpair.split()
    775             except ValueError:
    776                 raise TemplateSyntaxError, "'if' statement improperly formatted"
    777             if not_ != 'not':
    778                 raise TemplateSyntaxError, "Expected 'not' in if statement"
    779             boolvars.append((True, parser.compile_filter(boolvar)))
     693    while True:
     694        negated = False
     695        if bits.pop_lexem('not'):
     696            negated = True
     697        expr = bits.parse_expression(required=True)
     698        boolvars.append((negated, expr))
     699        link = bits.pop_name()
     700        if not link:
     701            break
     702        if link_type:
     703            if link_type != link:
     704                bits.syntax_error("can't mix 'and' and 'or'")
    780705        else:
    781             boolvars.append((False, parser.compile_filter(boolpair)))
    782     nodelist_true = parser.parse(('else', 'endif'))
    783     token = parser.next_token()
    784     if token.contents == 'else':
    785         nodelist_false = parser.parse(('endif',))
    786         parser.delete_first_token()
    787     else:
    788         nodelist_false = NodeList()
     706            if not link in ('and', 'or'):
     707                bits.pushback()
     708                bits.expected("'and' or 'or'")
     709            link_type = link
     710   
     711    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')   
    789712    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    790 do_if = register.tag("if", do_if)
     713do_if = register.tag("if", uses_token_stream(do_if))
    791714
    792715#@register.tag
    793 def ifchanged(parser, token):
     716#@uses_token_stream
     717def ifchanged(parser, bits):
    794718    """
    795719    Checks if a value has changed from the last iteration of a loop.
    796720
     
    817741                    {{ date.hour }}
    818742                {% endifchanged %}
    819743            {% endfor %}
    820     """
    821     bits = token.contents.split()
    822     nodelist_true = parser.parse(('else', 'endifchanged'))
    823     token = parser.next_token()
    824     if token.contents == 'else':
    825         nodelist_false = parser.parse(('endifchanged',))
    826         parser.delete_first_token()
    827     else:
    828         nodelist_false = NodeList()
    829     return IfChangedNode(nodelist_true, nodelist_false, *bits[1:])
    830 ifchanged = register.tag(ifchanged)
     744    """   
     745    values = bits.parse_expression_list()
     746    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     747    return IfChangedNode(nodelist_true, nodelist_false, values)
     748ifchanged = register.tag(uses_token_stream(ifchanged))
    831749
    832750#@register.tag
    833751def ssi(parser, token):
     
    882800load = register.tag(load)
    883801
    884802#@register.tag
    885 def now(parser, token):
     803#@uses_token_stream
     804def now(parser, bits):
    886805    """
    887806    Displays the date, formatted according to the given string.
    888807
     
    893812
    894813        It is {% now "jS F Y H:i" %}
    895814    """
    896     bits = token.contents.split('"')
    897     if len(bits) != 3:
    898         raise TemplateSyntaxError, "'now' statement takes one argument"
    899     format_string = bits[1]
     815    format_string = bits.parse_expression(required=True)
    900816    return NowNode(format_string)
    901 now = register.tag(now)
     817now = register.tag(uses_token_stream(now))
    902818
    903819#@register.tag
    904 def regroup(parser, token):
     820#@uses_token_stream
     821def regroup(parser, bits):
    905822    """
    906823    Regroups a list of alike objects by a common attribute.
    907824
     
    947864        {% regroup people|dictsort:"gender" by gender as grouped %}
    948865
    949866    """
    950     firstbits = token.contents.split(None, 3)
    951     if len(firstbits) != 4:
    952         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
    953     target = parser.compile_filter(firstbits[1])
    954     if firstbits[2] != 'by':
    955         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    956     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    957     if lastbits_reversed[1][::-1] != 'as':
    958         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    959                                   " be 'as'")
    960 
    961     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    962 
    963     var_name = lastbits_reversed[0][::-1]
     867    target = bits.parse_expression(required=True)   
     868    if not bits.pop_lexem('by'):
     869        raise bits.expected("'by'")
     870    expression = bits.parse_expression(required=True)
     871    try:
     872        var_name = parse_as(bits)
     873    except TokenSyntaxError:
     874        raise bits.expected("as <name>")
    964875    return RegroupNode(target, expression, var_name)
    965 regroup = register.tag(regroup)
     876regroup = register.tag(uses_token_stream(regroup))
    966877
    967878def spaceless(parser, token):
    968879    """
     
    989900            </strong>
    990901        {% endspaceless %}
    991902    """
    992     nodelist = parser.parse(('endspaceless',))
    993     parser.delete_first_token()
    994     return SpacelessNode(nodelist)
     903    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
    995904spaceless = register.tag(spaceless)
    996905
    997906#@register.tag
     
    1028937    return TemplateTagNode(tag)
    1029938templatetag = register.tag(templatetag)
    1030939
    1031 def url(parser, token):
     940#@register.tag
     941#@uses_token_stream
     942def url(parser, bits):
    1032943    """
    1033944    Returns an absolute URL matching given view with its parameters.
    1034945
     
    1059970
    1060971    The URL will look like ``/clients/client/123/``.
    1061972    """
    1062     bits = token.contents.split(' ')
    1063     if len(bits) < 2:
    1064         raise TemplateSyntaxError("'%s' takes at least one argument"
    1065                                   " (path to a view)" % bits[0])
    1066     viewname = bits[1]
    1067     args = []
    1068     kwargs = {}
    1069     asvar = None
    1070        
    1071     if len(bits) > 2:
    1072         bits = iter(bits[2:])
    1073         for bit in bits:
    1074             if bit == 'as':
    1075                 asvar = bits.next()
    1076                 break
    1077             else:
    1078                 for arg in bit.split(","):
    1079                     if '=' in arg:
    1080                         k, v = arg.split('=', 1)
    1081                         k = k.strip()
    1082                         kwargs[k] = parser.compile_filter(v)
    1083                     elif arg:
    1084                         args.append(parser.compile_filter(arg))
    1085     return URLNode(viewname, args, kwargs, asvar)
    1086 url = register.tag(url)
     973    view = bits.parse_string(bare=True, required=True)
     974    args, kwargs = parse_args_and_kwargs(bits, until=('as',))
     975    try:
     976        asvar = parse_as(bits)
     977    except TokenSyntaxError:
     978        asvar = None
     979    return URLNode(view, args, kwargs, asvar)
     980url = register.tag(uses_token_stream(url))
    1087981
    1088982#@register.tag
    1089 def widthratio(parser, token):
     983#@uses_token_stream
     984def widthratio(parser, bits):
    1090985    """
    1091986    For creating bar charts and such, this tag calculates the ratio of a given
    1092987    value to a maximum value, and then applies that ratio to a constant.
     
    1099994    the above example will be 88 pixels wide (because 175/200 = .875;
    1100995    .875 * 100 = 87.5 which is rounded up to 88).
    1101996    """
    1102     bits = token.contents.split()
    1103     if len(bits) != 4:
    1104         raise TemplateSyntaxError("widthratio takes three arguments")
    1105     tag, this_value_expr, max_value_expr, max_width = bits
    1106     try:
    1107         max_width = int(max_width)
    1108     except ValueError:
    1109         raise TemplateSyntaxError("widthratio final argument must be an integer")
    1110     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1111                           parser.compile_filter(max_value_expr), max_width)
    1112 widthratio = register.tag(widthratio)
     997    this_value_expr, max_value_expr = bits.parse_expression_list(count=2)
     998    max_width = bits.parse_int(required=True)
     999    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
     1000widthratio = register.tag(uses_token_stream(widthratio))
    11131001
    11141002#@register.tag
    1115 def do_with(parser, token):
     1003#@uses_token_stream
     1004def do_with(parser, bits):
    11161005    """
    11171006    Adds a value to the context (inside of this block) for caching and easy
    11181007    access.
     
    11221011        {% with person.some_sql_method as total %}
    11231012            {{ total }} object{{ total|pluralize }}
    11241013        {% endwith %}
     1014       
    11251015    """
    1126     bits = list(token.split_contents())
    1127     if len(bits) != 4 or bits[2] != "as":
    1128         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1129                                   bits[0])
    1130     var = parser.compile_filter(bits[1])
    1131     name = bits[3]
    1132     nodelist = parser.parse(('endwith',))
    1133     parser.delete_first_token()
    1134     return WithNode(var, name, nodelist)
    1135 do_with = register.tag('with', do_with)
     1016    try:
     1017        expr = bits.parse_expression()
     1018        name = parse_as(bits)
     1019    except TokenSyntaxError:
     1020        bits.expected("value as name")
     1021    nodelist = parser.parse_nodelist(('endwith',))
     1022    return WithNode(expr, name, nodelist)
     1023do_with = register.tag('with', uses_token_stream(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 uses_token_stream
     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
     
    2829    def super(self):
    2930        if self.parent:
    3031            return mark_safe(self.parent.render(self.context))
    31         return ''
     32        return u''
    3233
    3334    def add_parent(self, nodelist):
    3435        if self.parent:
     
    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:
    125120            if settings.TEMPLATE_DEBUG:
    126121                raise
    127             return ''
     122            return u''
    128123        except:
    129             return '' # Fail silently for invalid included templates.
     124            return u'' # 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.
     
    144141        parser.__loaded_blocks.append(block_name)
    145142    except AttributeError: # parser.__loaded_blocks isn't a list yet
    146143        parser.__loaded_blocks = [block_name]
    147     nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
    148     parser.delete_first_token()
     144    nodelist = parser.parse_nodelist(('endblock', 'endblock %s' % block_name))
    149145    return BlockNode(block_name, nodelist)
     146register.tag('block', do_block)
    150147
    151 def do_extends(parser, token):
     148
     149#@register.tag('extends')
     150#@uses_token_stream
     151def do_extends(parser, bits):
    152152    """
    153153    Signal that this template extends a parent template.
    154154
    155155    This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
    156156    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
     157    or ``{% extends expression %}`` uses the value of ``expression`` as either the
    158158    name of the parent template to extend (if it evaluates to a string) or as
    159159    the parent tempate itelf (if it evaluates to a Template object).
    160160    """
    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])
     161    parent_name = bits.parse_expression(required=True)
    169162    nodelist = parser.parse()
    170163    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)
     164        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits.name
     165    return ExtendsNode(nodelist, parent_name)
     166register.tag('extends', uses_token_stream(do_extends))
    173167
    174 def do_include(parser, token):
     168
     169#@register.tag('include')
     170#@uses_token_stream
     171def do_include(parser, bits):
    175172    """
    176173    Loads a template and renders it with the current context.
    177174
     
    179176
    180177        {% include "foo/some_include" %}
    181178    """
    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)
     179    template_name = bits.parse_expression(required=True)
     180    if isinstance(template_name, Literal):
     181        # remove ConstantIncludeNode and this hack will be gone
     182        return ConstantIncludeNode(template_name.resolve(None))
     183    return IncludeNode(template_name)
     184register.tag('include', uses_token_stream(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
    2 from django.utils.encoding import force_unicode
    3 from django.utils.html import escape
    4 from django.utils.safestring import SafeData, EscapeData
     1from django.template.compiler import Lexer, Parser, Token, TokenStream, tag_re, bit_re
     2from django.template import NodeList, ExpressionNode, TemplateSyntaxError
     3from django.utils.encoding import force_unicode, smart_str
    54
    65class DebugLexer(Lexer):
    76    def __init__(self, template_string, origin):
     
    5049        return DebugNodeList()
    5150
    5251    def create_variable_node(self, contents):
    53         return DebugVariableNode(contents)
     52        return DebugExpressionNode(contents)
    5453
    5554    def extend_nodelist(self, nodelist, node, token):
    5655        node.source = token.source
     
    6564        if not hasattr(e, 'source'):
    6665            e.source = token.source
    6766
     67    def token_stream(self, token):
     68        return DebugTokenStream(self, token)
     69
     70
    6871class DebugNodeList(NodeList):
    6972    def render_node(self, node, context):
    7073        try:
     
    8184            raise wrapped
    8285        return result
    8386
    84 class DebugVariableNode(VariableNode):
     87class DebugExpressionNode(ExpressionNode):
    8588    def render(self, context):
    8689        try:
    87             output = force_unicode(self.filter_expression.resolve(context))
     90            return super(DebugExpressionNode, self).render(context)
    8891        except TemplateSyntaxError, e:
    8992            if not hasattr(e, 'source'):
    9093                e.source = self.source
    9194            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
     95           
     96class DebugTokenStream(TokenStream):
     97    def syntax_error(self, msg):
     98        if self.name:
     99            msg = u"{%% %s %%} %s" % (self.name, msg)           
     100        if self.token:
     101            raise self.parser.source_error(self.token.source, msg)
     102        raise TemplateSyntaxError(msg)
     103           
  • 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)
  • tests/regressiontests/templates/tests.py

     
    132132
    133133    def test_token_smart_split(self):
    134134        # Regression test for #7027
    135         token = template.Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
     135        token = template.compiler.Token(template.compiler.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
    136136        split = token.split_contents()
    137137        self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])
    138138
     
    194194                    output = self.render(test_template, vals)
    195195                except Exception, e:
    196196                    if e.__class__ != result:
    197                         raise
     197                        #raise
    198198                        failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    199199                    continue
    200200                if output != result:
     
    288288            'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
    289289            'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
    290290            'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
     291            'basic-syntax28': ("{{ 'fred' }}", {}, "fred"),
     292            'basic-syntax29': (r"{{ '\'fred\'' }}", {}, "'fred'"),
     293            'basic-syntax30': (r"{{ _('\'fred\'') }}", {}, "'fred'"),
    291294
    292295            # List-index syntax allows a template to access a certain item of a subscriptable object.
    293296            'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
     
    347350            'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
    348351
    349352            # Default argument testing
    350             'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
     353            'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),           
    351354
    352355            # Fail silently for methods that raise an exception with a
    353356            # "silent_variable_failure" attribute
     
    375378           
    376379            #filters should accept empty string constants
    377380            'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""),
     381           
     382            # Single-quoted argument
     383            'filter-syntax20': (r"{{ var|yesno:'yup,nup,mup' }} {{ var|yesno }}", {"var": True}, 'yup yes'),             
    378384
    379385            ### COMMENT SYNTAX ########################################################
    380386            'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
     
    419425            'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'),
    420426            'cycle13': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'),
    421427
     428            ### EMPTY STRINGS #########################################################
     429            'emptystring01': ("{{ '' }}", {}, ""),
     430            'emptystring02': ("{% ifequal foo '' %}x{% endifequal %}", {'foo': ''}, 'x'),
     431            'emptystring03': ("{% ifequal foo|default:'' foo %}x{% endifequal %}", {'foo': ''}, 'x'),
     432
    422433            ### EXCEPTIONS ############################################################
    423434
    424435            # Raise exception for invalid template name
     
    621632            'ifequal-numeric10': ('{% ifequal x -5 %}yes{% endifequal %}', {'x': -5}, 'yes'),
    622633            'ifequal-numeric11': ('{% ifequal x -5.2 %}yes{% endifequal %}', {'x': -5.2}, 'yes'),
    623634            'ifequal-numeric12': ('{% ifequal x +5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
     635           
     636            # FILTER EXPRESSIONS AS ARGUMENTS
     637            'ifequal-filter01': ('{% ifequal a|upper "A" %}x{% endifequal %}', {'a': 'a'}, 'x'),
     638            'ifequal-filter02': ('{% ifequal "A" a|upper %}x{% endifequal %}', {'a': 'a'}, 'x'),
     639            'ifequal-filter03': ('{% ifequal a|upper b|upper %}x{% endifequal %}', {'a': 'x', 'b': 'X'}, 'x'),
     640            'ifequal-filter04': ('{% ifequal x|slice:"1" "a" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),
     641            'ifequal-filter05': ('{% ifequal x|slice:"1"|upper "A" %}x{% endifequal %}', {'x': 'aaa'}, 'x'),             
    624642
    625643            ### IFNOTEQUAL TAG ########################################################
    626644            'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
     
    816834                            you
    817835                            gentlemen.
    818836                            """),
     837                           
     838            ### NEGATIVE NUMERIC LITERALS #############################################                             
     839            'negative-numeric-literal01': ('{{ -1 }}', {}, '-1'),
     840            'negative-numeric-literal02': ('{{ -2.01 }}', {}, '-2.01'),
     841            'negative-numeric-literal03': ('{{ -0.1 }}', {}, '-0.1'),
     842            'negative-numeric-literal04': ('{% ifequal -1 -1 %}x{% endifequal %}', {}, 'x'),
     843            'negative-numeric-literal05': ('{{ foo|default:-1 }}', {'foo': None}, '-1'),                           
    819844
    820845            ### REGROUP TAG ###########################################################
    821846            'regroup01': ('{% regroup data by bar as grouped %}' + \
     
    841866                          '{% endfor %},' + \
    842867                          '{% endfor %}',
    843868                          {}, ''),
     869                         
     870            'regroup03': ('{% regroup data by created|date:"F Y" as grouped %}' + \
     871                          '{% for group in grouped %}' + \
     872                          '{{ group.grouper }}' + \
     873                          '({% for item in group.list %}' + \
     874                          '{{ item.created|date:"d" }}' + \
     875                          '{% endfor %})' + \
     876                          '{% endfor %}',
     877                          {'data': [
     878                                {'created': datetime(2008, 1, 1)},
     879                                {'created': datetime(2008, 2, 2)},
     880                                {'created': datetime(2008, 3, 3)},
     881                                {'created': datetime(2008, 4, 4)},
     882                          ]}, 'January 2008(01)February 2008(02)March 2008(03)April 2008(04)'),
    844883
    845884            ### TEMPLATETAG TAG #######################################################
    846885            'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
     
    902941            'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
    903942            'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
    904943            'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
     944            'url10': (u'{% url "метка_оператора" v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
    905945
    906946            # Failures
    907947            'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
Back to Top