~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/template/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
 
35
35
Sample code:
36
36
 
37
 
>>> import template
38
 
>>> s = '''
39
 
... <html>
40
 
... {% if test %}
41
 
...     <h1>{{ varvalue }}</h1>
42
 
... {% endif %}
43
 
... </html>
44
 
... '''
 
37
>>> from django import template
 
38
>>> s = u'<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
45
39
>>> t = template.Template(s)
46
40
 
47
41
(t is now a compiled template, and its render() method can be called multiple
49
43
 
50
44
>>> c = template.Context({'test':True, 'varvalue': 'Hello'})
51
45
>>> t.render(c)
52
 
'\n<html>\n\n    <h1>Hello</h1>\n\n</html>\n'
 
46
u'<html><h1>Hello</h1></html>'
53
47
>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
54
48
>>> t.render(c)
55
 
'\n<html>\n\n</html>\n'
 
49
u'<html></html>'
56
50
"""
57
51
import re
58
52
from inspect import getargspec
59
53
from django.conf import settings
60
54
from django.template.context import Context, RequestContext, ContextPopException
61
 
from django.utils.functional import curry
 
55
from django.utils.itercompat import is_iterable
 
56
from django.utils.functional import curry, Promise
62
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
63
62
 
64
63
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
65
64
 
91
90
tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
92
91
                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
93
92
                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
94
 
# matches if the string is valid number
95
 
number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$')
96
93
 
97
94
# global dictionary of libraries that have been loaded using get_library
98
95
libraries = {}
99
96
# global list of libraries to load by default for a new parser
100
97
builtins = []
101
98
 
 
99
# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
 
100
# uninitialised.
 
101
invalid_var_format_string = None
 
102
 
102
103
class TemplateSyntaxError(Exception):
103
104
    def __str__(self):
104
105
        try:
118
119
class TemplateDoesNotExist(Exception):
119
120
    pass
120
121
 
 
122
class TemplateEncodingError(Exception):
 
123
    pass
 
124
 
121
125
class VariableDoesNotExist(Exception):
122
126
 
123
127
    def __init__(self, msg, params=()):
124
128
        self.msg = msg
125
129
        self.params = params
126
 
    
 
130
 
127
131
    def __str__(self):
128
 
        return self.msg % self.params
129
 
    
 
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
 
130
137
class InvalidTemplateLibrary(Exception):
131
138
    pass
132
139
 
150
157
 
151
158
class Template(object):
152
159
    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
153
 
        "Compilation stage"
154
 
        if settings.TEMPLATE_DEBUG and origin == None:
 
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:
155
165
            origin = StringOrigin(template_string)
156
 
            # Could do some crazy stack-frame stuff to record where this string
157
 
            # came from...
158
166
        self.nodelist = compile_string(template_string, origin)
159
167
        self.name = name
160
168
 
169
177
 
170
178
def compile_string(template_string, origin):
171
179
    "Compiles template_string into NodeList ready for rendering"
172
 
    lexer = lexer_factory(template_string, origin)
173
 
    parser = parser_factory(lexer.tokenize())
 
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())
174
187
    return parser.parse()
175
188
 
176
189
class Token(object):
177
190
    def __init__(self, token_type, contents):
178
 
        "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT"
 
191
        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
179
192
        self.token_type, self.contents = token_type, contents
180
193
 
181
194
    def __str__(self):
184
197
            self.contents[:20].replace('\n', ''))
185
198
 
186
199
    def split_contents(self):
187
 
        return list(smart_split(self.contents))
 
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
188
213
 
189
214
class Lexer(object):
190
215
    def __init__(self, template_string, origin):
192
217
        self.origin = origin
193
218
 
194
219
    def tokenize(self):
195
 
        "Return a list of tokens from a given template_string"
196
 
        # remove all empty strings, because the regex has a tendency to add them
197
 
        bits = filter(None, tag_re.split(self.template_string))
198
 
        return map(self.create_token, bits)
 
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
199
228
 
200
 
    def create_token(self,token_string):
201
 
        "Convert the given token string into a new Token object and return it"
202
 
        if token_string.startswith(VARIABLE_TAG_START):
203
 
            token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
204
 
        elif token_string.startswith(BLOCK_TAG_START):
205
 
            token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
206
 
        elif token_string.startswith(COMMENT_TAG_START):
207
 
            token = Token(TOKEN_COMMENT, '')
 
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, '')
208
242
        else:
209
243
            token = Token(TOKEN_TEXT, token_string)
210
244
        return token
211
245
 
212
 
class DebugLexer(Lexer):
213
 
    def __init__(self, template_string, origin):
214
 
        super(DebugLexer, self).__init__(template_string, origin)
215
 
 
216
 
    def tokenize(self):
217
 
        "Return a list of tokens from a given template_string"
218
 
        token_tups, upto = [], 0
219
 
        for match in tag_re.finditer(self.template_string):
220
 
            start, end = match.span()
221
 
            if start > upto:
222
 
                token_tups.append( (self.template_string[upto:start], (upto, start)) )
223
 
                upto = start
224
 
            token_tups.append( (self.template_string[start:end], (start,end)) )
225
 
            upto = end
226
 
        last_bit = self.template_string[upto:]
227
 
        if last_bit:
228
 
            token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
229
 
        return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
230
 
 
231
 
    def create_token(self, token_string, source):
232
 
        token = super(DebugLexer, self).create_token(token_string)
233
 
        token.source = source
234
 
        return token
235
 
 
236
246
class Parser(object):
237
247
    def __init__(self, tokens):
238
248
        self.tokens = tokens
294
304
        return NodeList()
295
305
 
296
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
297
315
        nodelist.append(node)
298
316
 
299
317
    def enter_command(self, command, token):
302
320
    def exit_command(self):
303
321
        pass
304
322
 
305
 
    def error(self, token, msg ):
 
323
    def error(self, token, msg):
306
324
        return TemplateSyntaxError(msg)
307
325
 
308
326
    def empty_variable(self, token):
309
 
        raise self.error( token, "Empty variable tag")
 
327
        raise self.error(token, "Empty variable tag")
310
328
 
311
329
    def empty_block_tag(self, token):
312
 
        raise self.error( token, "Empty block tag")
 
330
        raise self.error(token, "Empty block tag")
313
331
 
314
332
    def invalid_block_tag(self, token, command):
315
 
        raise self.error( token, "Invalid block tag: '%s'" % command)
 
333
        raise self.error(token, "Invalid block tag: '%s'" % command)
316
334
 
317
335
    def unclosed_block_tag(self, parse_until):
318
336
        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
338
356
        return FilterExpression(token, self)
339
357
 
340
358
    def find_filter(self, filter_name):
341
 
        if self.filters.has_key(filter_name):
 
359
        if filter_name in self.filters:
342
360
            return self.filters[filter_name]
343
361
        else:
344
 
            raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
345
 
 
346
 
class DebugParser(Parser):
347
 
    def __init__(self, lexer):
348
 
        super(DebugParser, self).__init__(lexer)
349
 
        self.command_stack = []
350
 
 
351
 
    def enter_command(self, command, token):
352
 
        self.command_stack.append( (command, token.source) )
353
 
 
354
 
    def exit_command(self):
355
 
        self.command_stack.pop()
356
 
 
357
 
    def error(self, token, msg):
358
 
        return self.source_error(token.source, msg)
359
 
 
360
 
    def source_error(self, source,msg):
361
 
        e = TemplateSyntaxError(msg)
362
 
        e.source = source
363
 
        return e
364
 
 
365
 
    def create_nodelist(self):
366
 
        return DebugNodeList()
367
 
 
368
 
    def create_variable_node(self, contents):
369
 
        return DebugVariableNode(contents)
370
 
 
371
 
    def extend_nodelist(self, nodelist, node, token):
372
 
        node.source = token.source
373
 
        super(DebugParser, self).extend_nodelist(nodelist, node, token)
374
 
 
375
 
    def unclosed_block_tag(self, parse_until):
376
 
        command, source = self.command_stack.pop()
377
 
        msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
378
 
        raise self.source_error( source, msg)
379
 
 
380
 
    def compile_function_error(self, token, e):
381
 
        if not hasattr(e, 'source'):
382
 
            e.source = token.source
383
 
 
384
 
def lexer_factory(*args, **kwargs):
385
 
    if settings.TEMPLATE_DEBUG:
386
 
        return DebugLexer(*args, **kwargs)
387
 
    else:
388
 
        return Lexer(*args, **kwargs)
389
 
 
390
 
def parser_factory(*args, **kwargs):
391
 
    if settings.TEMPLATE_DEBUG:
392
 
        return DebugParser(*args, **kwargs)
393
 
    else:
394
 
        return Parser(*args, **kwargs)
 
362
            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
395
363
 
396
364
class TokenParser(object):
397
365
    """
409
377
 
410
378
    def top(self):
411
379
        "Overload this method to do the actual parsing and return the result."
412
 
        raise NotImplemented
 
380
        raise NotImplementedError()
413
381
 
414
382
    def more(self):
415
383
        "Returns True if there is more stuff in the tag."
418
386
    def back(self):
419
387
        "Undoes the last microparser. Use this for lookahead and backtracking."
420
388
        if not len(self.backout):
421
 
            raise TemplateSyntaxError, "back called without some previous parsing"
 
389
            raise TemplateSyntaxError("back called without some previous parsing")
422
390
        self.pointer = self.backout.pop()
423
391
 
424
392
    def tag(self):
426
394
        subject = self.subject
427
395
        i = self.pointer
428
396
        if i >= len(subject):
429
 
            raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
 
397
            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
430
398
        p = i
431
399
        while i < len(subject) and subject[i] not in (' ', '\t'):
432
400
            i += 1
442
410
        subject = self.subject
443
411
        i = self.pointer
444
412
        if i >= len(subject):
445
 
            raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject
 
413
            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
446
414
        if subject[i] in ('"', "'"):
447
415
            p = i
448
416
            i += 1
449
417
            while i < len(subject) and subject[i] != subject[p]:
450
418
                i += 1
451
419
            if i >= len(subject):
452
 
                raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject)
 
420
                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
453
421
            i += 1
454
422
            res = subject[p:i]
455
423
            while i < len(subject) and subject[i] in (' ', '\t'):
466
434
                    while i < len(subject) and subject[i] != c:
467
435
                        i += 1
468
436
                    if i >= len(subject):
469
 
                        raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
 
437
                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
470
438
                i += 1
471
439
            s = subject[p:i]
472
440
            while i < len(subject) and subject[i] in (' ', '\t'):
475
443
            self.pointer = i
476
444
            return s
477
445
 
478
 
 
479
 
 
480
 
 
481
446
filter_raw_string = r"""
482
447
^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
483
448
^"(?P<constant>%(str)s)"|
493
458
         )?
494
459
 )""" % {
495
460
    'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
496
 
    'var_chars': "A-Za-z0-9\_\." ,
 
461
    'var_chars': "\w\." ,
497
462
    'filter_sep': re.escape(FILTER_SEPARATOR),
498
463
    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
499
464
    'i18n_open' : re.escape("_("),
501
466
  }
502
467
 
503
468
filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
504
 
filter_re = re.compile(filter_raw_string)
 
469
filter_re = re.compile(filter_raw_string, re.UNICODE)
505
470
 
506
471
class FilterExpression(object):
507
472
    """
509
474
    and return a list of tuples of the filter name and arguments.
510
475
    Sample:
511
476
        >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
512
 
        >>> p = FilterParser(token)
513
 
        >>> p.filters
514
 
        [('default', 'Default value'), ('date', 'Y-m-d')]
515
 
        >>> p.var
516
 
        'variable'
 
477
        >>> p = Parser('')
 
478
        >>> fe = FilterExpression(token, p)
 
479
        >>> len(fe.filters)
 
480
        2
 
481
        >>> fe.var
 
482
        <Variable: 'variable'>
517
483
 
518
484
    This class should never be instantiated outside of the
519
485
    get_filters_from_token helper function.
527
493
        for match in matches:
528
494
            start = match.start()
529
495
            if upto != start:
530
 
                raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s"  % \
531
 
                                           (token[:upto], token[upto:start], token[start:])
 
496
                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
 
497
                                           (token[:upto], token[upto:start], token[start:]))
532
498
            if var == None:
533
499
                var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
534
 
                if i18n_constant:
535
 
                    var = '"%s"' %  _(i18n_constant)
536
 
                elif constant:
537
 
                    var = '"%s"' % 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'\"', '"')
538
509
                upto = match.end()
539
510
                if var == None:
540
 
                    raise TemplateSyntaxError, "Could not find variable at start of %s" % token
 
511
                    raise TemplateSyntaxError("Could not find variable at start of %s" % token)
541
512
                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
542
 
                    raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
 
513
                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
543
514
            else:
544
515
                filter_name = match.group("filter_name")
545
516
                args = []
549
520
                elif constant_arg is not None:
550
521
                    args.append((False, constant_arg.replace(r'\"', '"')))
551
522
                elif var_arg:
552
 
                    args.append((True, var_arg))
 
523
                    args.append((True, Variable(var_arg)))
553
524
                filter_func = parser.find_filter(filter_name)
554
525
                self.args_check(filter_name,filter_func, args)
555
526
                filters.append( (filter_func,args))
556
527
                upto = match.end()
557
528
        if upto != len(token):
558
 
            raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
559
 
        self.var, self.filters = var, filters
 
529
            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
 
530
        self.filters = filters
 
531
        self.var = Variable(var)
560
532
 
561
533
    def resolve(self, context, ignore_failures=False):
562
534
        try:
563
 
            obj = resolve_variable(self.var, context)
 
535
            obj = self.var.resolve(context)
564
536
        except VariableDoesNotExist:
565
537
            if ignore_failures:
566
538
                obj = None
567
539
            else:
568
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
569
546
                    return settings.TEMPLATE_STRING_IF_INVALID
570
547
                else:
571
548
                    obj = settings.TEMPLATE_STRING_IF_INVALID
573
550
            arg_vals = []
574
551
            for lookup, arg in args:
575
552
                if not lookup:
576
 
                    arg_vals.append(arg)
 
553
                    arg_vals.append(mark_safe(arg))
577
554
                else:
578
 
                    arg_vals.append(resolve_variable(arg, context))
579
 
            obj = func(obj, *arg_vals)
 
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
580
566
        return obj
581
567
 
582
568
    def args_check(name, func, provided):
597
583
                provided.pop(0)
598
584
        except IndexError:
599
585
            # Not enough
600
 
            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
 
586
            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
601
587
 
602
588
        # Defaults can be overridden.
603
589
        defaults = defaults and list(defaults) or []
606
592
                defaults.pop(0)
607
593
        except IndexError:
608
594
            # Too many.
609
 
            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
 
595
            raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
610
596
 
611
597
        return True
612
598
    args_check = staticmethod(args_check)
617
603
def resolve_variable(path, context):
618
604
    """
619
605
    Returns the resolved variable, which may contain attribute syntax, within
620
 
    the given context. The variable may be a hard-coded string (if it begins
621
 
    and ends with single or double quote marks).
622
 
 
623
 
    >>> c = {'article': {'section':'News'}}
624
 
    >>> resolve_variable('article.section', c)
625
 
    'News'
626
 
    >>> resolve_variable('article', c)
627
 
    {'section': 'News'}
628
 
    >>> class AClass: pass
629
 
    >>> c = AClass()
630
 
    >>> c.article = AClass()
631
 
    >>> c.article.section = 'News'
632
 
    >>> resolve_variable('article.section', c)
633
 
    'News'
 
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'
634
629
 
635
630
    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
636
631
    """
637
 
    if number_re.match(path):
638
 
        number_type = '.' in path and float or int
639
 
        current = number_type(path)
640
 
    elif path[0] in ('"', "'") and path[0] == path[-1]:
641
 
        current = path[1:-1]
642
 
    else:
 
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
        """
643
699
        current = context
644
 
        bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
645
 
        while bits:
 
700
        for bit in self.lookups:
646
701
            try: # dictionary lookup
647
 
                current = current[bits[0]]
 
702
                current = current[bit]
648
703
            except (TypeError, AttributeError, KeyError):
649
704
                try: # attribute lookup
650
 
                    current = getattr(current, bits[0])
 
705
                    current = getattr(current, bit)
651
706
                    if callable(current):
652
707
                        if getattr(current, 'alters_data', False):
653
708
                            current = settings.TEMPLATE_STRING_IF_INVALID
665
720
                                    raise
666
721
                except (TypeError, AttributeError):
667
722
                    try: # list-index lookup
668
 
                        current = current[int(bits[0])]
 
723
                        current = current[int(bit)]
669
724
                    except (IndexError, # list index out of range
670
725
                            ValueError, # invalid literal for int()
671
 
                            KeyError,   # current is a dict without `int(bits[0])` key
 
726
                            KeyError,   # current is a dict without `int(bit)` key
672
727
                            TypeError,  # unsubscriptable object
673
728
                            ):
674
 
                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute
 
729
                        raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
675
730
                except Exception, e:
676
731
                    if getattr(e, 'silent_variable_failure', False):
677
732
                        current = settings.TEMPLATE_STRING_IF_INVALID
678
733
                    else:
679
734
                        raise
680
 
            del bits[0]
681
 
    return current
 
735
 
 
736
        return current
682
737
 
683
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
 
684
743
    def render(self, context):
685
744
        "Return the node rendered as a string"
686
745
        pass
698
757
        return nodes
699
758
 
700
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
 
701
764
    def render(self, context):
702
765
        bits = []
703
766
        for node in self:
705
768
                bits.append(self.render_node(node, context))
706
769
            else:
707
770
                bits.append(node)
708
 
        return ''.join(bits)
 
771
        return mark_safe(''.join([force_unicode(b) for b in bits]))
709
772
 
710
773
    def get_nodes_by_type(self, nodetype):
711
774
        "Return a list of all nodes of the given type"
715
778
        return nodes
716
779
 
717
780
    def render_node(self, node, context):
718
 
        return(node.render(context))
719
 
 
720
 
class DebugNodeList(NodeList):
721
 
    def render_node(self, node, context):
722
 
        try:
723
 
            result = node.render(context)
724
 
        except TemplateSyntaxError, e:
725
 
            if not hasattr(e, 'source'):
726
 
                e.source = node.source
727
 
            raise
728
 
        except Exception, e:
729
 
            from sys import exc_info
730
 
            wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
731
 
            wrapped.source = node.source
732
 
            wrapped.exc_info = exc_info()
733
 
            raise wrapped
734
 
        return result
 
781
        return node.render(context)
735
782
 
736
783
class TextNode(Node):
737
784
    def __init__(self, s):
750
797
    def __repr__(self):
751
798
        return "<Variable Node: %s>" % self.filter_expression
752
799
 
753
 
    def encode_output(self, output):
754
 
        # Check type so that we don't run str() on a Unicode object
755
 
        if not isinstance(output, basestring):
756
 
            try:
757
 
                return str(output)
758
 
            except UnicodeEncodeError:
759
 
                # If __str__() returns a Unicode object, convert it to bytestring.
760
 
                return unicode(output).encode(settings.DEFAULT_CHARSET)
761
 
        elif isinstance(output, unicode):
762
 
            return output.encode(settings.DEFAULT_CHARSET)
 
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))
763
809
        else:
764
 
            return output
765
 
 
766
 
    def render(self, context):
767
 
        output = self.filter_expression.resolve(context)
768
 
        return self.encode_output(output)
769
 
 
770
 
class DebugVariableNode(VariableNode):
771
 
    def render(self, context):
772
 
        try:
773
 
            output = self.filter_expression.resolve(context)
774
 
        except TemplateSyntaxError, e:
775
 
            if not hasattr(e, 'source'):
776
 
                e.source = self.source
777
 
            raise
778
 
        return self.encode_output(output)
 
810
            return force_unicode(output)
779
811
 
780
812
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
781
813
    "Returns a template.Node subclass."
788
820
            message = "%s takes %s arguments" % (name, bmin)
789
821
        else:
790
822
            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
791
 
        raise TemplateSyntaxError, message
 
823
        raise TemplateSyntaxError(message)
792
824
    return node_class(bits)
793
825
 
794
826
class Library(object):
814
846
            self.tags[name] = compile_function
815
847
            return compile_function
816
848
        else:
817
 
            raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
 
849
            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
818
850
 
819
851
    def tag_function(self,func):
820
852
        self.tags[getattr(func, "_decorated_function", func).__name__] = func
838
870
            self.filters[name] = filter_func
839
871
            return filter_func
840
872
        else:
841
 
            raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)
 
873
            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
842
874
 
843
875
    def filter_function(self, func):
844
876
        self.filters[getattr(func, "_decorated_function", func).__name__] = func
849
881
 
850
882
        class SimpleNode(Node):
851
883
            def __init__(self, vars_to_resolve):
852
 
                self.vars_to_resolve = vars_to_resolve
 
884
                self.vars_to_resolve = map(Variable, vars_to_resolve)
853
885
 
854
886
            def render(self, context):
855
 
                resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
 
887
                resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
856
888
                return func(*resolved_vars)
857
889
 
858
890
        compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
867
899
                if params[0] == 'context':
868
900
                    params = params[1:]
869
901
                else:
870
 
                    raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
 
902
                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
871
903
 
872
904
            class InclusionNode(Node):
873
905
                def __init__(self, vars_to_resolve):
874
 
                    self.vars_to_resolve = vars_to_resolve
 
906
                    self.vars_to_resolve = map(Variable, vars_to_resolve)
875
907
 
876
908
                def render(self, context):
877
 
                    resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
 
909
                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
878
910
                    if takes_context:
879
911
                        args = [context] + resolved_vars
880
912
                    else:
884
916
 
885
917
                    if not getattr(self, 'nodelist', False):
886
918
                        from django.template.loader import get_template, select_template
887
 
                        if hasattr(file_name, '__iter__'):
 
919
                        if not isinstance(file_name, basestring) and is_iterable(file_name):
888
920
                            t = select_template(file_name)
889
921
                        else:
890
922
                            t = get_template(file_name)
891
923
                        self.nodelist = t.nodelist
892
 
                    return self.nodelist.render(context_class(dict))
 
924
                    return self.nodelist.render(context_class(dict,
 
925
                            autoescape=context.autoescape))
893
926
 
894
927
            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
895
928
            compile_func.__doc__ = func.__doc__
903
936
        try:
904
937
            mod = __import__(module_name, {}, {}, [''])
905
938
        except ImportError, e:
906
 
            raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
 
939
            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
907
940
        try:
908
941
            lib = mod.register
909
942
            libraries[module_name] = lib
910
943
        except AttributeError:
911
 
            raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name
 
944
            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
912
945
    return lib
913
946
 
914
947
def add_to_builtins(module_name):