Django

Code

Ticket #7806: tplrf-r8970.diff

File tplrf-r8970.diff, 129.2 kB (added by emulbreh, 2 years ago)

with tests

  • django/templatetags/i18n.py

    old new  
    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

    old new  
    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

    old new  
     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

    old new  
     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

    old new  
    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

    old new  
     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

    old new  
     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

    old new  
    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 = va
    412         self.name = name 
     355    def __init__(self, expr, name, nodelist): 
     356        self.expr = exp
     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

    old new  
     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

    old new  
    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

    old new  
     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

    old new  
    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

    old new  
    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

    old new  
    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),