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