Django

Code

Ticket #7806: tplrf.diff

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

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

    old new  
    99    from django.utils.itercompat import reversed     # Python 2.3 fallback 
    1010 
    1111from 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 
    13 from django.template import get_library, Library, InvalidTemplateLibrary 
     12from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError 
     13from django.template.library import get_library, Library, InvalidTemplateLibrary 
     14from django.template.compiler import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 
     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 '' 
     
    9894        for node in self.nodelist_loop: 
    9995            yield node 
    10096 
    101     def get_nodes_by_type(self, nodetype): 
    102         nodes = [] 
    103         if isinstance(self, nodetype): 
    104             nodes.append(self) 
    105         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) 
    106         return nodes 
    107  
    10897    def render(self, context): 
    109         nodelist = NodeList() 
     98        result = [] 
    11099        if 'forloop' in context: 
    111100            parentloop = context['forloop'] 
    112101        else: 
    113102            parentloop = {} 
    114103        context.push() 
    115         try: 
    116             values = self.sequence.resolve(context, True) 
    117         except VariableDoesNotExist: 
    118             values = [] 
     104        values = self.sequence.resolve_safe(context, default=[]) 
    119105        if values is None: 
    120106            values = [] 
    121107        if not hasattr(values, '__len__'): 
     
    144130                context.update(dict(zip(self.loopvars, item))) 
    145131            else: 
    146132                context[self.loopvars[0]] = item 
    147             for node in self.nodelist_loop
    148                 nodelist.append(node.render(context)) 
     133            for node in self.nodelist
     134                result.append(node.render(context)) 
    149135            if unpack: 
    150136                # The loop variables were pushed on to the context so pop them 
    151137                # off again. This is necessary because the tag lets the length 
     
    154140                # context. 
    155141                context.pop() 
    156142        context.pop() 
    157         return nodelist.render(context
     143        return mark_safe(''.join([force_unicode(b) for b in result])
    158144 
    159145class IfChangedNode(Node): 
    160     def __init__(self, nodelist, *varlist): 
     146    def __init__(self, nodelist, *vallist): 
    161147        self.nodelist = nodelist 
    162148        self._last_seen = None 
    163         self._varlist = map(Variable, varlist) 
     149        self._vallist = vallist 
    164150        self._id = str(id(self)) 
    165151 
    166152    def render(self, context): 
     
    168154            self._last_seen = None 
    169155            context['forloop'][self._id] = 1 
    170156        try: 
    171             if self._varlist: 
     157            if self._vallist: 
    172158                # Consider multiple parameters.  This automatically behaves 
    173159                # like an OR evaluation of the multiple variables. 
    174                 compare_to = [var.resolve(context) for var in self._varlist] 
     160                compare_to = [var.resolve(context) for var in self._vallist] 
    175161            else: 
    176162                compare_to = self.nodelist.render(context) 
    177163        except VariableDoesNotExist: 
     
    188174        else: 
    189175            return '' 
    190176 
    191 class IfEqualNode(Node): 
    192     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 
    193         self.var1, self.var2 = Variable(var1), Variable(var2
    194         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 
     177class IfEqualNode(ConditionalNode): 
     178    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate): 
     179        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false
     180        self.val1, self.val2 = val1, val2         
    195181        self.negate = negate 
    196182 
    197183    def __repr__(self): 
    198184        return "<IfEqualNode>" 
    199185 
    200     def render(self, context): 
    201         try: 
    202             val1 = self.var1.resolve(context) 
    203         except VariableDoesNotExist: 
    204             val1 = None 
    205         try: 
    206             val2 = self.var2.resolve(context) 
    207         except VariableDoesNotExist: 
    208             val2 = None 
    209         if (self.negate and val1 != val2) or (not self.negate and val1 == val2): 
    210             return self.nodelist_true.render(context) 
    211         return self.nodelist_false.render(context) 
     186    def check_condition(self, context): 
     187        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context) 
     188        return self.negate == (val1 != val2) 
    212189 
    213 class IfNode(Node): 
     190class IfNode(ConditionalNode): 
    214191    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): 
    215         self.bool_exprs = bool_exprs 
    216         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 
     192        super(IfNode, self).__init__(nodelist_true, nodelist_false) 
     193        self.bool_exprs = bool_exprs         
    217194        self.link_type = link_type 
    218195 
    219196    def __repr__(self): 
    220197        return "<If node>" 
    221198 
    222     def __iter__(self): 
    223         for node in self.nodelist_true: 
    224             yield node 
    225         for node in self.nodelist_false: 
    226             yield node 
    227  
    228     def get_nodes_by_type(self, nodetype): 
    229         nodes = [] 
    230         if isinstance(self, nodetype): 
    231             nodes.append(self) 
    232         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 
    233         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 
    234         return nodes 
    235  
    236     def render(self, context): 
    237         if self.link_type == IfNode.LinkTypes.or_: 
    238             for ifnot, bool_expr in self.bool_exprs: 
    239                 try: 
    240                     value = bool_expr.resolve(context, True) 
    241                 except VariableDoesNotExist: 
    242                     value = None 
    243                 if (value and not ifnot) or (ifnot and not value): 
    244                     return self.nodelist_true.render(context) 
    245             return self.nodelist_false.render(context) 
     199    def check_condition(self, context): 
     200        if self.link_type == 'or': 
     201            for negated, bool_expr in self.bool_exprs: 
     202                value = bool_expr.resolve_safe(context, default=False) 
     203                if bool(value) != negated: 
     204                    return True 
     205            return False            
    246206        else: 
    247             for ifnot, bool_expr in self.bool_exprs: 
    248                 try: 
    249                     value = bool_expr.resolve(context, True) 
    250                 except VariableDoesNotExist: 
    251                     value = None 
    252                 if not ((value and not ifnot) or (ifnot and not value)): 
    253                     return self.nodelist_false.render(context) 
    254             return self.nodelist_true.render(context) 
     207            for negated, bool_expr in self.bool_exprs: 
     208                value = bool_expr.resolve_safe(context, default=False) 
     209                if bool(value) == negated: 
     210                    return False 
     211            return True 
     212         
    255213 
    256     class LinkTypes: 
    257         and_ = 0, 
    258         or_ = 1 
    259  
    260214class RegroupNode(Node): 
    261215    def __init__(self, target, expression, var_name): 
    262216        self.target, self.expression = target, expression 
    263217        self.var_name = var_name 
    264218 
    265219    def render(self, context): 
    266         obj_list = self.target.resolve(context, True
     220        obj_list = self.target.resolve_safe(context
    267221        if obj_list == None: 
    268222            # target variable wasn't found in context; fail silently. 
    269223            context[self.var_name] = [] 
     
    273227        context[self.var_name] = [ 
    274228            {'grouper': key, 'list': list(val)} 
    275229            for key, val in 
    276             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) 
     230            groupby(obj_list, lambda v, f=self.expression.resolve: f(v)) 
    277231        ] 
    278232        return '' 
    279233 
     
    310264                    return '' # Fail silently for invalid included templates. 
    311265        return output 
    312266 
    313 class LoadNode(Node): 
    314     def render(self, context): 
    315         return '' 
     267class LoadNode(EmptyNode): pass 
    316268 
    317269class NowNode(Node): 
    318270    def __init__(self, format_string): 
     
    322274        from datetime import datetime 
    323275        from django.utils.dateformat import DateFormat 
    324276        df = DateFormat(datetime.now()) 
    325         return df.format(self.format_string
     277        return df.format(self.format_string.resolve_safe(context)
    326278 
    327279class SpacelessNode(Node): 
    328280    def __init__(self, nodelist): 
     
    357309 
    358310    def render(self, context): 
    359311        from django.core.urlresolvers import reverse, NoReverseMatch 
    360         args = [arg.resolve(context) for arg in self.args] 
    361         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) 
     312        args = [arg.resolve_safe(context) for arg in self.args] 
     313        kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context)) 
    362314                       for k, v in self.kwargs.items()]) 
    363315        try: 
    364316            return reverse(self.view_name, args=args, kwargs=kwargs) 
     
    391343        return str(int(round(ratio))) 
    392344 
    393345class WithNode(Node): 
    394     def __init__(self, var, name, nodelist): 
    395         self.var = va
    396         self.name = name 
     346    def __init__(self, expr, name, nodelist): 
     347        self.expr = exp
     348        self.name = name         
    397349        self.nodelist = nodelist 
    398350 
    399351    def __repr__(self): 
    400352        return "<WithNode>" 
    401353 
    402354    def render(self, context): 
    403         val = self.var.resolve(context) 
    404355        context.push() 
    405         context[self.name] = val 
     356        context[self.name] = self.expr.resolve_safe(context) 
    406357        output = self.nodelist.render(context) 
    407358        context.pop() 
    408359        return output 
     
    418369    arg = args[1] 
    419370    if arg not in (u'on', u'off'): 
    420371        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") 
    421     nodelist = parser.parse(('endautoescape',)) 
    422     parser.delete_first_token() 
     372    nodelist = parser.parse_nodelist(('endautoescape',)) 
    423373    return AutoEscapeControlNode((arg == 'on'), nodelist) 
    424374autoescape = register.tag(autoescape) 
    425375 
     
    489439 
    490440    if len(args) > 4 and args[-2] == 'as': 
    491441        name = args[-1] 
    492         node = CycleNode(args[1:-2], name) 
     442        values = [parser.compile_filter(arg) for arg in args[1:-2]] 
     443        node = CycleNode(values, name) 
    493444        if not hasattr(parser, '_namedCycleNodes'): 
    494445            parser._namedCycleNodes = {} 
    495446        parser._namedCycleNodes[name] = node 
    496447    else: 
    497         node = CycleNode(args[1:]) 
     448        values = [parser.compile_filter(arg) for arg in args[1:]] 
     449        node = CycleNode(values) 
    498450    return node 
    499451cycle = register.tag(cycle) 
    500452 
     
    527479        {% endfilter %} 
    528480    """ 
    529481    _, rest = token.contents.split(None, 1) 
    530     filter_expr = parser.compile_filter("var|%s" % (rest)
     482    filter_expr = parser.compile_filter("var|%s" % rest
    531483    for func, unused in filter_expr.filters: 
    532484        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 
    533485            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__) 
    534     nodelist = parser.parse(('endfilter',)) 
    535     parser.delete_first_token() 
     486    nodelist = parser.parse_nodelist(('endfilter',)) 
    536487    return FilterNode(filter_expr, nodelist) 
    537488do_filter = register.tag("filter", do_filter) 
    538489 
     
    565516        {% firstof var1 var2 var3 "fallback value" %} 
    566517 
    567518    """ 
    568     bits = token.split_contents()[1:] 
    569     if len(bits) < 1: 
    570         raise TemplateSyntaxError("'firstof' statement requires at least one" 
    571                                   " argument") 
    572     return FirstOfNode(bits) 
     519    bits = parser.token_stream(token) 
     520    expressions = bits.parse_expression_list(minimum=1) 
     521    bits.assert_consumed() 
     522    return FirstOfNode(expressions) 
    573523firstof = register.tag(firstof) 
    574524 
    575525#@register.tag(name="for") 
     
    612562        ==========================  ================================================ 
    613563 
    614564    """ 
    615     bits = token.contents.split() 
    616     if len(bits) < 4: 
    617         raise TemplateSyntaxError("'for' statements should have at least four" 
    618                                   " words: %s" % token.contents) 
    619  
    620     is_reversed = bits[-1] == 'reversed' 
    621     in_index = is_reversed and -3 or -2 
    622     if bits[in_index] != 'in': 
    623         raise TemplateSyntaxError("'for' statements should use the format" 
    624                                   " 'for x in y': %s" % token.contents) 
    625  
    626     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 
    627     for var in loopvars: 
    628         if not var or ' ' in var: 
    629             raise TemplateSyntaxError("'for' tag received an invalid argument:" 
    630                                       " %s" % token.contents) 
    631  
    632     sequence = parser.compile_filter(bits[in_index+1]) 
    633     nodelist_loop = parser.parse(('endfor',)) 
    634     parser.delete_first_token() 
    635     return ForNode(loopvars, sequence, is_reversed, nodelist_loop) 
     565    bits = parser.token_stream(token) 
     566    loopvars = [] 
     567    while True: 
     568        var = bits.pop_name() 
     569        if var: 
     570            loopvars.append(var) 
     571            if not bits.pop_lexem(','): 
     572                break 
     573        else: 
     574            break 
     575    if not loopvars: 
     576        raise TemplateSyntaxError("'for' tag requires at least one loopvar")     
     577         
     578    if not bits.pop_lexem('in'): 
     579        raise TemplateSyntaxError("'for' tag requires 'in' keyword") 
     580    try:     
     581        sequence = bits.parse_expression() 
     582    except TokenSyntaxError: 
     583        raise bits.expected("expression") 
     584    reversed = bits.pop_lexem('reversed') 
     585    bits.assert_consumed() 
     586    nodelist = parser.parse_nodelist(('endfor',)) 
     587    return ForNode(loopvars, sequence, reversed, nodelist) 
    636588do_for = register.tag("for", do_for) 
    637589 
    638590def do_ifequal(parser, token, negate): 
    639     bits = list(token.split_contents()) 
    640     if len(bits) != 3: 
    641         raise TemplateSyntaxError, "%r takes two arguments" % bits[0] 
    642     end_tag = 'end' + bits[0] 
    643     nodelist_true = parser.parse(('else', end_tag)) 
    644     token = parser.next_token() 
    645     if token.contents == 'else': 
    646         nodelist_false = parser.parse((end_tag,)) 
    647         parser.delete_first_token() 
    648     else: 
    649         nodelist_false = NodeList() 
    650     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) 
     591    bits = parser.token_stream(token) 
     592    val1, val2 = bits.parse_expression_list(count=2) 
     593    bits.assert_consumed() 
     594    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 
     595    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) 
    651596 
    652597#@register.tag 
    653598def ifequal(parser, token): 
     
    737682            {% endif %} 
    738683        {% endif %} 
    739684    """ 
    740     bits = token.contents.split() 
    741     del bits[0] 
    742     if not bits: 
    743         raise TemplateSyntaxError("'if' statement requires at least one argument") 
    744     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] 
    745     bitstr = ' '.join(bits) 
    746     boolpairs = bitstr.split(' and ') 
     685    bits = parser.token_stream(token) 
     686    link_type = None 
     687    link = None 
    747688    boolvars = [] 
    748     if len(boolpairs) == 1: 
    749         link_type = IfNode.LinkTypes.or_ 
    750         boolpairs = bitstr.split(' or ') 
    751     else: 
    752         link_type = IfNode.LinkTypes.and_ 
    753         if ' or ' in bitstr: 
    754             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" 
    755     for boolpair in boolpairs: 
    756         if ' ' in boolpair: 
    757             try: 
    758                 not_, boolvar = boolpair.split() 
    759             except ValueError: 
    760                 raise TemplateSyntaxError, "'if' statement improperly formatted" 
    761             if not_ != 'not': 
    762                 raise TemplateSyntaxError, "Expected 'not' in if statement" 
    763             boolvars.append((True, parser.compile_filter(boolvar))) 
     689    while True: 
     690        negated = False 
     691        if bits.pop_lexem('not'): 
     692            negated = True 
     693        try: 
     694            expr = bits.parse_expression() 
     695        except TokenSyntaxError: 
     696            if link: 
     697                raise TemplateSyntaxError("'if' statement improperly formatted") 
     698            else: 
     699                raise TemplateSyntaxError("'if' statement requires at least one argument") 
     700        boolvars.append((negated, expr)) 
     701        link = bits.pop_name() 
     702        if not link: 
     703            break 
     704        if link_type: 
     705            if link_type != link: 
     706                raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'") 
    764707        else: 
    765             boolvars.append((False, parser.compile_filter(boolpair))) 
    766     nodelist_true = parser.parse(('else', 'endif')) 
    767     token = parser.next_token() 
    768     if token.contents == 'else': 
    769         nodelist_false = parser.parse(('endif',)) 
    770         parser.delete_first_token() 
    771     else: 
    772         nodelist_false = NodeList() 
     708            if not link in ('and', 'or'): 
     709                raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link)         
     710            link_type = link 
     711    bits.assert_consumed() 
     712     
     713    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if') 
     714     
    773715    return IfNode(boolvars, nodelist_true, nodelist_false, link_type) 
    774716do_if = register.tag("if", do_if) 
    775717 
     
    802744                {% endifchanged %} 
    803745            {% endfor %} 
    804746    """ 
    805     bits = token.contents.split() 
    806     nodelist = parser.parse(('endifchanged',)) 
    807     parser.delete_first_token() 
    808     return IfChangedNode(nodelist, *bits[1:]) 
     747    bits = parser.token_stream(token) 
     748    nodelist = parser.parse_nodelist(('endifchanged',)) 
     749    values = bits.parse_expression_list() 
     750    bits.assert_consumed() 
     751    return IfChangedNode(nodelist, *values) 
    809752ifchanged = register.tag(ifchanged) 
    810753 
    811754#@register.tag 
     
    872815 
    873816        It is {% now "jS F Y H:i" %} 
    874817    """ 
    875     bits = token.contents.split('"') 
    876     if len(bits) != 3: 
    877         raise TemplateSyntaxError, "'now' statement takes one argument" 
    878     format_string = bits[1] 
     818    bits = parser.token_stream(token) 
     819    try: 
     820        format_string = bits.parse_expression() 
     821    except TokenSyntaxError: 
     822        bits.expected("expression") 
     823    bits.assert_consumed() 
    879824    return NowNode(format_string) 
    880825now = register.tag(now) 
    881826 
     
    926871        {% regroup people|dictsort:"gender" by gender as grouped %} 
    927872 
    928873    """ 
    929     firstbits = token.contents.split(None, 3) 
    930     if len(firstbits) != 4: 
    931         raise TemplateSyntaxError, "'regroup' tag takes five arguments" 
    932     target = parser.compile_filter(firstbits[1]) 
    933     if firstbits[2] != 'by': 
    934         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 
    935     lastbits_reversed = firstbits[3][::-1].split(None, 2) 
    936     if lastbits_reversed[1][::-1] != 'as': 
    937         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 
    938                                   " be 'as'") 
    939  
    940     expression = parser.compile_filter(lastbits_reversed[2][::-1]) 
    941  
    942     var_name = lastbits_reversed[0][::-1] 
     874    bits = parser.token_stream(token) 
     875    try: 
     876        target = bits.parse_expression() 
     877    except TokenSyntaxError: 
     878        bits.expected("expression") 
     879     
     880    if not bits.pop_lexem('by'): 
     881        raise bits.expected("'by'") 
     882    try: 
     883        expression = bits.parse_expression() 
     884    except TokenSyntaxError: 
     885        raise TemplateSyntaxError() 
     886    try: 
     887        var_name = parse_as(bits) 
     888    except TokenSyntaxError: 
     889        raise bits.expected("as <name>") 
     890    bits.assert_consumed() 
    943891    return RegroupNode(target, expression, var_name) 
    944892regroup = register.tag(regroup) 
    945893 
     
    968916            </strong> 
    969917        {% endspaceless %} 
    970918    """ 
    971     nodelist = parser.parse(('endspaceless',)) 
    972     parser.delete_first_token() 
    973     return SpacelessNode(nodelist) 
     919    return SpacelessNode(parser.parse_nodelist(('endspaceless',))) 
    974920spaceless = register.tag(spaceless) 
    975921 
    976922#@register.tag 
     
    1038984 
    1039985    The URL will look like ``/clients/client/123/``. 
    1040986    """ 
    1041     bits = token.contents.split(' ', 2) 
    1042     if len(bits) < 2: 
    1043         raise TemplateSyntaxError("'%s' takes at least one argument" 
    1044                                   " (path to a view)" % bits[0]) 
    1045     args = [] 
    1046     kwargs = {} 
    1047     if len(bits) > 2: 
    1048         for arg in bits[2].split(','): 
    1049             if '=' in arg: 
    1050                 k, v = arg.split('=', 1) 
    1051                 k = k.strip() 
    1052                 kwargs[k] = parser.compile_filter(v) 
    1053             else: 
    1054                 args.append(parser.compile_filter(arg)) 
    1055     return URLNode(bits[1], args, kwargs) 
     987    bits = parser.token_stream(token) 
     988    try: 
     989        view = bits.parse_string(bare=True) 
     990    except TokenSyntaxError: 
     991        raise bits.expected("viewname") 
     992    args, kwargs = parse_args_and_kwargs(bits) 
     993    bits.assert_consumed() 
     994    return URLNode(view, args, kwargs) 
    1056995url = register.tag(url) 
    1057996 
    1058997#@register.tag 
     
    10841023#@register.tag 
    10851024def do_with(parser, token): 
    10861025    """ 
    1087     Adds a value to the context (inside of this block) for caching and easy 
     1026    Adds values to the context (inside of this block) for caching and easy 
    10881027    access. 
    10891028 
    10901029    For example:: 
     
    10921031        {% with person.some_sql_method as total %} 
    10931032            {{ total }} object{{ total|pluralize }} 
    10941033        {% endwith %} 
     1034         
     1035        {% with person.some_sql_method as total, person.get_full_name as full_name %} 
     1036            {{ full_name }}: {{ total }} object{{ total|pluralize }} 
     1037        {% endwith %} 
     1038         
    10951039    """ 
    1096     bits = list(token.split_contents()
    1097     if len(bits) != 4 or bits[2] != "as"
    1098         raise TemplateSyntaxError("%r expected format is 'value as name'" % 
    1099                                   bits[0]
    1100     var = parser.compile_filter(bits[1]) 
    1101     name = bits[3] 
    1102     nodelist = parser.parse(('endwith',)
    1103     parser.delete_first_token(
    1104     return WithNode(var, name, nodelist) 
     1040    bits = parser.token_stream(token
     1041    try
     1042        expr = bits.parse_expression() 
     1043        name = parse_as(bits
     1044    except TokenSyntaxError: 
     1045        raise TemplateSyntaxError("%r expected format is 'value as name'" % bits.name) 
     1046    bits.assert_consumed(
     1047    nodelist = parser.parse_nodelist(('endwith',)
     1048    return WithNode(expr, name, nodelist) 
    11051049do_with = register.tag('with', 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, Variabl
    2 from django.template import Library, Node, TextNod
    3 from django.template.loader import get_template, get_template_from_string, find_template_source 
     1from django.template import TemplateSyntaxError, Variable, Library, Node, TextNod
     2from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_sourc
     3from django.template.expressions import Literal 
    44from django.conf import settings 
    55from django.utils.safestring import mark_safe 
    66 
     
    3939class ExtendsNode(Node): 
    4040    must_be_first = True 
    4141 
    42     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None): 
     42    def __init__(self, nodelist, parent_name, template_dirs=None): 
    4343        self.nodelist = nodelist 
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr 
     44        self.parent_name = parent_name 
    4545        self.template_dirs = template_dirs 
    4646 
    4747    def __repr__(self): 
    48         if self.parent_name_expr: 
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token 
    5048        return '<ExtendsNode: extends "%s">' % self.parent_name 
    5149 
    5250    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 
     51        parent = self.parent_name.resolve_safe(context) 
    5652        if not parent: 
    5753            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 
    6054            raise TemplateSyntaxError, error_msg 
    6155        if hasattr(parent, 'render'): 
    6256            return parent # parent is a Template object 
     
    114108 
    115109class IncludeNode(Node): 
    116110    def __init__(self, template_name): 
    117         self.template_name = Variable(template_name) 
     111        self.template_name = template_name 
    118112 
    119113    def render(self, context): 
    120114        try: 
    121             template_name = self.template_name.resolve(context) 
     115            template_name = self.template_name.resolve_safe(context) 
    122116            t = get_template(template_name) 
    123117            return t.render(context) 
    124118        except TemplateSyntaxError, e: 
     
    161155    bits = token.contents.split() 
    162156    if len(bits) != 2: 
    163157        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]) 
     158    parent_name = parser.compile_filter(bits[1]) 
    169159    nodelist = parser.parse() 
    170160    if nodelist.get_nodes_by_type(ExtendsNode): 
    171161        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] 
    172     return ExtendsNode(nodelist, parent_name, parent_name_expr
     162    return ExtendsNode(nodelist, parent_name
    173163 
    174164def do_include(parser, token): 
    175165    """ 
     
    179169 
    180170        {% include "foo/some_include" %} 
    181171    """ 
    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]) 
     172    bits = parser.token_stream(token) 
     173    try: 
     174        template_name = bits.parse_expression() 
     175    except TokenSyntaxError: 
     176        raise bits.expected("expression")     
     177    if isinstance(template_name, Literal): 
     178        # remove ConstantIncludeNode and this hack will be gone 
     179        return ConstantIncludeNode(template_name.resolve(None)) 
     180    return IncludeNode(template_name) 
    189181 
    190182register.tag('block', do_block) 
    191183register.tag('extends', do_extends) 
  • 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 
     1from django.template.compiler import Lexer, Parser, tag_re 
     2from django.template import NodeList, ExpressionNode, TemplateSyntaxError 
    23from django.utils.encoding import force_unicode 
    34from django.utils.html import escape 
    45from django.utils.safestring import SafeData, EscapeData 
     
    5051        return DebugNodeList() 
    5152 
    5253    def create_variable_node(self, contents): 
    53         return DebugVariableNode(contents) 
     54        return DebugExpressionNode(contents) 
    5455 
    5556    def extend_nodelist(self, nodelist, node, token): 
    5657        node.source = token.source 
     
    8182            raise wrapped 
    8283        return result 
    8384 
    84 class DebugVariableNode(VariableNode): 
     85class DebugExpressionNode(ExpressionNode): 
    8586    def render(self, context): 
    8687        try: 
    87             output = force_unicode(self.filter_expression.resolve(context)
     88            return super(DebugExpressionNode, self).render(context
    8889        except TemplateSyntaxError, e: 
    8990            if not hasattr(e, 'source'): 
    9091                e.source = self.source 
    9192            raise 
    92         except UnicodeDecodeError: 
    93             return '' 
    94         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): 
    95             return escape(output) 
    96         else: 
    97             return output 
  • django/template/loader.py

    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) 
  • 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 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): 
     
    171170    the variable ``variable``. Make sure that the string 
    172171    in there is something that is in the .po file. 
    173172    """ 
    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() 
     173    bits = parser.token_stream(token) 
     174    try: 
     175        value = bits.parse_expression() 
     176    except TokenSyntaxError: 
     177        bits.expected("expression") 
     178    noop = bits.pop_lexem('noop') 
     179    bits.assert_consumed() 
    186180    return TranslateNode(value, noop) 
    187181 
    188182def do_block_translate(parser, token): 
  • 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 
    32from django.core.cache import cache 
    43from django.utils.encoding import force_unicode 
    54 
     
    87class CacheNode(Node): 
    98    def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): 
    109        self.nodelist = nodelist 
    11         self.expire_time_var = Variable(expire_time_var) 
     10        self.expire_time_var = expire_time_var 
    1211        self.fragment_name = fragment_name 
    1312        self.vary_on = vary_on 
    1413 
     
    2221        except (ValueError, TypeError): 
    2322            raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 
    2423        # Build a unicode key for this fragment and all vary-on's. 
    25         cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) 
     24        cache_key = u':'.join([self.fragment_name] + [force_unicode(var.resolve_safe(context)) for var in self.vary_on]) 
    2625        value = cache.get(cache_key) 
    2726        if value is None: 
    2827            value = self.nodelist.render(context) 
     
    5049 
    5150    Each unique set of arguments will result in a unique cache entry. 
    5251    """ 
    53     nodelist = parser.parse(('endcache',)) 
    54     parser.delete_first_token() 
    55     tokens = token.contents.split() 
    56     if len(tokens) < 3: 
    57         raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) 
    58     return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) 
     52    nodelist = parser.parse_nodelist(('endcache',)) 
     53    bits = parser.token_stream(token) 
     54    try: 
     55        expire_time = bits.parse_expression() 
     56    except TokenSyntaxError: 
     57        bits.expected("expression") 
     58    name = bits.pop_name() 
     59    if not name: 
     60        raise TemplateSyntaxError, "'cache' requires a fragment name"     
     61    vary_on = bits.parse_expression_list() 
     62    bits.assert_consumed()     
     63    return CacheNode(nodelist, expire_time, name, vary_on) 
    5964 
    6065register.tag('cache', do_cache)