Django

Code

root/django/branches/newforms-admin/django/template/__init__.py

Revision 7584, 34.7 kB (checked in by brosner, 6 months ago)

newforms-admin: Merged from trunk up to [7583].

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """
2 This is the Django template system.
3
4 How it works:
5
6 The Lexer.tokenize() function converts a template string (i.e., a string containing
7 markup with custom template tags) to tokens, which can be either plain text
8 (TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
9
10 The Parser() class takes a list of tokens in its constructor, and its parse()
11 method returns a compiled template -- which is, under the hood, a list of
12 Node objects.
13
14 Each 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
16 logic (IfNode), results of looping (ForNode), or anything else. The core Node
17 types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
18 define their own custom node types.
19
20 Each Node has a render() method, which takes a Context and returns a string of
21 the rendered node. For example, the render() method of a Variable Node returns
22 the variable's value as a string. The render() method of an IfNode returns the
23 rendered output of whatever was inside the loop, recursively.
24
25 The Template class is a convenient wrapper that takes care of template
26 compilation and rendering.
27
28 Usage:
29
30 The only thing you should ever use directly in this file is the Template class.
31 Create a compiled template object with a template_string, then call render()
32 with a context. In the compilation stage, the TemplateSyntaxError exception
33 will be raised if the template doesn't have proper syntax.
34
35 Sample code:
36
37 >>> from django import template
38 >>> s = u'<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
39 >>> t = template.Template(s)
40
41 (t is now a compiled template, and its render() method can be called multiple
42 times with multiple contexts)
43
44 >>> c = template.Context({'test':True, 'varvalue': 'Hello'})
45 >>> t.render(c)
46 u'<html><h1>Hello</h1></html>'
47 >>> c = template.Context({'test':False, 'varvalue': 'Hello'})
48 >>> t.render(c)
49 u'<html></html>'
50 """
51 import re
52 from inspect import getargspec
53 from django.conf import settings
54 from 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
62
63 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
64
65 TOKEN_TEXT = 0
66 TOKEN_VAR = 1
67 TOKEN_BLOCK = 2
68 TOKEN_COMMENT = 3
69
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
99 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
100 # uninitialised.
101 invalid_var_format_string = None
102
103 class TemplateSyntaxError(Exception):
104     def __str__(self):
105         try:
106             import cStringIO as StringIO
107         except ImportError:
108             import StringIO
109         output = StringIO.StringIO()
110         output.write(Exception.__str__(self))
111         # Check if we wrapped an exception and print that too.
112         if hasattr(self, 'exc_info'):
113             import traceback
114             output.write('\n\nOriginal ')
115             e = self.exc_info
116             traceback.print_exception(e[0], e[1], e[2], 500, output)
117         return output.getvalue()
118
119 class TemplateDoesNotExist(Exception):
120     pass
121
122 class TemplateEncodingError(Exception):
123     pass
124
125 class VariableDoesNotExist(Exception):
126
127     def __init__(self, msg, params=()):
128         self.msg = msg
129         self.params = params
130
131     def __str__(self):
132         return unicode(self).encode('utf-8')
133
134     def __unicode__(self):
135         return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
136
137 class InvalidTemplateLibrary(Exception):
138     pass
139
140 class Origin(object):
141     def __init__(self, name):
142         self.name = name
143
144     def reload(self):
145         raise NotImplementedError
146
147     def __str__(self):
148         return self.name
149
150 class StringOrigin(Origin):
151     def __init__(self, source):
152         super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
153         self.source = source
154
155     def reload(self):
156         return self.source
157
158 class Template(object):
159     def __init__(self, template_string, origin=None, name='<Unknown Template>'):
160         try:
161             template_string = smart_unicode(template_string)
162         except UnicodeDecodeError:
163             raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
164         if settings.TEMPLATE_DEBUG and origin is None:
165             origin = StringOrigin(template_string)
166         self.nodelist = compile_string(template_string, origin)
167         self.name = name
168
169     def __iter__(self):
170         for node in self.nodelist:
171             for subnode in node:
172                 yield subnode
173
174     def render(self, context):
175         "Display stage -- can be called many times"
176         return self.nodelist.render(context)
177
178 def compile_string(template_string, origin):
179     "Compiles template_string into NodeList ready for rendering"
180     if settings.TEMPLATE_DEBUG:
181         from debug import DebugLexer, DebugParser
182         lexer_class, parser_class = DebugLexer, DebugParser
183     else:
184         lexer_class, parser_class = Lexer, Parser
185     lexer = lexer_class(template_string, origin)
186     parser = parser_class(lexer.tokenize())
187     return parser.parse()
188
189 class Token(object):
190     def __init__(self, token_type, contents):
191         # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
192         self.token_type, self.contents = token_type, contents
193
194     def __str__(self):
195         return '<%s token: "%s...">' % \
196             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
197             self.contents[:20].replace('\n', ''))
198
199     def split_contents(self):
200         return list(smart_split(self.contents))
201
202 class Lexer(object):
203     def __init__(self, template_string, origin):
204         self.template_string = template_string
205         self.origin = origin
206
207     def tokenize(self):
208         "Return a list of tokens from a given template_string."
209         in_tag = False
210         result = []
211         for bit in tag_re.split(self.template_string):
212             if bit:
213                 result.append(self.create_token(bit, in_tag))
214             in_tag = not in_tag
215         return result
216
217     def create_token(self, token_string, in_tag):
218         """
219         Convert the given token string into a new Token object and return it.
220         If in_tag is True, we are processing something that matched a tag,
221         otherwise it should be treated as a literal string.
222         """
223         if in_tag:
224             if token_string.startswith(VARIABLE_TAG_START):
225                 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
226             elif token_string.startswith(BLOCK_TAG_START):
227                 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
228             elif token_string.startswith(COMMENT_TAG_START):
229                 token = Token(TOKEN_COMMENT, '')
230         else:
231             token = Token(TOKEN_TEXT, token_string)
232         return token
233
234 class Parser(object):
235     def __init__(self, tokens):
236         self.tokens = tokens
237         self.tags = {}
238         self.filters = {}
239         for lib in builtins:
240             self.add_library(lib)
241
242     def parse(self, parse_until=None):
243         if parse_until is None: parse_until = []
244         nodelist = self.create_nodelist()
245         while self.tokens:
246             token = self.next_token()
247             if token.token_type == TOKEN_TEXT:
248                 self.extend_nodelist(nodelist, TextNode(token.contents), token)
249             elif token.token_type == TOKEN_VAR:
250                 if not token.contents:
251                     self.empty_variable(token)
252                 filter_expression = self.compile_filter(token.contents)
253                 var_node = self.create_variable_node(filter_expression)
254                 self.extend_nodelist(nodelist, var_node,token)
255             elif token.token_type == TOKEN_BLOCK:
256                 if token.contents in parse_until:
257                     # put token back on token list so calling code knows why it terminated
258                     self.prepend_token(token)
259                     return nodelist
260                 try:
261                     command = token.contents.split()[0]
262                 except IndexError:
263                     self.empty_block_tag(token)
264                 # execute callback function for this tag and append resulting node
265                 self.enter_command(command, token)
266                 try:
267                     compile_func = self.tags[command]
268                 except KeyError:
269                     self.invalid_block_tag(token, command)
270                 try:
271                     compiled_result = compile_func(self, token)
272                 except TemplateSyntaxError, e:
273                     if not self.compile_function_error(token, e):
274                         raise
275                 self.extend_nodelist(nodelist, compiled_result, token)
276                 self.exit_command()
277         if parse_until:
278             self.unclosed_block_tag(parse_until)
279         return nodelist
280
281     def skip_past(self, endtag):
282         while self.tokens:
283             token = self.next_token()
284             if token.token_type == TOKEN_BLOCK and token.contents == endtag:
285                 return
286         self.unclosed_block_tag([endtag])
287
288     def create_variable_node(self, filter_expression):
289         return VariableNode(filter_expression)
290
291     def create_nodelist(self):
292         return NodeList()
293
294     def extend_nodelist(self, nodelist, node, token):
295         if node.must_be_first and nodelist:
296             try:
297                 if nodelist.contains_nontext:
298                     raise AttributeError
299             except AttributeError:
300                 raise TemplateSyntaxError("%r must be the first tag in the template." % node)
301         if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
302             nodelist.contains_nontext = True
303         nodelist.append(node)
304
305     def enter_command(self, command, token):
306         pass
307
308     def exit_command(self):
309         pass
310
311     def error(self, token, msg):
312         return TemplateSyntaxError(msg)
313
314     def empty_variable(self, token):
315         raise self.error(token, "Empty variable tag")
316
317     def empty_block_tag(self, token):
318         raise self.error(token, "Empty block tag")
319
320     def invalid_block_tag(self, token, command):
321         raise self.error(token, "Invalid block tag: '%s'" % command)
322
323     def unclosed_block_tag(self, parse_until):
324         raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
325
326     def compile_function_error(self, token, e):
327         pass
328
329     def next_token(self):
330         return self.tokens.pop(0)
331
332     def prepend_token(self, token):
333         self.tokens.insert(0, token)
334
335     def delete_first_token(self):
336         del self.tokens[0]
337
338     def add_library(self, lib):
339         self.tags.update(lib.tags)
340         self.filters.update(lib.filters)
341
342     def compile_filter(self, token):
343         "Convenient wrapper for FilterExpression"
344         return FilterExpression(token, self)
345
346     def find_filter(self, filter_name):
347         if filter_name in self.filters:
348             return self.filters[filter_name]
349         else:
350             raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
351
352 class TokenParser(object):
353     """
354     Subclass this and implement the top() method to parse a template line. When
355     instantiating the parser, pass in the line from the Django template parser.
356
357     The parser's "tagname" instance-variable stores the name of the tag that
358     the filter was called with.
359     """
360     def __init__(self, subject):
361         self.subject = subject
362         self.pointer = 0
363         self.backout = []
364         self.tagname = self.tag()
365
366     def top(self):
367         "Overload this method to do the actual parsing and return the result."
368         raise NotImplementedError()
369
370     def more(self):
371         "Returns True if there is more stuff in the tag."
372         return self.pointer < len(self.subject)
373
374     def back(self):
375         "Undoes the last microparser. Use this for lookahead and backtracking."
376         if not len(self.backout):
377             raise TemplateSyntaxError("back called without some previous parsing")
378         self.pointer = self.backout.pop()
379
380     def tag(self):
381         "A microparser that just returns the next tag from the line."
382         subject = self.subject
383         i = self.pointer
384         if i >= len(subject):
385             raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
386         p = i
387         while i < len(subject) and subject[i] not in (' ', '\t'):
388             i += 1
389         s = subject[p:i]
390         while i < len(subject) and subject[i] in (' ', '\t'):
391             i += 1
392         self.backout.append(self.pointer)
393         self.pointer = i
394         return s
395
396     def value(self):
397         "A microparser that parses for a value: some string constant or variable name."
398         subject = self.subject
399         i = self.pointer
400         if i >= len(subject):
401             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
402         if subject[i] in ('"', "'"):
403             p = i
404             i += 1
405             while i < len(subject) and subject[i] != subject[p]:
406                 i += 1
407             if i >= len(subject):
408                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
409             i += 1
410             res = subject[p:i]
411             while i < len(subject) and subject[i] in (' ', '\t'):
412                 i += 1
413             self.backout.append(self.pointer)
414             self.pointer = i
415             return res
416         else:
417             p = i
418             while i < len(subject) and subject[i] not in (' ', '\t'):
419                 if subject[i] in ('"', "'"):
420                     c = subject[i]
421                     i += 1
422                     while i < len(subject) and subject[i] != c:
423                         i += 1
424                     if i >= len(subject):
425                         raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
426                 i += 1
427             s = subject[p:i]
428             while i < len(subject) and subject[i] in (' ', '\t'):
429                 i += 1
430             self.backout.append(self.pointer)
431             self.pointer = i
432             return s
433
434 filter_raw_string = r"""
435 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
436 ^"(?P<constant>%(str)s)"|
437 ^(?P<var>[%(var_chars)s]+)|
438  (?:%(filter_sep)s
439      (?P<filter_name>\w+)
440          (?:%(arg_sep)s
441              (?:
442               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
443               "(?P<constant_arg>%(str)s)"|
444               (?P<var_arg>[%(var_chars)s]+)
445              )
446          )?
447  )""" % {
448     'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
449     'var_chars': "\w\." ,
450     'filter_sep': re.escape(FILTER_SEPARATOR),
451     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
452     'i18n_open' : re.escape("_("),
453     'i18n_close' : re.escape(")"),
454   }
455
456 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
457 filter_re = re.compile(filter_raw_string, re.UNICODE)
458
459 class FilterExpression(object):
460     """
461     Parses a variable token and its optional filters (all as a single string),
462     and return a list of tuples of the filter name and arguments.
463     Sample:
464         >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
465         >>> p = Parser('')
466         >>> fe = FilterExpression(token, p)
467         >>> len(fe.filters)
468         2
469         >>> fe.var
470         <Variable: 'variable'>
471
472     This class should never be instantiated outside of the
473     get_filters_from_token helper function.
474     """
475     def __init__(self, token, parser):
476         self.token = token
477         matches = filter_re.finditer(token)
478         var = None
479         filters = []
480         upto = 0
481         for match in matches:
482             start = match.start()
483             if upto != start:
484                 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
485                                            (token[:upto], token[upto:start], token[start:]))
486             if var == None:
487                 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
488                 if i18n_constant:
489                     var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
490                 elif constant:
491                     var = '"%s"' % constant.replace(r'\"', '"')
492                 upto = match.end()
493                 if var == None:
494                     raise TemplateSyntaxError("Could not find variable at start of %s" % token)
495                 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
496                     raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
497             else:
498                 filter_name = match.group("filter_name")
499                 args = []
500                 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
501                 if i18n_arg:
502                     args.append((False, _(i18n_arg.replace(r'\"', '"'))))
503                 elif constant_arg is not None:
504                     args.append((False, constant_arg.replace(r'\"', '"')))
505                 elif var_arg:
506                     args.append((True, Variable(var_arg)))
507                 filter_func = parser.find_filter(filter_name)
508                 self.args_check(filter_name,filter_func, args)
509                 filters.append( (filter_func,args))
510                 upto = match.end()
511         if upto != len(token):
512             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
513         self.filters = filters
514         self.var = Variable(var)
515
516     def resolve(self, context, ignore_failures=False):
517         try:
518             obj = self.var.resolve(context)
519         except VariableDoesNotExist:
520             if ignore_failures:
521                 obj = None
522             else:
523                 if settings.TEMPLATE_STRING_IF_INVALID:
524                     global invalid_var_format_string
525                     if invalid_var_format_string is None:
526                         invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
527                     if invalid_var_format_string:
528                     &