Ticket #7799: tplrf.diff

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

     
     1from django.utils.encoding import force_unicode
     2from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     3from django.utils.html import escape
     4from django.template.expressions import LookupError
     5from django.conf import settings
     6
     7class Node(object):
     8    # Set this to True for nodes that must be first in the template (although
     9    # they can be preceded by text nodes.
     10    must_be_first = False
     11
     12    def render(self, context):
     13        "Return the node rendered as a string"
     14        pass
     15
     16    def __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

     
     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

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

     
     1import re
     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 parse_context_map(bits):
     76    context_map = {}
     77    try:
     78        while True:
     79            expr = bits.parse_expression()
     80            name = parse_as(bits)
     81            context_map[name] = expr
     82            if not bits.pop_lexem(','):
     83                break
     84    except TokenSyntaxError:
     85        bits.expected("context map")
     86    return context_map
     87
     88def resolve_args_and_kwargs(args, kwargs, context):
     89        resolved_args = [arg.resolve(context, True) for arg in args]
     90        resolved_kwargs = {}
     91        for name in kwargs:
     92                resolved_kwargs[name] = kwargs[name].resolve(context, True)
     93        return resolved_args, resolved_kwargs
     94   
     95def render_with_context_map(renderable, context_map, context):
     96    """
     97    renderable: a NodeList or Template
     98    context_map: a dict mapping strings to FilterExpressions
     99    context: a Context object
     100   
     101    """
     102    if context_map is None:
     103        return renderable.render(context)
     104    d = {}
     105    for name in context_map:
     106        d[name] = context_map[name].resolve_safe(context)
     107    context.update(d)
     108    output = renderable.render(context)
     109    context.pop()
     110    return output
     111
  • django/template/expressions.py

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

     
    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, render_with_context_map, parse_context_map, 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 = var
    396         self.name = name
     346    def __init__(self, context_map, nodelist):
     347        self.context_map = context_map
    397348        self.nodelist = nodelist
    398349
    399350    def __repr__(self):
    400351        return "<WithNode>"
    401352
    402353    def render(self, context):
    403         val = self.var.resolve(context)
    404         context.push()
    405         context[self.name] = val
    406         output = self.nodelist.render(context)
    407         context.pop()
    408         return output
     354        return render_with_context_map(self.nodelist, self.context_map, context)
    409355
    410356#@register.tag
    411357def autoescape(parser, token):
     
    418364    arg = args[1]
    419365    if arg not in (u'on', u'off'):
    420366        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    421     nodelist = parser.parse(('endautoescape',))
    422     parser.delete_first_token()
     367    nodelist = parser.parse_nodelist(('endautoescape',))
    423368    return AutoEscapeControlNode((arg == 'on'), nodelist)
    424369autoescape = register.tag(autoescape)
    425370
     
    489434
    490435    if len(args) > 4 and args[-2] == 'as':
    491436        name = args[-1]
    492         node = CycleNode(args[1:-2], name)
     437        values = [parser.compile_filter(arg) for arg in args[1:-2]]
     438        node = CycleNode(values, name)
    493439        if not hasattr(parser, '_namedCycleNodes'):
    494440            parser._namedCycleNodes = {}
    495441        parser._namedCycleNodes[name] = node
    496442    else:
    497         node = CycleNode(args[1:])
     443        values = [parser.compile_filter(arg) for arg in args[1:]]
     444        node = CycleNode(values)
    498445    return node
    499446cycle = register.tag(cycle)
    500447
     
    527474        {% endfilter %}
    528475    """
    529476    _, rest = token.contents.split(None, 1)
    530     filter_expr = parser.compile_filter("var|%s" % (rest))
     477    filter_expr = parser.compile_filter("var|%s" % rest)
    531478    for func, unused in filter_expr.filters:
    532479        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    533480            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    534     nodelist = parser.parse(('endfilter',))
    535     parser.delete_first_token()
     481    nodelist = parser.parse_nodelist(('endfilter',))
    536482    return FilterNode(filter_expr, nodelist)
    537483do_filter = register.tag("filter", do_filter)
    538484
     
    565511        {% firstof var1 var2 var3 "fallback value" %}
    566512
    567513    """
    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)
     514    bits = parser.token_stream(token)
     515    expressions = bits.parse_expression_list(minimum=1)
     516    bits.assert_consumed()
     517    return FirstOfNode(expressions)
    573518firstof = register.tag(firstof)
    574519
    575520#@register.tag(name="for")
     
    612557        ==========================  ================================================
    613558
    614559    """
    615     bits = token.contents.split()
    616     if len(bits) < 4:
    617         raise TemplateSyntaxError("'for' statements should have at least four"
    618                                   " words: %s" % token.contents)
    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)
     560    bits = parser.token_stream(token)
     561    loopvars = []
     562    while True:
     563        var = bits.pop_name()
     564        if var:
     565            loopvars.append(var)
     566            if not bits.pop_lexem(','):
     567                break
     568        else:
     569            break
     570    if not loopvars:
     571        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
     572       
     573    if not bits.pop_lexem('in'):
     574        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
     575    try:   
     576        sequence = bits.parse_expression()
     577    except TokenSyntaxError:
     578        raise bits.expected("expression")
     579    reversed = bits.pop_lexem('reversed')
     580    bits.assert_consumed()
     581    nodelist = parser.parse_nodelist(('endfor',))
     582    return ForNode(loopvars, sequence, reversed, nodelist)
    636583do_for = register.tag("for", do_for)
    637584
    638585def 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)
     586    bits = parser.token_stream(token)
     587    val1, val2 = bits.parse_expression_list(count=2)
     588    bits.assert_consumed()
     589    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     590    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    651591
    652592#@register.tag
    653593def ifequal(parser, token):
     
    737677            {% endif %}
    738678        {% endif %}
    739679    """
    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 ')
     680    bits = parser.token_stream(token)
     681    link_type = None
     682    link = None
    747683    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)))
     684    while True:
     685        negated = False
     686        if bits.pop_lexem('not'):
     687            negated = True
     688        try:
     689            expr = bits.parse_expression()
     690        except TokenSyntaxError:
     691            if link:
     692                raise TemplateSyntaxError("'if' statement improperly formatted")
     693            else:
     694                raise TemplateSyntaxError("'if' statement requires at least one argument")
     695        boolvars.append((negated, expr))
     696        link = bits.pop_name()
     697        if not link:
     698            break
     699        if link_type:
     700            if link_type != link:
     701                raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'")
    764702        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()
     703            if not link in ('and', 'or'):
     704                raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link)       
     705            link_type = link
     706    bits.assert_consumed()
     707   
     708    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')
     709   
    773710    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    774711do_if = register.tag("if", do_if)
    775712
     
    802739                {% endifchanged %}
    803740            {% endfor %}
    804741    """
    805     bits = token.contents.split()
    806     nodelist = parser.parse(('endifchanged',))
    807     parser.delete_first_token()
    808     return IfChangedNode(nodelist, *bits[1:])
     742    bits = parser.token_stream(token)
     743    nodelist = parser.parse_nodelist(('endifchanged',))
     744    values = bits.parse_expression_list()
     745    bits.assert_consumed()
     746    return IfChangedNode(nodelist, *values)
    809747ifchanged = register.tag(ifchanged)
    810748
    811749#@register.tag
     
    872810
    873811        It is {% now "jS F Y H:i" %}
    874812    """
    875     bits = token.contents.split('"')
    876     if len(bits) != 3:
    877         raise TemplateSyntaxError, "'now' statement takes one argument"
    878     format_string = bits[1]
     813    bits = parser.token_stream(token)
     814    try:
     815        format_string = bits.parse_expression()
     816    except TokenSyntaxError:
     817        bits.expected("expression")
     818    bits.assert_consumed()
    879819    return NowNode(format_string)
    880820now = register.tag(now)
    881821
     
    926866        {% regroup people|dictsort:"gender" by gender as grouped %}
    927867
    928868    """
    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]
     869    bits = parser.token_stream(token)
     870    try:
     871        target = bits.parse_expression()
     872    except TokenSyntaxError:
     873        bits.expected("expression")
     874   
     875    if not bits.pop_lexem('by'):
     876        raise bits.expected("'by'")
     877    try:
     878        expression = bits.parse_expression()
     879    except TokenSyntaxError:
     880        raise TemplateSyntaxError()
     881    try:
     882        var_name = parse_as(bits)
     883    except TokenSyntaxError:
     884        raise bits.expected("as <name>")
     885    bits.assert_consumed()
    943886    return RegroupNode(target, expression, var_name)
    944887regroup = register.tag(regroup)
    945888
     
    968911            </strong>
    969912        {% endspaceless %}
    970913    """
    971     nodelist = parser.parse(('endspaceless',))
    972     parser.delete_first_token()
    973     return SpacelessNode(nodelist)
     914    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
    974915spaceless = register.tag(spaceless)
    975916
    976917#@register.tag
     
    1038979
    1039980    The URL will look like ``/clients/client/123/``.
    1040981    """
    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)
     982    bits = parser.token_stream(token)
     983    try:
     984        view = bits.parse_string(bare=True)
     985    except TokenSyntaxError:
     986        raise bits.expected("viewname")
     987    args, kwargs = parse_args_and_kwargs(bits)
     988    bits.assert_consumed()
     989    return URLNode(view, args, kwargs)
    1056990url = register.tag(url)
    1057991
    1058992#@register.tag
     
    10841018#@register.tag
    10851019def do_with(parser, token):
    10861020    """
    1087     Adds a value to the context (inside of this block) for caching and easy
     1021    Adds values to the context (inside of this block) for caching and easy
    10881022    access.
    10891023
    10901024    For example::
     
    10921026        {% with person.some_sql_method as total %}
    10931027            {{ total }} object{{ total|pluralize }}
    10941028        {% endwith %}
     1029       
     1030        {% with person.some_sql_method as total, person.get_full_name as full_name %}
     1031            {{ full_name }}: {{ total }} object{{ total|pluralize }}
     1032        {% endwith %}
     1033       
    10951034    """
    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)
     1035    bits = parser.token_stream(token)
     1036    context_map = parse_context_map(bits)
     1037    bits.assert_consumed()
     1038    nodelist = parser.parse_nodelist(('endwith',))
     1039    return WithNode(context_map, nodelist)
    11051040do_with = register.tag('with', do_with)
  • django/template/compat.py

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

     
    99
    1010class Context(object):
    1111    "A stack container for variable context"
    12     def __init__(self, dict_=None, autoescape=True):
     12    def __init__(self, dict_=None, autoescape=True, loader=None):
    1313        dict_ = dict_ or {}
    1414        self.dicts = [dict_]
    1515        self.autoescape = autoescape
     16        self.template_cache = {}
     17        self.loader = loader
    1618
    1719    def __repr__(self):
    1820        return repr(self.dicts)
     
    6567        self.dicts = [other_dict] + self.dicts
    6668        return other_dict
    6769
     70    def get_template(self, template_name):
     71        if not template_name in self.template_cache:
     72            if self.loader is None:
     73                from django.template import loader
     74                tpl = loader.get_template(template_name)
     75            else:
     76                tpl = self.loader.get_template(template_name)
     77            self.template_cache[template_name] = tpl
     78        return self.template_cache[template_name]
     79
     80    def select_template(self, template_name_list):
     81        from django.template import TemplateDoesNotExist
     82        for template_name in template_name_list:
     83            try:
     84                return self.get_template(template_name)
     85            except TemplateDoesNotExist:
     86                continue
     87        raise TemplateDoesNotExist, ', '.join(template_name_list)
     88
    6889# This is a function rather than module-level procedural code because we only
    6990# want it to execute if somebody uses RequestContext.
    7091def get_standard_processors():
     
    93114    Additional processors can be specified as a list of callables
    94115    using the "processors" keyword argument.
    95116    """
    96     def __init__(self, request, dict=None, processors=None):
    97         Context.__init__(self, dict)
     117    def __init__(self, request, dict=None, processors=None, loader=None):
     118        Context.__init__(self, dict, loader=loader)
    98119        if processors is None:
    99120            processors = ()
    100121        else:
  • django/template/loader_tags.py

     
    1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    2 from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     1from django.template import TemplateSyntaxError, Variable, Library, Node, TextNode
     2from django.template.utils import render_with_context_map, parse_context_map
     3from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
    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):
    4343        self.nodelist = nodelist
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
    45         self.template_dirs = template_dirs
     44        self.parent_name = parent_name
    4645
    4746    def __repr__(self):
    48         if self.parent_name_expr:
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    5047        return '<ExtendsNode: extends "%s">' % self.parent_name
    5148
    5249    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
     50        parent = self.parent_name.resolve_safe(context)
    5651        if not parent:
    5752            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
    6053            raise TemplateSyntaxError, error_msg
    6154        if hasattr(parent, 'render'):
    6255            return parent # parent is a Template object
    6356        try:
    64             source, origin = find_template_source(parent, self.template_dirs)
     57            return context.get_template(parent)
    6558        except TemplateDoesNotExist:
    6659            raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
    67         else:
    68             return get_template_from_string(source, origin, parent)
    6960
    7061    def render(self, context):
    7162        compiled_parent = self.get_parent(context)
     
    9687                parent_block.nodelist = block_node.nodelist
    9788        return compiled_parent.render(context)
    9889
    99 class ConstantIncludeNode(Node):
    100     def __init__(self, template_path):
    101         try:
    102             t = get_template(template_path)
    103             self.template = t
    104         except:
    105             if settings.TEMPLATE_DEBUG:
    106                 raise
    107             self.template = None
    108 
    109     def render(self, context):
    110         if self.template:
    111             return self.template.render(context)
    112         else:
    113             return ''
    114 
    11590class IncludeNode(Node):
    116     def __init__(self, template_name):
    117         self.template_name = Variable(template_name)
     91    def __init__(self, template_name, context_map=None):
     92        self.template_name = template_name
     93        self.context_map = context_map
    11894
    11995    def render(self, context):
    12096        try:
    121             template_name = self.template_name.resolve(context)
    122             t = get_template(template_name)
    123             return t.render(context)
     97            template_name = self.template_name.resolve_safe(context)
     98            tpl = context.get_template(template_name)           
     99            return render_with_context_map(tpl, self.context_map, context)
    124100        except TemplateSyntaxError, e:
    125101            if settings.TEMPLATE_DEBUG:
    126102                raise
     
    161137    bits = token.contents.split()
    162138    if len(bits) != 2:
    163139        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])
     140    parent_name = parser.compile_filter(bits[1])
    169141    nodelist = parser.parse()
    170142    if nodelist.get_nodes_by_type(ExtendsNode):
    171143        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
    172     return ExtendsNode(nodelist, parent_name, parent_name_expr)
     144    return ExtendsNode(nodelist, parent_name)
    173145
    174146def do_include(parser, token):
    175147    """
    176148    Loads a template and renders it with the current context.
     149    Optionally takes a "with value as name (, value as name)*" clause.
    177150
    178151    Example::
    179152
    180153        {% include "foo/some_include" %}
     154        {% include "foo" with value as name %}
     155        {% include "foo" with value as name, bar as baz %}
    181156    """
    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])
     157    bits = parser.token_stream(token)
     158    template_name = bits.parse_expression()
     159    context_map = None
     160    if bits.pop_lexem('with'):
     161        context_map = parse_context_map(bits)
     162    bits.assert_consumed()
     163    return IncludeNode(template_name, context_map)   
    189164
    190165register.tag('block', do_block)
    191166register.tag('extends', do_extends)
  • django/template/library.py

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

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

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

     
    11import re
    22
    3 from django.template import Node, Variable, VariableNode
    4 from django.template import TemplateSyntaxError, TokenParser, Library
    5 from django.template import TOKEN_TEXT, TOKEN_VAR
     3from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library
     4from django.template.compiler import TOKEN_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

     
    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)
Back to Top