1import re, sys 2from inspect import getargspec 3 4from template.context import Context, RequestContext, ContextPopException 5from helper import * 6 7__all__ = ('Template', 'Context', 'RequestContext', 'compile_string') 8 9TOKEN_TEXT = 0 10TOKEN_VAR = 1 11TOKEN_BLOCK = 2 12TOKEN_COMMENT = 3 13 14# template syntax constants 15FILTER_SEPARATOR = '|' 16FILTER_ARGUMENT_SEPARATOR = ':' 17VARIABLE_ATTRIBUTE_SEPARATOR = '.' 18BLOCK_TAG_START = '{%' 19BLOCK_TAG_END = '%}' 20VARIABLE_TAG_START = '{{' 21VARIABLE_TAG_END = '}}' 22COMMENT_TAG_START = '{#' 23COMMENT_TAG_END = '#}' 24SINGLE_BRACE_START = '{' 25SINGLE_BRACE_END = '}' 26 27ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 28 29# what to report as the origin for templates that come from non-loader sources 30# (e.g. strings) 31UNKNOWN_SOURCE="<unknown source>" 32 33# match a variable or block tag and capture the entire tag, including start/end delimiters 34tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 35 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 36 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) 37 38# global dictionary of libraries that have been loaded using get_library 39libraries = {} 40# global list of libraries to load by default for a new parser 41builtins = [] 42 43# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means 44# uninitialised. 45invalid_var_format_string = None 46 47class TemplateSyntaxError(Exception): 48 def __str__(self): 49 try: 50 import cStringIO as StringIO 51 except ImportError: 52 import StringIO 53 output = StringIO.StringIO() 54 output.write(Exception.__str__(self)) 55 # Check if we wrapped an exception and print that too. 56 if hasattr(self, 'exc_info'): 57 import traceback 58 output.write('\n\nOriginal ') 59 e = self.exc_info 60 traceback.print_exception(e[0], e[1], e[2], 500, output) 61 return output.getvalue() 62 63class TemplateDoesNotExist(Exception): 64 pass 65 66class TemplateEncodingError(Exception): 67 pass 68 69class VariableDoesNotExist(Exception): 70 71 def __init__(self, msg, params=()): 72 self.msg = msg 73 self.params = params 74 75 def __str__(self): 76 return unicode(self).encode('utf-8') 77 78class InvalidTemplateLibrary(Exception): 79 pass 80 81class Origin(object): 82 def __init__(self, name): 83 self.name = name 84 85 def reload(self): 86 raise NotImplementedError 87 88 def __str__(self): 89 return self.name 90 91class StringOrigin(Origin): 92 def __init__(self, source): 93 super(StringOrigin, self).__init__(UNKNOWN_SOURCE) 94 self.source = source 95 96 def reload(self): 97 return self.source 98 99class Template(object): 100 def __init__(self, template_string, origin=None, name='<Unknown Template>'): 101 self.nodelist = compile_string(template_string, origin) 102 self.name = name 103 104 def __iter__(self): 105 for node in self.nodelist: 106 for subnode in node: 107 yield subnode 108 109 def render(self, context): 110 "Display stage -- can be called many times" 111 return self.nodelist.render(context) 112 113def compile_string(template_string, origin): 114 "Compiles template_string into NodeList ready for rendering" 115 lexer_class, parser_class = Lexer, Parser 116 lexer = lexer_class(template_string, origin) 117 parser = parser_class(lexer.tokenize()) 118 return parser.parse() 119 120class Token(object): 121 def __init__(self, token_type, contents): 122 # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT. 123 self.token_type, self.contents = token_type, contents 124 125 def __str__(self): 126 return '<%s token: "%s...">' % \ 127 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type], 128 self.contents[:20].replace('\n', '')) 129 130 def split_contents(self): 131 split = [] 132 bits = iter(smart_split(self.contents)) 133 for bit in bits: 134 # Handle translation-marked template pieces 135 if bit.startswith('_("') or bit.startswith("_('"): 136 sentinal = bit[2] + ')' 137 trans_bit = [bit] 138 while not bit.endswith(sentinal): 139 bit = bits.next() 140 trans_bit.append(bit) 141 bit = ' '.join(trans_bit) 142 split.append(bit) 143 return split 144 145class Lexer(object): 146 def __init__(self, template_string, origin): 147 self.template_string = template_string 148 self.origin = origin 149 150 def tokenize(self): 151 "Return a list of tokens from a given template_string." 152 in_tag = False 153 result = [] 154 for bit in tag_re.split(self.template_string): 155 if bit: 156 result.append(self.create_token(bit, in_tag)) 157 in_tag = not in_tag 158 return result 159 160 def create_token(self, token_string, in_tag): 161 """ 162 Convert the given token string into a new Token object and return it. 163 If in_tag is True, we are processing something that matched a tag, 164 otherwise it should be treated as a literal string. 165 """ 166 if in_tag: 167 if token_string.startswith(VARIABLE_TAG_START): 168 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 169 elif token_string.startswith(BLOCK_TAG_START): 170 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 171 elif token_string.startswith(COMMENT_TAG_START): 172 token = Token(TOKEN_COMMENT, '') 173 else: 174 token = Token(TOKEN_TEXT, token_string) 175 return token 176 177class Parser(object): 178 def __init__(self, tokens): 179 self.tokens = tokens 180 self.tags = {} 181 self.filters = {} 182 for lib in builtins: 183 self.add_library(lib) 184 185 def parse(self, parse_until=None): 186 if parse_until is None: parse_until = [] 187 nodelist = self.create_nodelist() 188 while self.tokens: 189 token = self.next_token() 190 if token.token_type == TOKEN_TEXT: 191 self.extend_nodelist(nodelist, TextNode(token.contents), token) 192 elif token.token_type == TOKEN_VAR: 193 if not token.contents: 194 self.empty_variable(token) 195 var_node = self.create_variable_node(token.contents) 196 self.extend_nodelist(nodelist, var_node,token) 197 elif token.token_type == TOKEN_BLOCK: 198 if token.contents in parse_until: 199 # put token back on token list so calling code knows why it terminated 200 self.prepend_token(token) 201 return nodelist 202 try: 203 command = token.contents.split()[0] 204 except IndexError: 205 self.empty_block_tag(token) 206 # execute callback function for this tag and append resulting node 207 self.enter_command(command, token) 208 try: 209 compile_func = self.tags[command] 210 except KeyError: 211 self.invalid_block_tag(token, command) 212 try: 213 compiled_result = compile_func(self, token) 214 except TemplateSyntaxError, e: 215 if not self.compile_function_error(token, e): 216 raise 217 self.extend_nodelist(nodelist, compiled_result, token) 218 self.exit_command() 219 if parse_until: 220 self.unclosed_block_tag(parse_until) 221 return nodelist 222 223 def skip_past(self, endtag): 224 while self.tokens: 225 token = self.next_token() 226 if token.token_type == TOKEN_BLOCK and token.contents == endtag: 227 return 228 self.unclosed_block_tag([endtag]) 229 230 def create_variable_node(self, content): 231 return VariableNode(content) 232 233 def create_nodelist(self): 234 return NodeList() 235 236 def extend_nodelist(self, nodelist, node, token): 237 if node.must_be_first and nodelist: 238 try: 239 if nodelist.contains_nontext: 240 raise AttributeError 241 except AttributeError: 242 raise TemplateSyntaxError("%r must be the first tag in the template." % node) 243 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): 244 nodelist.contains_nontext = True 245 nodelist.append(node) 246 247 def enter_command(self, command, token): 248 pass 249 250 def exit_command(self): 251 pass 252 253 def error(self, token, msg): 254 return TemplateSyntaxError(msg) 255 256 def empty_variable(self, token): 257 raise self.error(token, "Empty variable tag") 258 259 def empty_block_tag(self, token): 260 raise self.error(token, "Empty block tag") 261 262 def invalid_block_tag(self, token, command): 263 raise self.error(token, "Invalid block tag: '%s'" % command) 264 265 def unclosed_block_tag(self, parse_until): 266 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 267 268 def compile_function_error(self, token, e): 269 pass 270 271 def next_token(self): 272 return self.tokens.pop(0) 273 274 def prepend_token(self, token): 275 self.tokens.insert(0, token) 276 277 def delete_first_token(self): 278 del self.tokens[0] 279 280 def add_library(self, lib): 281 self.tags.update(lib.tags) 282 self.filters.update(lib.filters) 283 284 def find_filter(self, filter_name): 285 if filter_name in self.filters: 286 return self.filters[filter_name] 287 else: 288 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 289 290class TokenParser(object): 291 """ 292 Subclass this and implement the top() method to parse a template line. When 293 instantiating the parser, pass in the line from the Django template parser. 294 295 The parser's "tagname" instance-variable stores the name of the tag that 296 the filter was called with. 297 """ 298 def __init__(self, subject): 299 self.subject = subject 300 self.pointer = 0 301 self.backout = [] 302 self.tagname = self.tag() 303 304 def top(self): 305 "Overload this method to do the actual parsing and return the result." 306 raise NotImplementedError() 307 308 def more(self): 309 "Returns True if there is more stuff in the tag." 310 return self.pointer < len(self.subject) 311 312 def back(self): 313 "Undoes the last microparser. Use this for lookahead and backtracking." 314 if not len(self.backout): 315 raise TemplateSyntaxError("back called without some previous parsing") 316 self.pointer = self.backout.pop() 317 318 def tag(self): 319 "A microparser that just returns the next tag from the line." 320 subject = self.subject 321 i = self.pointer 322 if i >= len(subject): 323 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) 324 p = i 325 while i < len(subject) and subject[i] not in (' ', '\t'): 326 i += 1 327 s = subject[p:i] 328 while i < len(subject) and subject[i] in (' ', '\t'): 329 i += 1 330 self.backout.append(self.pointer) 331 self.pointer = i 332 return s 333 334 def value(self): 335 "A microparser that parses for a value: some string constant or variable name." 336 subject = self.subject 337 i = self.pointer 338 if i >= len(subject): 339 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) 340 if subject[i] in ('"', "'"): 341 p = i 342 i += 1 343 while i < len(subject) and subject[i] != subject[p]: 344 i += 1 345 if i >= len(subject): 346 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 347 i += 1 348 res = subject[p:i] 349 while i < len(subject) and subject[i] in (' ', '\t'): 350 i += 1 351 self.backout.append(self.pointer) 352 self.pointer = i 353 return res 354 else: 355 p = i 356 while i < len(subject) and subject[i] not in (' ', '\t'): 357 if subject[i] in ('"', "'"): 358 c = subject[i] 359 i += 1 360 while i < len(subject) and subject[i] != c: 361 i += 1 362 if i >= len(subject): 363 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 364 i += 1 365 s = subject[p:i] 366 while i < len(subject) and subject[i] in (' ', '\t'): 367 i += 1 368 self.backout.append(self.pointer) 369 self.pointer = i 370 return s 371 372class Node(object): 373 # Set this to True for nodes that must be first in the template (although 374 # they can be preceded by text nodes. 375 must_be_first = False 376 377 def render(self, context): 378 "Return the node rendered as a string" 379 pass 380 381 def __iter__(self): 382 yield self 383 384 def get_nodes_by_type(self, nodetype): 385 "Return a list of all nodes (within this node and its nodelist) of the given type" 386 nodes = [] 387 if isinstance(self, nodetype): 388 nodes.append(self) 389 if hasattr(self, 'nodelist'): 390 nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 391 return nodes 392 393class NodeList(list): 394 # Set to True the first time a non-TextNode is inserted by 395 # extend_nodelist(). 396 contains_nontext = False 397 398 def render(self, context): 399 bits = [] 400 for node in self: 401 if isinstance(node, Node): 402 bits.append(self.render_node(node, context)) 403 else: 404 bits.append(node) 405 return str(''.join([str(b) for b in bits])) 406 407 def get_nodes_by_type(self, nodetype): 408 "Return a list of all nodes of the given type" 409 nodes = [] 410 for node in self: 411 nodes.extend(node.get_nodes_by_type(nodetype)) 412 return nodes 413 414 def render_node(self, node, context): 415 return node.render(context) 416 417class TextNode(Node): 418 def __init__(self, s): 419 self.s = s 420 421 def __repr__(self): 422 return "<Text Node: '%s'>" % str(self.s[:25], 'ascii', 423 errors='replace') 424 425 def render(self, context): 426 return self.s 427 428def _render_value_in_context(value, context): 429 """ 430 Converts any value to a string to become part of a rendered template. This 431 means escaping, if required, and conversion to a unicode object. If value 432 is a string, it is expected to have already been translated. 433 """ 434 return str(value) 435 436class VariableNode(Node): 437 def __init__(self,expr): 438 self.expr = expr 439 440 def render(self,context): 441 return str(eval(self.expr,context)) 442 443def generic_tag_compiler(params, defaults, name, node_class, parser, token): 444 "Returns a template.Node subclass." 445 bits = token.split_contents()[1:] 446 bmax = len(params) 447 def_len = defaults and len(defaults) or 0 448 bmin = bmax - def_len 449 if(len(bits) < bmin or len(bits) > bmax): 450 if bmin == bmax: 451 message = "%s takes %s arguments" % (name, bmin) 452 else: 453 message = "%s takes between %s and %s arguments" % (name, bmin, bmax) 454 raise TemplateSyntaxError(message) 455 return node_class(bits) 456 457class Library(object): 458 def __init__(self): 459 self.filters = {} 460 self.tags = {} 461 462 def tag(self, name=None, compile_function=None): 463 if name == None and compile_function == None: 464 # @register.tag() 465 return self.tag_function 466 elif name != None and compile_function == None: 467 if(callable(name)): 468 # @register.tag 469 return self.tag_function(name) 470 else: 471 # @register.tag('somename') or @register.tag(name='somename') 472 def dec(func): 473 return self.tag(name, func) 474 return dec 475 elif name != None and compile_function != None: 476 # register.tag('somename', somefunc) 477 self.tags[name] = compile_function 478 return compile_function 479 else: 480 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) 481 482 def tag_function(self,func): 483 self.tags[getattr(func, "_decorated_function", func).__name__] = func 484 return func 485 486 def filter(self, name=None, filter_func=None): 487 if name == None and filter_func == None: 488 # @register.filter() 489 return self.filter_function 490 elif filter_func == None: 491 if(callable(name)): 492 # @register.filter 493 return self.filter_function(name) 494 else: 495 # @register.filter('somename') or @register.filter(name='somename') 496 def dec(func): 497 return self.filter(name, func) 498 return dec 499 elif name != None and filter_func != None: 500 # register.filter('somename', somefunc) 501 self.filters[name] = filter_func 502 return filter_func 503 else: 504 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) 505 506 def filter_function(self, func): 507 self.filters[getattr(func, "_decorated_function", func).__name__] = func 508 return func 509 510 def simple_tag(self,func): 511 params, xx, xxx, defaults = getargspec(func) 512 513 class SimpleNode(Node): 514 def __init__(self, vars_to_resolve): 515 self.vars_to_resolve = map(Variable, vars_to_resolve) 516 517 def render(self, context): 518 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] 519 return func(*resolved_vars) 520 521 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode) 522 compile_func.__doc__ = func.__doc__ 523 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 524 return func 525 526 def inclusion_tag(self, file_name, context_class=Context, takes_context=False): 527 def dec(func): 528 params, xx, xxx, defaults = getargspec(func) 529 if takes_context: 530 if params[0] == 'context': 531 params = params[1:] 532 else: 533 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 534 535 class InclusionNode(Node): 536 def __init__(self, vars_to_resolve): 537 self.vars_to_resolve = map(Variable, vars_to_resolve) 538 539 def render(self, context): 540 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] 541 if takes_context: 542 args = [context] + resolved_vars 543 else: 544 args = resolved_vars 545 546 dict = func(*args) 547 548 if not getattr(self, 'nodelist', False): 549 from template.loader import get_template, select_template 550 if not isinstance(file_name, basestring) and is_iterable(file_name): 551 t = select_template(file_name) 552 else: 553 t = get_template(file_name) 554 self.nodelist = t.nodelist 555 return self.nodelist.render(context_class(dict, 556 autoescape=context.autoescape)) 557 558 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode) 559 compile_func.__doc__ = func.__doc__ 560 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 561 return func 562 return dec 563 564def get_library(module_name): 565 lib = libraries.get(module_name, None) 566 if not lib: 567 try: 568 mod = import_module(module_name) 569 except ImportError, e: 570 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) 571 try: 572 lib = mod.register 573 libraries[module_name] = lib 574 except AttributeError: 575 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 576 return lib 577 578def add_to_builtins(module_name): 579 builtins.append(get_library(module_name)) 580 581add_to_builtins('template.defaulttags') 582