Ticket #7806: tplrf-r8970.diff

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

with tests

  • django/templatetags/i18n.py

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

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

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
  • django/template/compiler.py

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
     100
     101
     102
     103
     104
     105
     106
     107
     108
     109
     110
     111
     112
     113
     114
     115
     116
     117
     118
     119
     120
     121
     122
     123
     124
     125
     126
     127
     128
     129
     130
     131
     132
     133
     134
     135
     136
     137
     138
     139
     140
     141
     142
     143
     144
     145
     146
     147
     148
     149
     150
     151
     152
     153
     154
     155
     156
     157
     158
     159
     160
     161
     162
     163
     164
     165
     166
     167
     168
     169
     170
     171
     172
     173
     174
     175
     176
     177
     178
     179
     180
     181
     182
     183
     184
     185
     186
     187
     188
     189
     190
     191
     192
     193
     194
     195
     196
     197
     198
     199
     200
     201
     202
     203
     204
     205
     206
     207
     208
     209
     210
     211
     212
     213
     214
     215
     216
     217
     218
     219
     220
     221
     222
     223
     224
     225
     226
     227
     228
     229
     230
     231
     232
     233
     234
     235
     236
     237
     238
     239
     240
     241
     242
     243
     244
     245
     246
     247
     248
     249
     250
     251
     252
     253
     254
     255
     256
     257
     258
     259
     260
     261
     262
     263
     264
     265
     266
     267
     268
     269
     270
     271
     272
     273
     274
     275
     276
     277
     278
     279
     280
     281
     282
     283
     284
     285
     286
     287
     288
     289
     290
     291
     292
     293
     294
     295
     296
     297
     298
     299
     300
     301
     302
     303
     304
     305
     306
     307
     308
     309
     310
     311
     312
     313
     314
     315
     316
     317
     318
     319
     320
     321
     322
     323
     324
     325
     326
     327
     328
     329
     330
     331
     332
     333
     334
     335
     336
     337
     338
     339
     340
     341
     342
     343
     344
     345
     346
     347
     348
     349
     350
     351
     352
     353
     354
     355
     356
     357
     358
     359
     360
     361
     362
     363
     364
     365
     366
     367
     368
     369
     370
     371
     372
     373
     374
     375
     376
     377
     378
     379
     380
     381
     382
     383
     384
     385
     386
     387
     388
     389
     390
     391
     392
     393
     394
     395
     396
     397
     398
     399
     400
     401
     402
     403
     404
     405
     406
     407
     408
     409
     410
     411
     412
     413
     414
     415
     416
     417
     418
     419
     420
     421
     422
     423
     424
     425
     426
     427
     428
     429
     430
     431
     432
     433
     434
     435
     436
     437
     438
     439
     440
     441
     442
     443
     444
     445
     446
     447
     448
     449
     450
     451
     452
     453
     454
     455
     456
     457
     458
     459
     460
     461
     462
     463
     464
     465
     466
     467
     468
     469
     470
     471
     472
     473
     474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
     485
     486
     487
     488
     489
     490
     491
     492
     493
     494
     495
     496
     497
     498
     499
     500
     501
     502
     503
     504
     505
     506
     507
     508
     509
     510
     511
     512
     513
     514
     515
     516
     517
     518
     519
     520
     521
     522
     523
     524
     525
     526
     527
     528
     529
     530
     531
     532
     533
     534
     535
     536
     537
  • django/template/__init__.py

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

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
  • django/template/expressions.py

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
     100
     101
     102
     103
     104
     105
     106
     107
     108
     109
     110
     111
     112
     113
     114
  • django/template/defaulttags.py

     
    88except NameError:
    99    from django.utils.itercompat import reversed     # Python 2.3 fallback
    1010
    11 from django.template import Node, NodeList, Template, Context, Variable
    12 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
     11from django.template import Node, NodeList, Template, Context
     12from django.template import TemplateSyntaxError,
    1313from django.template import get_library, Library, InvalidTemplateLibrary
     14
     15
    1416from django.conf import settings
    15 from django.utils.encoding import smart_str, smart_unicode
     17from django.utils.encoding import smart_str, _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, cyclevas, variable_name=None):
     42        self.cycle_iter = itertools_cycle()
    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(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)
    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, vas):
     72        self.va
    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
    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
    9086
    9187    def __repr__(self):
    9288        reversed_text = self.is_reversed and ' reversed' or ''
    9389        return "<For Node: for %s in %s, tail_len: %d%s>" % \
    94             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
     90            (', '.join(self.loopvars), self.sequence, len(self.nodelist),
    9591             reversed_text)
    9692
    9793    def __iter__(self):
    98         for node in self.nodelist_loop:
     94        for node in self.nodelist:
    9995            yield node
    10096
    101     def get_nodes_by_type(self, nodetype):
    102         nodes = []
    103         if isinstance(self, nodetype):
    104             nodes.append(self)
    105         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
    106         return nodes
    107 
    10897    def render(self, context):
    109         nodelist = NodeList()
     98       
    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                t.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 )
    158144
    159 class IfChangedNode(Node):
    160     def __init__(self, nodelist_true, nodelist_false, *varlist):
    161         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    162         self._last_seen = None
    163         self._varlist = map(Variable, varlist)
    164         self._id = str(id(self))
     145class IfChangedNode(Node):
     146    def __init__(self, nodelist_true, nodelist_false, list):
     147        s
     148        self.last_seen = None
     149        self.
     150        self.id = str(id(self))
    165151
    166152    def render(self, context):
    167         if 'forloop' in context and self._id not in context['forloop']:
    168             self._last_seen = None
    169             context['forloop'][self._id] = 1
     153        if 'forloop' in context and self.id not in context['forloop']:
     154            self.last_seen = None
     155            context['forloop'][self.id] = 1
    170156        try:
    171             if self._varlist:
     157            if self.list:
    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 = [list]
    175161            else:
    176162                compare_to = self.nodelist_true.render(context)
    177         except VariableDoesNotExist:
     163        except :
    178164            compare_to = None
    179165
    180         if compare_to != self._last_seen:
    181             firstloop = (self._last_seen == None)
    182             self._last_seen = compare_to
     166        if last_seen:
     167            firstloop = (self.last_seen == None)
     168            self.last_seen = compare_to
    183169            context.push()
    184170            context['ifchanged'] = {'firstloop': firstloop}
    185171            content = self.nodelist_true.render(context)
     
    187173            return content
    188174        elif self.nodelist_false:
    189175            return self.nodelist_false.render(context)
    190         return ''
     176        return ''
    191177
    192 class IfEqualNode(Node):
    193     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    194         self.var1, self.var2 = Variable(var1), Variable(var2)
    195         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     178
     179class IfEqualNode(ConditionalNode):
     180    def __init__(self, val1, val2, nodelist_true, nodelist_false, negate):
     181        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
     182        self.val1, self.val2 = val1, val2
    196183        self.negate = negate
    197184
    198     def __repr__(self):
    199         return "<IfEqualNode>"
     185    def check_condition(self, context):
     186        val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context)
     187        return self.negate == (val1 != val2)
    200188
    201     def render(self, context):
    202         try:
    203             val1 = self.var1.resolve(context)
    204         except VariableDoesNotExist:
    205             val1 = None
    206         try:
    207             val2 = self.var2.resolve(context)
    208         except VariableDoesNotExist:
    209             val2 = None
    210         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    211             return self.nodelist_true.render(context)
    212         return self.nodelist_false.render(context)
    213189
    214 class IfNode(Node):
     190class IfNode(Node):
    215191    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
    216         self.bool_exprs = bool_exprs
    217         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    218         self.link_type = link_type
     192        super(IfNode, self).__init__(nodelist_true, nodelist_false)
     193        self.bool_exprs, self.link_type = bool_exprs, link_type
    219194
    220     def __repr__(self):
    221         return "<If node>"
    222 
    223     def __iter__(self):
    224         for node in self.nodelist_true:
    225             yield node
    226         for node in self.nodelist_false:
    227             yield node
    228 
    229     def get_nodes_by_type(self, nodetype):
    230         nodes = []
    231         if isinstance(self, nodetype):
    232             nodes.append(self)
    233         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    234         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    235         return nodes
    236 
    237     def render(self, context):
    238         if self.link_type == IfNode.LinkTypes.or_:
    239             for ifnot, bool_expr in self.bool_exprs:
    240                 try:
    241                     value = bool_expr.resolve(context, True)
    242                 except VariableDoesNotExist:
    243                     value = None
    244                 if (value and not ifnot) or (ifnot and not value):
    245                     return self.nodelist_true.render(context)
    246             return self.nodelist_false.render(context)
     195    def check_condition(self, context):
     196        if self.link_type == 'or':
     197            for negated, bool_expr in self.bool_exprs:
     198                value = bool_expr.resolve_safe(context, default=False)
     199                if bool(value) != negated:
     200                    return True
     201            return False
    247202        else:
    248             for ifnot, bool_expr in self.bool_exprs:
    249                 try:
    250                     value = bool_expr.resolve(context, True)
    251                 except VariableDoesNotExist:
    252                     value = None
    253                 if not ((value and not ifnot) or (ifnot and not value)):
    254                     return self.nodelist_false.render(context)
    255             return self.nodelist_true.render(context)
     203            for negated, bool_expr in self.bool_exprs:
     204                value = bool_expr.resolve_safe(context, default=False)
     205                if bool(value) == negated:
     206                    return False
     207            return True
     208       
    256209
    257     class LinkTypes:
    258         and_ = 0,
    259         or_ = 1
    260 
    261210class RegroupNode(Node):
    262211    def __init__(self, target, expression, var_name):
    263212        self.target, self.expression = target, expression
    264213        self.var_name = var_name
    265214
    266215    def render(self, context):
    267         obj_list = self.target.resolve(context, True)
     216        obj_list = self.target.resolve)
    268217        if obj_list == None:
    269218            # target variable wasn't found in context; fail silently.
    270219            context[self.var_name] = []
    271             return ''
     220            return ''
    272221        # List of dictionaries in the format:
    273222        # {'grouper': 'key', 'list': [list of contents]}.
    274223        context[self.var_name] = [
    275224            {'grouper': key, 'list': list(val)}
    276225            for key, val in
    277             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
     226            groupby(obj_list, lambda v, f=self.expression.resolve))
    278227        ]
    279         return ''
     228        return ''
    280229
    281230def include_is_allowed(filepath):
    282231    for root in settings.ALLOWED_INCLUDE_ROOTS:
     
    293242            if settings.DEBUG:
    294243                return "[Didn't have permission to include file]"
    295244            else:
    296                 return '' # Fail silently for invalid includes.
     245                return '' # Fail silently for invalid includes.
    297246        try:
    298247            fp = open(self.filepath, 'r')
    299248            output = fp.read()
     
    308257                if settings.DEBUG:
    309258                    return "[Included template had syntax error: %s]" % e
    310259                else:
    311                     return '' # Fail silently for invalid included templates.
     260                    return '' # Fail silently for invalid included templates.
    312261        return output
    313262
    314 class LoadNode(Node):
    315     def render(self, context):
    316         return ''
     263class LoadNode(EmptyNode): pass
    317264
    318265class NowNode(Node):
    319266    def __init__(self, format_string):
     
    323270        from datetime import datetime
    324271        from django.utils.dateformat import DateFormat
    325272        df = DateFormat(datetime.now())
    326         return df.format(self.format_string)
     273        return df.format(self.format_string)
    327274
    328275class SpacelessNode(Node):
    329276    def __init__(self, nodelist):
     
    359306
    360307    def render(self, context):
    361308        from django.core.urlresolvers import reverse, NoReverseMatch
    362         args = [arg.resolve(context) for arg in self.args]
    363         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
     309        args = [arg.resolve(context) for arg in self.args]
     310        kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
    364311                       for k, v in self.kwargs.items()])
    365312       
    366313       
     
    382329                   
    383330        if self.asvar:
    384331            context[self.asvar] = url
    385             return ''
     332            return ''
    386333        else:
    387334            return url
    388335
     
    396343        try:
    397344            value = self.val_expr.resolve(context)
    398345            maxvalue = self.max_expr.resolve(context)
    399         except VariableDoesNotExist:
    400             return ''
     346        except :
     347            return ''
    401348        try:
    402             value = float(value)
    403             maxvalue = float(maxvalue)
    404             ratio = (value / maxvalue) * int(self.max_width)
     349            ratio = (float(value) / float(maxvalue)) * int(self.max_width)
    405350        except (ValueError, ZeroDivisionError):
    406             return ''
    407         return str(int(round(ratio)))
     351            return ''
     352        return (int(round(ratio)))
    408353
    409354class WithNode(Node):
    410     def __init__(self, var, name, nodelist):
    411         self.var = var
    412         self.name = name
     355    def __init__(self, r, name, nodelist):
     356        self.r
     357        self.name = name
    413358        self.nodelist = nodelist
    414359
    415     def __repr__(self):
    416         return "<WithNode>"
    417 
    418360    def render(self, context):
    419         val = self.var.resolve(context)
    420361        context.push()
    421         context[self.name] = val
     362        context[self.name] =
    422363        output = self.nodelist.render(context)
    423364        context.pop()
    424365        return output
     
    434375    arg = args[1]
    435376    if arg not in (u'on', u'off'):
    436377        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    437     nodelist = parser.parse(('endautoescape',))
    438     parser.delete_first_token()
     378    nodelist = parser.parse_nodelist(('endautoescape',))
    439379    return AutoEscapeControlNode((arg == 'on'), nodelist)
    440380autoescape = register.tag(autoescape)
    441381
     
    493433        # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
    494434        # case.
    495435        args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
    496 
     436   
    497437    if len(args) == 2:
    498438        # {% cycle foo %} case.
    499439        name = args[1]
     
    505445
    506446    if len(args) > 4 and args[-2] == 'as':
    507447        name = args[-1]
    508         node = CycleNode(args[1:-2], name)
     448        values = [parser.compile_filter(arg) for arg in args[1:-2]]
     449        node = CycleNode(values, name)
    509450        if not hasattr(parser, '_namedCycleNodes'):
    510451            parser._namedCycleNodes = {}
    511452        parser._namedCycleNodes[name] = node
    512453    else:
    513         node = CycleNode(args[1:])
     454        values = [parser.compile_filter(arg) for arg in args[1:]]
     455        node = CycleNode(values)
    514456    return node
    515457cycle = register.tag(cycle)
    516458
     
    542484            This text will be HTML-escaped, and will appear in lowercase.
    543485        {% endfilter %}
    544486    """
    545     _, rest = token.contents.split(None, 1)
    546     filter_expr = parser.compile_filter("var|%s" % (rest))
    547     for func, unused in filter_expr.filters:
     487    name, filter_ = token.contents.split(None, 1)       
     488    bits = parser.token_stream("var|%s" % filter_)
     489    try:
     490        filter_expr = bits.parse_expression()
     491    except TokenSyntaxError:
     492        raise TemplateSyntaxError("'%s' requires a valid filter chain, got: '%s'" % name, filter_)
     493    for func, args in filter_expr.filters:
    548494        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    549495            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    550     nodelist = parser.parse(('endfilter',))
    551     parser.delete_first_token()
     496    nodelist = parser.parse_nodelist(('endfilter',))
    552497    return FilterNode(filter_expr, nodelist)
    553498do_filter = register.tag("filter", do_filter)
    554499
    555500#@register.tag
    556 def firstof(parser, token):
     501#@uses_token_stream
     502def firstof(parser, bits):
    557503    """
    558504    Outputs the first variable passed that is not False.
    559505
     
    581527        {% firstof var1 var2 var3 "fallback value" %}
    582528
    583529    """
    584     bits = token.split_contents()[1:]
    585     if len(bits) < 1:
    586         raise TemplateSyntaxError("'firstof' statement requires at least one"
    587                                   " argument")
    588     return FirstOfNode(bits)
    589 firstof = register.tag(firstof)
     530    expressions = bits.parse_expression_list(minimum=1)
     531    return FirstOfNode(expressions)
     532firstof = register.tag(uses_token_stream(firstof))
    590533
    591534#@register.tag(name="for")
    592 def do_for(parser, token):
     535#@uses_token_stream
     536def do_for(parser, bits):
    593537    """
    594538    Loops over each item in an array.
    595539
     
    628572        ==========================  ================================================
    629573
    630574    """
    631     bits = token.contents.split()
    632     if len(bits) < 4:
    633         raise TemplateSyntaxError("'for' statements should have at least four"
    634                                   " words: %s" % token.contents)
     575    loopvars = []
     576    while True:
     577        var = bits.pop_name()
     578        if not var:
     579            break
     580        loopvars.append(var)
     581        if not bits.pop_lexem(','):
     582            break
     583    if not loopvars:
     584        raise TemplateSyntaxError("'for' tag requires at least one loopvar")   
     585       
     586    if not bits.pop_lexem('in'):
     587        raise TemplateSyntaxError("'for' tag requires 'in' keyword")
     588    sequence = bits.parse_expression(required=True)
     589    reversed = bits.pop_lexem('reversed')
     590    nodelist = parser.parse_nodelist(('endfor',))
     591    return ForNode(loopvars, sequence, reversed, nodelist)
     592do_for = register.tag("for", uses_token_stream(do_for))
    635593
    636     is_reversed = bits[-1] == 'reversed'
    637     in_index = is_reversed and -3 or -2
    638     if bits[in_index] != 'in':
    639         raise TemplateSyntaxError("'for' statements should use the format"
    640                                   " 'for x in y': %s" % token.contents)
     594def do_ifequal(parser, bits, negate):
     595    val1, val2 = bits.parse_expression_list(count=2)
     596    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     597    return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    641598
    642     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    643     for var in loopvars:
    644         if not var or ' ' in var:
    645             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    646                                       " %s" % token.contents)
    647 
    648     sequence = parser.compile_filter(bits[in_index+1])
    649     nodelist_loop = parser.parse(('endfor',))
    650     parser.delete_first_token()
    651     return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
    652 do_for = register.tag("for", do_for)
    653 
    654 def do_ifequal(parser, token, negate):
    655     bits = list(token.split_contents())
    656     if len(bits) != 3:
    657         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
    658     end_tag = 'end' + bits[0]
    659     nodelist_true = parser.parse(('else', end_tag))
    660     token = parser.next_token()
    661     if token.contents == 'else':
    662         nodelist_false = parser.parse((end_tag,))
    663         parser.delete_first_token()
    664     else:
    665         nodelist_false = NodeList()
    666     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
    667 
    668599#@register.tag
    669 def ifequal(parser, token):
     600#@uses_token_stream
     601def ifequal(parser, bits):
    670602    """
    671603    Outputs the contents of the block if the two arguments equal each other.
    672604
     
    682614            ...
    683615        {% endifnotequal %}
    684616    """
    685     return do_ifequal(parser, token, False)
    686 ifequal = register.tag(ifequal)
     617    return do_ifequal(parser, , False)
     618ifequal = register.tag()
    687619
    688620#@register.tag
    689 def ifnotequal(parser, token):
     621#@uses_token_stream
     622def ifnotequal(parser, bits):
    690623    """
    691624    Outputs the contents of the block if the two arguments are not equal.
    692625    See ifequal.
    693626    """
    694     return do_ifequal(parser, token, True)
    695 ifnotequal = register.tag(ifnotequal)
     627    return do_ifequal(parser, , True)
     628ifnotequal = register.tag()
    696629
    697630#@register.tag(name="if")
    698 def do_if(parser, token):
     631#@uses_token_stream
     632def do_if(parser, bits):
    699633    """
    700634    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    701635    (i.e., exists, is not empty, and is not a false boolean value), the
     
    753687            {% endif %}
    754688        {% endif %}
    755689    """
    756     bits = token.contents.split()
    757     del bits[0]
    758     if not bits:
    759         raise TemplateSyntaxError("'if' statement requires at least one argument")
    760     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    761     bitstr = ' '.join(bits)
    762     boolpairs = bitstr.split(' and ')
     690    link_type = None
     691    link = None
    763692    boolvars = []
    764     if len(boolpairs) == 1:
    765         link_type = IfNode.LinkTypes.or_
    766         boolpairs = bitstr.split(' or ')
    767     else:
    768         link_type = IfNode.LinkTypes.and_
    769         if ' or ' in bitstr:
    770             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
    771     for boolpair in boolpairs:
    772         if ' ' in boolpair:
    773             try:
    774                 not_, boolvar = boolpair.split()
    775             except ValueError:
    776                 raise TemplateSyntaxError, "'if' statement improperly formatted"
    777             if not_ != 'not':
    778                 raise TemplateSyntaxError, "Expected 'not' in if statement"
    779             boolvars.append((True, parser.compile_filter(boolvar)))
     693    while True:
     694        negated = False
     695        if bits.pop_lexem('not'):
     696            negated = True
     697        expr = bits.parse_expression(required=True)
     698        boolvars.append((negated, expr))
     699        link = bits.pop_name()
     700        if not link:
     701            break
     702        if link_type:
     703            if link_type != link:
     704                bits.syntax_error("can't mix 'and' and 'or'")
    780705        else:
    781             boolvars.append((False, parser.compile_filter(boolpair)))
    782     nodelist_true = parser.parse(('else', 'endif'))
    783     token = parser.next_token()
    784     if token.contents == 'else':
    785         nodelist_false = parser.parse(('endif',))
    786         parser.delete_first_token()
    787     else:
    788         nodelist_false = NodeList()
     706            if not link in ('and', 'or'):
     707                bits.pushback()
     708                bits.expected("'and' or 'or'")
     709            link_type = link
     710   
     711    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')   
    789712    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    790 do_if = register.tag("if", do_if)
     713do_if = register.tag("if", )
    791714
    792715#@register.tag
    793 def ifchanged(parser, token):
     716#@uses_token_stream
     717def ifchanged(parser, bits):
    794718    """
    795719    Checks if a value has changed from the last iteration of a loop.
    796720
     
    817741                    {{ date.hour }}
    818742                {% endifchanged %}
    819743            {% endfor %}
    820     """
    821     bits = token.contents.split()
    822     nodelist_true = parser.parse(('else', 'endifchanged'))
    823     token = parser.next_token()
    824     if token.contents == 'else':
    825         nodelist_false = parser.parse(('endifchanged',))
    826         parser.delete_first_token()
    827     else:
    828         nodelist_false = NodeList()
    829     return IfChangedNode(nodelist_true, nodelist_false, *bits[1:])
    830 ifchanged = register.tag(ifchanged)
     744    """   
     745    values = bits.parse_expression_list()
     746    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     747    return IfChangedNode(nodelist_true, nodelist_false, values)
     748ifchanged = register.tag(uses_token_stream(ifchanged))
    831749
    832750#@register.tag
    833751def ssi(parser, token):
     
    882800load = register.tag(load)
    883801
    884802#@register.tag
    885 def now(parser, token):
     803#@uses_token_stream
     804def now(parser, bits):
    886805    """
    887806    Displays the date, formatted according to the given string.
    888807
     
    893812
    894813        It is {% now "jS F Y H:i" %}
    895814    """
    896     bits = token.contents.split('"')
    897     if len(bits) != 3:
    898         raise TemplateSyntaxError, "'now' statement takes one argument"
    899     format_string = bits[1]
     815    format_string = bits.parse_expression(required=True)
    900816    return NowNode(format_string)
    901 now = register.tag(now)
     817now = register.tag()
    902818
    903819#@register.tag
    904 def regroup(parser, token):
     820#@uses_token_stream
     821def regroup(parser, bits):
    905822    """
    906823    Regroups a list of alike objects by a common attribute.
    907824
     
    947864        {% regroup people|dictsort:"gender" by gender as grouped %}
    948865
    949866    """
    950     firstbits = token.contents.split(None, 3)
    951     if len(firstbits) != 4:
    952         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
    953     target = parser.compile_filter(firstbits[1])
    954     if firstbits[2] != 'by':
    955         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    956     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    957     if lastbits_reversed[1][::-1] != 'as':
    958         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    959                                   " be 'as'")
    960 
    961     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    962 
    963     var_name = lastbits_reversed[0][::-1]
     867    target = bits.parse_expression(required=True)   
     868    if not bits.pop_lexem('by'):
     869        raise bits.expected("'by'")
     870    expression = bits.parse_expression(required=True)
     871    try:
     872        var_name = parse_as(bits)
     873    except TokenSyntaxError:
     874        raise bits.expected("as <name>")
    964875    return RegroupNode(target, expression, var_name)
    965 regroup = register.tag(regroup)
     876regroup = register.tag()
    966877
    967878def spaceless(parser, token):
    968879    """
     
    989900            </strong>
    990901        {% endspaceless %}
    991902    """
    992     nodelist = parser.parse(('endspaceless',))
    993     parser.delete_first_token()
    994     return SpacelessNode(nodelist)
     903    return SpacelessNode(parser.parse_nodelist(('endspaceless',)))
    995904spaceless = register.tag(spaceless)
    996905
    997906#@register.tag
     
    1028937    return TemplateTagNode(tag)
    1029938templatetag = register.tag(templatetag)
    1030939
    1031 def url(parser, token):
     940#@register.tag
     941#@uses_token_stream
     942def url(parser, bits):
    1032943    """
    1033944    Returns an absolute URL matching given view with its parameters.
    1034945
     
    1059970
    1060971    The URL will look like ``/clients/client/123/``.
    1061972    """
    1062     bits = token.contents.split(' ')
    1063     if len(bits) < 2:
    1064         raise TemplateSyntaxError("'%s' takes at least one argument"
    1065                                   " (path to a view)" % bits[0])
    1066     viewname = bits[1]
    1067     args = []
    1068     kwargs = {}
    1069     asvar = None
    1070        
    1071     if len(bits) > 2:
    1072         bits = iter(bits[2:])
    1073         for bit in bits:
    1074             if bit == 'as':
    1075                 asvar = bits.next()
    1076                 break
    1077             else:
    1078                 for arg in bit.split(","):
    1079                     if '=' in arg:
    1080                         k, v = arg.split('=', 1)
    1081                         k = k.strip()
    1082                         kwargs[k] = parser.compile_filter(v)
    1083                     elif arg:
    1084                         args.append(parser.compile_filter(arg))
    1085     return URLNode(viewname, args, kwargs, asvar)
    1086 url = register.tag(url)
     973    view = bits.parse_string(bare=True, required=True)
     974    args, kwargs = parse_args_and_kwargs(bits, until=('as',))
     975    try:
     976        asvar = parse_as(bits)
     977    except TokenSyntaxError:
     978        asvar = None
     979    return URLNode(view, args, kwargs, asvar)
     980url = register.tag(uses_token_stream(url))
    1087981
    1088982#@register.tag
    1089 def widthratio(parser, token):
     983#@uses_token_stream
     984def widthratio(parser, bits):
    1090985    """
    1091986    For creating bar charts and such, this tag calculates the ratio of a given
    1092987    value to a maximum value, and then applies that ratio to a constant.
     
    1099994    the above example will be 88 pixels wide (because 175/200 = .875;
    1100995    .875 * 100 = 87.5 which is rounded up to 88).
    1101996    """
    1102     bits = token.contents.split()
    1103     if len(bits) != 4:
    1104         raise TemplateSyntaxError("widthratio takes three arguments")
    1105     tag, this_value_expr, max_value_expr, max_width = bits
    1106     try:
    1107         max_width = int(max_width)
    1108     except ValueError:
    1109         raise TemplateSyntaxError("widthratio final argument must be an integer")
    1110     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1111                           parser.compile_filter(max_value_expr), max_width)
    1112 widthratio = register.tag(widthratio)
     997    this_value_expr, max_value_expr = bits.parse_expression_list(count=2)
     998    max_width = bits.parse_int(required=True)
     999    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
     1000widthratio = register.tag(uses_token_stream(widthratio))
    11131001
    11141002#@register.tag
    1115 def do_with(parser, token):
     1003#@uses_token_stream
     1004def do_with(parser, bits):
    11161005    """
    11171006    Adds a value to the context (inside of this block) for caching and easy
    11181007    access.
     
    11221011        {% with person.some_sql_method as total %}
    11231012            {{ total }} object{{ total|pluralize }}
    11241013        {% endwith %}
     1014
    11251015    """
    1126     bits = list(token.split_contents())
    1127     if len(bits) != 4 or bits[2] != "as":
    1128         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1129                                   bits[0])
    1130     var = parser.compile_filter(bits[1])
    1131     name = bits[3]
    1132     nodelist = parser.parse(('endwith',))
    1133     parser.delete_first_token()
    1134     return WithNode(var, name, nodelist)
    1135 do_with = register.tag('with', do_with)
     1016    try:
     1017        expr = bits.parse_expression()
     1018        name = parse_as(bits)
     1019    except TokenSyntaxError:
     1020        bits.expected("value as name")
     1021    nodelist = parser.parse_nodelist(('endwith',))
     1022    return WithNode(expr, name, nodelist)
     1023do_with = register.tag('with', uses_token_stream(do_with))
  • django/template/compat.py

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
     100
     101
     102
     103
     104
     105
     106
     107
     108
     109
     110
     111
     112
     113
     114
     115
     116
     117
     118
     119
     120
     121
     122
     123
  • django/template/loader_tags.py

     
    1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    2 from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     1from django.template import TemplateSyntaxError, Library, Node, TextNode
     2from django.template.compiler import uses_token_stream
     3from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source
     4from django.template.expressions import Literal
    45from django.conf import settings
    56from django.utils.safestring import mark_safe
    67
     
    2829    def super(self):
    2930        if self.parent:
    3031            return mark_safe(self.parent.render(self.context))
    31         return ''
     32        return ''
    3233
    3334    def add_parent(self, nodelist):
    3435        if self.parent:
     
    3940class ExtendsNode(Node):
    4041    must_be_first = True
    4142
    42     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
     43    def __init__(self, nodelist, parent_name, template_dirs=None):
    4344        self.nodelist = nodelist
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
     45        self.parent_name
    4546        self.template_dirs = template_dirs
    4647
    4748    def __repr__(self):
    48         if self.parent_name_expr:
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    5049        return '<ExtendsNode: extends "%s">' % self.parent_name
    5150
    5251    def get_parent(self, context):
    53         if self.parent_name_expr:
    54             self.parent_name = self.parent_name_expr.resolve(context)
    55         parent = self.parent_name
     52        parent = self.parent_name.resolve_safe(context)
    5653        if not parent:
    5754            error_msg = "Invalid template name in 'extends' tag: %r." % parent
    58             if self.parent_name_expr:
    59                 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
    6055            raise TemplateSyntaxError, error_msg
    6156        if hasattr(parent, 'render'):
    6257            return parent # parent is a Template object
     
    114109
    115110class IncludeNode(Node):
    116111    def __init__(self, template_name):
    117         self.template_name = Variable(template_name)
     112        self.template_name =
    118113
    119114    def render(self, context):
    120115        try:
    121             template_name = self.template_name.resolve(context)
     116            template_name = self.template_name.resolve(context)
    122117            t = get_template(template_name)
    123118            return t.render(context)
    124119        except TemplateSyntaxError, e:
    125120            if settings.TEMPLATE_DEBUG:
    126121                raise
    127             return ''
     122            return ''
    128123        except:
    129             return '' # Fail silently for invalid included templates.
     124            return '' # Fail silently for invalid included templates.
    130125
     126
     127
    131128def do_block(parser, token):
    132129    """
    133130    Define a block that can be overridden by child templates.
     
    144141        parser.__loaded_blocks.append(block_name)
    145142    except AttributeError: # parser.__loaded_blocks isn't a list yet
    146143        parser.__loaded_blocks = [block_name]
    147     nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
    148     parser.delete_first_token()
     144    nodelist = parser.parse_nodelist(('endblock', 'endblock %s' % block_name))
    149145    return BlockNode(block_name, nodelist)
     146
    150147
    151 def do_extends(parser, token):
     148
     149#@register.tag('extends')
     150#@uses_token_stream
     151def do_extends(parser, bits):
    152152    """
    153153    Signal that this template extends a parent template.
    154154
    155155    This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
    156156    uses the literal value "base" as the name of the parent template to extend,
    157     or ``{% extends variable %}`` uses the value of ``variable`` as either the
     157    or ``{% extends `` as either the
    158158    name of the parent template to extend (if it evaluates to a string) or as
    159159    the parent tempate itelf (if it evaluates to a Template object).
    160160    """
    161     bits = token.contents.split()
    162     if len(bits) != 2:
    163         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
    164     parent_name, parent_name_expr = None, None
    165     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
    166         parent_name = bits[1][1:-1]
    167     else:
    168         parent_name_expr = parser.compile_filter(bits[1])
     161    parent_name = bits.parse_expression(required=True)
    169162    nodelist = parser.parse()
    170163    if nodelist.get_nodes_by_type(ExtendsNode):
    171         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
    172     return ExtendsNode(nodelist, parent_name, parent_name_expr)
     164        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits.name
     165    return ExtendsNode(nodelist, parent_name)
     166register.tag('extends', uses_token_stream(do_extends))
    173167
    174 def do_include(parser, token):
     168
     169#@register.tag('include')
     170#@uses_token_stream
     171def do_include(parser, bits):
    175172    """
    176173    Loads a template and renders it with the current context.
    177174
     
    179176
    180177        {% include "foo/some_include" %}
    181178    """
    182     bits = token.contents.split()
    183     if len(bits) != 2:
    184         raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
    185     path = bits[1]
    186     if path[0] in ('"', "'") and path[-1] == path[0]:
    187         return ConstantIncludeNode(path[1:-1])
    188     return IncludeNode(bits[1])
    189 
    190 register.tag('block', do_block)
    191 register.tag('extends', do_extends)
    192 register.tag('include', do_include)
     179    template_name = bits.parse_expression(required=True)
     180    if isinstance(template_name, Literal):
     181        # remove ConstantIncludeNode and this hack will be gone
     182        return ConstantIncludeNode(template_name.resolve(None))
     183    return IncludeNode(template_name)
     184register.tag('include', uses_token_stream(do_include))
  • django/template/library.py

     
     1
     2
     3
     4
     5
     6
     7
     8
     9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
     100
     101
     102
     103
     104
     105
     106
     107
     108
     109
     110
     111
     112
     113
     114
     115
     116
     117
     118
     119
     120
     121
     122
     123
     124
     125
     126
     127
     128
     129
     130
     131
     132
     133
     134
     135
     136
     137
     138
     139
     140
     141
     142
     143
     144
     145
     146
     147
     148
     149
     150
     151
     152
     153
     154
     155
     156
     157
     158
     159
     160
     161
  • django/template/debug.py

     
    1 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
    2 from django.utils.encoding import force_unicode
    3 from django.utils.html import escape
    4 from django.utils.safestring import SafeData, EscapeData
     1from django.template.compiler import Lexer, Parser, Token, TokenStream, tag_re, bit_re
     2from django.template import NodeList, ExpressionNode, TemplateSyntaxError
     3from django.utils.encoding import force_unicode, smart_str
    54
    65class DebugLexer(Lexer):
    76    def __init__(self, template_string, origin):
     
    5049        return DebugNodeList()
    5150
    5251    def create_variable_node(self, contents):
    53         return DebugVariableNode(contents)
     52        return DebugNode(contents)
    5453
    5554    def extend_nodelist(self, nodelist, node, token):
    5655        node.source = token.source
     
    6564        if not hasattr(e, 'source'):
    6665            e.source = token.source
    6766
     67
     68
     69
     70
    6871class DebugNodeList(NodeList):
    6972    def render_node(self, node, context):
    7073        try:
     
    8184            raise wrapped
    8285        return result
    8386
    84 class DebugVariableNode(VariableNode):
     87class DebugNode):
    8588    def render(self, context):
    8689        try:
    87             output = force_unicode(self.filter_expression.resolve(context))
     90            )
    8891        except TemplateSyntaxError, e:
    8992            if not hasattr(e, 'source'):
    9093                e.source = self.source
    9194            raise
    92         except UnicodeDecodeError:
    93             return ''
    94         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
    95             return escape(output)
    96         else:
    97             return output
     95           
     96class DebugTokenStream(TokenStream):
     97    def syntax_error(self, msg):
     98        if self.name:
     99            msg = u"{%% %s %%} %s" % (self.name, msg)           
     100        if self.token:
     101            raise self.parser.source_error(self.token.source, msg)
     102        raise TemplateSyntaxError(msg)
     103           
  • django/template/loader.py

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

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