Package web2py :: Package gluon :: Module template
[hide private]
[frames] | no frames]

Source Code for Module web2py.gluon.template

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework (Copyrighted, 2007-2011). 
  6  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  7   
  8  Author: Thadeus Burgess 
  9   
 10  Contributors: 
 11   
 12  - Thank you to Massimo Di Pierro for creating the original gluon/template.py 
 13  - Thank you to Jonathan Lundell for extensively testing the regex on Jython. 
 14  - Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py. 
 15  """ 
 16   
 17  import os 
 18  import re 
 19  import cgi 
 20  import cStringIO 
 21  import logging 
 22  try: 
 23      from restricted import RestrictedError 
 24  except: 
25 - def RestrictedError(a,b,c):
26 logging.error(str(a)+':'+str(b)+':'+str(c)) 27 return RuntimeError
28
29 -class Node(object):
30 """ 31 Basic Container Object 32 """
33 - def __init__(self, value = None, pre_extend = False):
34 self.value = value 35 self.pre_extend = pre_extend
36
37 - def __str__(self):
38 return str(self.value)
39
40 -class SuperNode(Node):
41 - def __init__(self, name = '', pre_extend = False):
42 self.name = name 43 self.value = None 44 self.pre_extend = pre_extend
45
46 - def __str__(self):
47 if self.value: 48 return str(self.value) 49 else: 50 raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \ 51 "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
52
53 - def __repr__(self):
54 return "%s->%s" % (self.name, self.value)
55
56 -class BlockNode(Node):
57 """ 58 Block Container. 59 60 This Node can contain other Nodes and will render in a hierarchical order 61 of when nodes were added. 62 63 ie:: 64 65 {{ block test }} 66 This is default block test 67 {{ end }} 68 """
69 - def __init__(self, name = '', pre_extend = False, delimiters = ('{{','}}')):
70 """ 71 name - Name of this Node. 72 """ 73 self.nodes = [] 74 self.name = name 75 self.pre_extend = pre_extend 76 self.left, self.right = delimiters
77
78 - def __repr__(self):
79 lines = ['%sblock %s%s' % (self.left,self.name,self.right)] 80 for node in self.nodes: 81 lines.append(str(node)) 82 lines.append('%send%s' % (self.left, self.right)) 83 return ''.join(lines)
84
85 - def __str__(self):
86 """ 87 Get this BlockNodes content, not including child Nodes 88 """ 89 lines = [] 90 for node in self.nodes: 91 if not isinstance(node, BlockNode): 92 lines.append(str(node)) 93 return ''.join(lines)
94
95 - def append(self, node):
96 """ 97 Add an element to the nodes. 98 99 Keyword Arguments 100 101 - node -- Node object or string to append. 102 """ 103 if isinstance(node, str) or isinstance(node, Node): 104 self.nodes.append(node) 105 else: 106 raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
107
108 - def extend(self, other):
109 """ 110 Extend the list of nodes with another BlockNode class. 111 112 Keyword Arguments 113 114 - other -- BlockNode or Content object to extend from. 115 """ 116 if isinstance(other, BlockNode): 117 self.nodes.extend(other.nodes) 118 else: 119 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
120
121 - def output(self, blocks):
122 """ 123 Merges all nodes into a single string. 124 125 blocks -- Dictionary of blocks that are extending 126 from this template. 127 """ 128 lines = [] 129 # Get each of our nodes 130 for node in self.nodes: 131 # If we have a block level node. 132 if isinstance(node, BlockNode): 133 # If we can override this block. 134 if node.name in blocks: 135 # Override block from vars. 136 lines.append(blocks[node.name].output(blocks)) 137 # Else we take the default 138 else: 139 lines.append(node.output(blocks)) 140 # Else its just a string 141 else: 142 lines.append(str(node)) 143 # Now combine all of our lines together. 144 return ''.join(lines)
145
146 -class Content(BlockNode):
147 """ 148 Parent Container -- Used as the root level BlockNode. 149 150 Contains functions that operate as such. 151 """
152 - def __init__(self, name = "ContentBlock", pre_extend = False):
153 """ 154 Keyword Arguments 155 156 name -- Unique name for this BlockNode 157 """ 158 self.name = name 159 self.nodes = [] 160 self.blocks = {} 161 self.pre_extend = pre_extend
162
163 - def __str__(self):
164 lines = [] 165 # For each of our nodes 166 for node in self.nodes: 167 # If it is a block node. 168 if isinstance(node, BlockNode): 169 # And the node has a name that corresponds with a block in us 170 if node.name in self.blocks: 171 # Use the overriding output. 172 lines.append(self.blocks[node.name].output(self.blocks)) 173 else: 174 # Otherwise we just use the nodes output. 175 lines.append(node.output(self.blocks)) 176 else: 177 # It is just a string, so include it. 178 lines.append(str(node)) 179 # Merge our list together. 180 return ''.join(lines)
181
182 - def _insert(self, other, index = 0):
183 """ 184 Inserts object at index. 185 """ 186 if isinstance(other, str) or isinstance(other, Node): 187 self.nodes.insert(index, other) 188 else: 189 raise TypeError("Invalid type, must be instance of ``str`` or ``Node``.")
190
191 - def insert(self, other, index = 0):
192 """ 193 Inserts object at index. 194 195 You may pass a list of objects and have them inserted. 196 """ 197 if isinstance(other, (list, tuple)): 198 # Must reverse so the order stays the same. 199 other.reverse() 200 for item in other: 201 self._insert(item, index) 202 else: 203 self._insert(other, index)
204
205 - def append(self, node):
206 """ 207 Adds a node to list. If it is a BlockNode then we assign a block for it. 208 """ 209 if isinstance(node, str) or isinstance(node, Node): 210 self.nodes.append(node) 211 if isinstance(node, BlockNode): 212 self.blocks[node.name] = node 213 else: 214 raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
215
216 - def extend(self, other):
217 """ 218 Extends the objects list of nodes with another objects nodes 219 """ 220 if isinstance(other, BlockNode): 221 self.nodes.extend(other.nodes) 222 self.blocks.update(other.blocks) 223 else: 224 raise TypeError("Invalid type; must be instance of ``BlockNode``. %s" % other)
225
226 - def clear_content(self):
227 self.nodes = []
228
229 -class TemplateParser(object):
230 231 r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL) 232 233 r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL) 234 235 # These are used for re-indentation. 236 # Indent + 1 237 re_block = re.compile('^(elif |else:|except:|except |finally:).*$', 238 re.DOTALL) 239 # Indent - 1 240 re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL) 241 # Indent - 1 242 re_pass = re.compile('^pass( .*)?$', re.DOTALL) 243
244 - def __init__(self, text, 245 name = "ParserContainer", 246 context = dict(), 247 path = 'views/', 248 writer = 'response.write', 249 lexers = {}, 250 delimiters = ('{{','}}'), 251 _super_nodes = [], 252 ):
253 """ 254 text -- text to parse 255 context -- context to parse in 256 path -- folder path to templates 257 writer -- string of writer class to use 258 lexers -- dict of custom lexers to use. 259 delimiters -- for example ('{{','}}') 260 _super_nodes -- a list of nodes to check for inclusion 261 this should only be set by "self.extend" 262 It contains a list of SuperNodes from a child 263 template that need to be handled. 264 """ 265 266 # Keep a root level name. 267 self.name = name 268 # Raw text to start parsing. 269 self.text = text 270 # Writer to use (refer to the default for an example). 271 # This will end up as 272 # "%s(%s, escape=False)" % (self.writer, value) 273 self.writer = writer 274 275 # Dictionary of custom name lexers to use. 276 if isinstance(lexers, dict): 277 self.lexers = lexers 278 else: 279 self.lexers = {} 280 281 # Path of templates 282 self.path = path 283 # Context for templates. 284 self.context = context 285 286 # allow optional alternative delimiters 287 self.delimiters = delimiters 288 if delimiters!=('{{','}}'): 289 escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1])) 290 self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL) 291 292 293 # Create a root level Content that everything will go into. 294 self.content = Content(name=name) 295 296 # Stack will hold our current stack of nodes. 297 # As we descend into a node, it will be added to the stack 298 # And when we leave, it will be removed from the stack. 299 # self.content should stay on the stack at all times. 300 self.stack = [self.content] 301 302 # This variable will hold a reference to every super block 303 # that we come across in this template. 304 self.super_nodes = [] 305 306 # This variable will hold a reference to the child 307 # super nodes that need handling. 308 self.child_super_nodes = _super_nodes 309 310 # This variable will hold a reference to every block 311 # that we come across in this template 312 self.blocks = {} 313 314 # Begin parsing. 315 self.parse(text)
316
317 - def to_string(self):
318 """ 319 Return the parsed template with correct indentation. 320 321 Used to make it easier to port to python3. 322 """ 323 return self.reindent(str(self.content))
324
325 - def __str__(self):
326 "Make sure str works exactly the same as python 3" 327 return self.to_string()
328
329 - def __unicode__(self):
330 "Make sure str works exactly the same as python 3" 331 return self.to_string()
332
333 - def reindent(self, text):
334 """ 335 Reindents a string of unindented python code. 336 """ 337 338 # Get each of our lines into an array. 339 lines = text.split('\n') 340 341 # Our new lines 342 new_lines = [] 343 344 # Keeps track of how many indents we have. 345 # Used for when we need to drop a level of indentation 346 # only to reindent on the next line. 347 credit = 0 348 349 # Current indentation 350 k = 0 351 352 ################# 353 # THINGS TO KNOW 354 ################# 355 356 # k += 1 means indent 357 # k -= 1 means unindent 358 # credit = 1 means unindent on the next line. 359 360 for raw_line in lines: 361 line = raw_line.strip() 362 363 # ignore empty lines 364 if not line: 365 continue 366 367 # If we have a line that contains python code that 368 # should be unindented for this line of code. 369 # and then reindented for the next line. 370 if TemplateParser.re_block.match(line): 371 k = k + credit - 1 372 373 # We obviously can't have a negative indentation 374 k = max(k,0) 375 376 # Add the indentation! 377 new_lines.append(' '*(4*k)+line) 378 379 # Bank account back to 0 again :( 380 credit = 0 381 382 # If we are a pass block, we obviously de-dent. 383 if TemplateParser.re_pass.match(line): 384 k -= 1 385 386 # If we are any of the following, de-dent. 387 # However, we should stay on the same level 388 # But the line right after us will be de-dented. 389 # So we add one credit to keep us at the level 390 # while moving back one indentation level. 391 if TemplateParser.re_unblock.match(line): 392 credit = 1 393 k -= 1 394 395 # If we are an if statement, a try, or a semi-colon we 396 # probably need to indent the next line. 397 if line.endswith(':') and not line.startswith('#'): 398 k += 1 399 400 # This must come before so that we can raise an error with the 401 # right content. 402 new_text = '\n'.join(new_lines) 403 404 if k > 0: 405 self._raise_error('missing "pass" in view', new_text) 406 elif k < 0: 407 self._raise_error('too many "pass" in view', new_text) 408 409 return new_text
410
411 - def _raise_error(self, message='', text=None):
412 """ 413 Raise an error using itself as the filename and textual content. 414 """ 415 raise RestrictedError(self.name, text or self.text, message)
416
417 - def _get_file_text(self, filename):
418 """ 419 Attempt to open ``filename`` and retrieve its text. 420 421 This will use self.path to search for the file. 422 """ 423 424 # If they didn't specify a filename, how can we find one! 425 if not filename.strip(): 426 self._raise_error('Invalid template filename') 427 428 # Get the filename; filename looks like ``"template.html"``. 429 # We need to eval to remove the quotes and get the string type. 430 filename = eval(filename, self.context) 431 432 # Get the path of the file on the system. 433 filepath = os.path.join(self.path, filename) 434 435 # try to read the text. 436 try: 437 fileobj = open(filepath, 'rb') 438 text = fileobj.read() 439 fileobj.close() 440 except IOError: 441 self._raise_error('Unable to open included view file: ' + filepath) 442 443 return text
444
445 - def include(self, content, filename):
446 """ 447 Include ``filename`` here. 448 """ 449 text = self._get_file_text(filename) 450 451 t = TemplateParser(text, 452 name = filename, 453 context = self.context, 454 path = self.path, 455 writer = self.writer, 456 delimiters = self.delimiters) 457 458 content.append(t.content)
459
460 - def extend(self, filename):
461 """ 462 Extend ``filename``. Anything not declared in a block defined by the 463 parent will be placed in the parent templates ``{{include}}`` block. 464 """ 465 text = self._get_file_text(filename) 466 467 # Create out nodes list to send to the parent 468 super_nodes = [] 469 # We want to include any non-handled nodes. 470 super_nodes.extend(self.child_super_nodes) 471 # And our nodes as well. 472 super_nodes.extend(self.super_nodes) 473 474 t = TemplateParser(text, 475 name = filename, 476 context = self.context, 477 path = self.path, 478 writer = self.writer, 479 delimiters = self.delimiters, 480 _super_nodes = super_nodes) 481 482 # Make a temporary buffer that is unique for parent 483 # template. 484 buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters) 485 pre = [] 486 487 # Iterate through each of our nodes 488 for node in self.content.nodes: 489 # If a node is a block 490 if isinstance(node, BlockNode): 491 # That happens to be in the parent template 492 if node.name in t.content.blocks: 493 # Do not include it 494 continue 495 496 if isinstance(node, Node): 497 # Or if the node was before the extension 498 # we should not include it 499 if node.pre_extend: 500 pre.append(node) 501 continue 502 503 # Otherwise, it should go int the 504 # Parent templates {{include}} section. 505 buf.append(node) 506 else: 507 buf.append(node) 508 509 # Clear our current nodes. We will be replacing this with 510 # the parent nodes. 511 self.content.nodes = [] 512 513 # Set our include, unique by filename 514 t.content.blocks['__include__' + filename] = buf 515 516 # Make sure our pre_extended nodes go first 517 t.content.insert(pre) 518 519 # Then we extend our blocks 520 t.content.extend(self.content) 521 522 # Work off the parent node. 523 self.content = t.content
524
525 - def parse(self, text):
526 527 # Basically, r_tag.split will split the text into 528 # an array containing, 'non-tag', 'tag', 'non-tag', 'tag' 529 # so if we alternate this variable, we know 530 # what to look for. This is alternate to 531 # line.startswith("{{") 532 in_tag = False 533 extend = None 534 pre_extend = True 535 536 # Use a list to store everything in 537 # This is because later the code will "look ahead" 538 # for missing strings or brackets. 539 ij = self.r_tag.split(text) 540 # j = current index 541 # i = current item 542 for j in range(len(ij)): 543 i = ij[j] 544 545 if i: 546 if len(self.stack) == 0: 547 self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag') 548 549 # Our current element in the stack. 550 top = self.stack[-1] 551 552 if in_tag: 553 line = i 554 555 # If we are missing any strings!!!! 556 # This usually happens with the following example 557 # template code 558 # 559 # {{a = '}}'}} 560 # or 561 # {{a = '}}blahblah{{'}} 562 # 563 # This will fix these 564 # This is commented out because the current template 565 # system has this same limitation. Since this has a 566 # performance hit on larger templates, I do not recommend 567 # using this code on production systems. This is still here 568 # for "i told you it *can* be fixed" purposes. 569 # 570 # 571 # if line.count("'") % 2 != 0 or line.count('"') % 2 != 0: 572 # 573 # # Look ahead 574 # la = 1 575 # nextline = ij[j+la] 576 # 577 # # As long as we have not found our ending 578 # # brackets keep going 579 # while '}}' not in nextline: 580 # la += 1 581 # nextline += ij[j+la] 582 # # clear this line, so we 583 # # don't attempt to parse it 584 # # this is why there is an "if i" 585 # # around line 530 586 # ij[j+la] = '' 587 # 588 # # retrieve our index. 589 # index = nextline.index('}}') 590 # 591 # # Everything before the new brackets 592 # before = nextline[:index+2] 593 # 594 # # Everything after 595 # after = nextline[index+2:] 596 # 597 # # Make the next line everything after 598 # # so it parses correctly, this *should* be 599 # # all html 600 # ij[j+1] = after 601 # 602 # # Add everything before to the current line 603 # line += before 604 605 # Get rid of '{{' and '}}' 606 line = line[2:-2].strip() 607 608 # This is bad juju, but let's do it anyway 609 if not line: 610 continue 611 612 # We do not want to replace the newlines in code, 613 # only in block comments. 614 def remove_newline(re_val): 615 # Take the entire match and replace newlines with 616 # escaped newlines. 617 return re_val.group(0).replace('\n', '\\n')
618 619 # Perform block comment escaping. 620 # This performs escaping ON anything 621 # in between """ and """ 622 line = re.sub(TemplateParser.r_multiline, 623 remove_newline, 624 line) 625 626 if line.startswith('='): 627 # IE: {{=response.title}} 628 name, value = '=', line[1:].strip() 629 else: 630 v = line.split(' ', 1) 631 if len(v) == 1: 632 # Example 633 # {{ include }} 634 # {{ end }} 635 name = v[0] 636 value = '' 637 else: 638 # Example 639 # {{ block pie }} 640 # {{ include "layout.html" }} 641 # {{ for i in range(10): }} 642 name = v[0] 643 value = v[1] 644 645 # This will replace newlines in block comments 646 # with the newline character. This is so that they 647 # retain their formatting, but squish down to one 648 # line in the rendered template. 649 650 # First check if we have any custom lexers 651 if name in self.lexers: 652 # Pass the information to the lexer 653 # and allow it to inject in the environment 654 655 # You can define custom names such as 656 # '{{<<variable}}' which could potentially 657 # write unescaped version of the variable. 658 self.lexers[name](parser = self, 659 value = value, 660 top = top, 661 stack = self.stack,) 662 663 elif name == '=': 664 # So we have a variable to insert into 665 # the template 666 buf = "\n%s(%s)" % (self.writer, value) 667 top.append(Node(buf, pre_extend = pre_extend)) 668 669 elif name == 'block' and not value.startswith('='): 670 # Make a new node with name. 671 node = BlockNode(name = value.strip(), 672 pre_extend = pre_extend, 673 delimiters = self.delimiters) 674 675 # Append this node to our active node 676 top.append(node) 677 678 # Make sure to add the node to the stack. 679 # so anything after this gets added 680 # to this node. This allows us to 681 # "nest" nodes. 682 self.stack.append(node) 683 684 elif name == 'end' and not value.startswith('='): 685 # We are done with this node. 686 687 # Save an instance of it 688 self.blocks[top.name] = top 689 690 # Pop it. 691 self.stack.pop() 692 693 elif name == 'super' and not value.startswith('='): 694 # Get our correct target name 695 # If they just called {{super}} without a name 696 # attempt to assume the top blocks name. 697 if value: 698 target_node = value 699 else: 700 target_node = top.name 701 702 # Create a SuperNode instance 703 node = SuperNode(name = target_node, 704 pre_extend = pre_extend) 705 706 # Add this to our list to be taken care of 707 self.super_nodes.append(node) 708 709 # And put in in the tree 710 top.append(node) 711 712 elif name == 'include' and not value.startswith('='): 713 # If we know the target file to include 714 if value: 715 self.include(top, value) 716 717 # Otherwise, make a temporary include node 718 # That the child node will know to hook into. 719 else: 720 include_node = BlockNode(name = '__include__' + self.name, 721 pre_extend = pre_extend, 722 delimiters = self.delimiters) 723 top.append(include_node) 724 725 elif name == 'extend' and not value.startswith('='): 726 # We need to extend the following 727 # template. 728 extend = value 729 pre_extend = False 730 731 else: 732 # If we don't know where it belongs 733 # we just add it anyways without formatting. 734 if line and in_tag: 735 736 # Split on the newlines >.< 737 tokens = line.split('\n') 738 739 # We need to look for any instances of 740 # for i in range(10): 741 # = i 742 # pass 743 # So we can properly put a response.write() in place. 744 continuation = False 745 len_parsed = 0 746 for k in range(len(tokens)): 747 748 tokens[k] = tokens[k].strip() 749 len_parsed += len(tokens[k]) 750 751 if tokens[k].startswith('='): 752 if tokens[k].endswith('\\'): 753 continuation = True 754 tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip()) 755 else: 756 tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip()) 757 elif continuation: 758 tokens[k] += ')' 759 continuation = False 760 761 762 buf = "\n%s" % '\n'.join(tokens) 763 top.append(Node(buf, pre_extend = pre_extend)) 764 765 else: 766 # It is HTML so just include it. 767 buf = "\n%s(%r, escape=False)" % (self.writer, i) 768 top.append(Node(buf, pre_extend = pre_extend)) 769 770 # Remember: tag, not tag, tag, not tag 771 in_tag = not in_tag 772 773 # Make a list of items to remove from child 774 to_rm = [] 775 776 # Go through each of the children nodes 777 for node in self.child_super_nodes: 778 # If we declared a block that this node wants to include 779 if node.name in self.blocks: 780 # Go ahead and include it! 781 node.value = self.blocks[node.name] 782 # Since we processed this child, we don't need to 783 # pass it along to the parent 784 to_rm.append(node) 785 786 # Remove some of the processed nodes 787 for node in to_rm: 788 # Since this is a pointer, it works beautifully. 789 # Sometimes I miss C-Style pointers... I want my asterisk... 790 self.child_super_nodes.remove(node) 791 792 # If we need to extend a template. 793 if extend: 794 self.extend(extend)
795 796 # We need this for integration with gluon
797 -def parse_template(filename, 798 path = 'views/', 799 context = dict(), 800 lexers = {}, 801 delimiters = ('{{','}}') 802 ):
803 """ 804 filename can be a view filename in the views folder or an input stream 805 path is the path of a views folder 806 context is a dictionary of symbols used to render the template 807 """ 808 809 # First, if we have a str try to open the file 810 if isinstance(filename, str): 811 try: 812 fp = open(os.path.join(path, filename), 'rb') 813 text = fp.read() 814 fp.close() 815 except IOError: 816 raise RestrictedError(filename, '', 'Unable to find the file') 817 else: 818 text = filename.read() 819 820 # Use the file contents to get a parsed template and return it. 821 return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
822
823 -def get_parsed(text):
824 """ 825 Returns the indented python code of text. Useful for unit testing. 826 827 """ 828 return str(TemplateParser(text))
829 830 # And this is a generic render function. 831 # Here for integration with gluon.
832 -def render(content = "hello world", 833 stream = None, 834 filename = None, 835 path = None, 836 context = {}, 837 lexers = {}, 838 delimiters = ('{{','}}') 839 ):
840 """ 841 >>> render() 842 'hello world' 843 >>> render(content='abc') 844 'abc' 845 >>> render(content='abc\\'') 846 "abc'" 847 >>> render(content='a"\\'bc') 848 'a"\\'bc' 849 >>> render(content='a\\nbc') 850 'a\\nbc' 851 >>> render(content='a"bcd"e') 852 'a"bcd"e' 853 >>> render(content="'''a\\nc'''") 854 "'''a\\nc'''" 855 >>> render(content="'''a\\'c'''") 856 "'''a\'c'''" 857 >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5)) 858 '0<br />1<br />2<br />3<br />4<br />' 859 >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}')) 860 '0<br />1<br />2<br />3<br />4<br />' 861 >>> render(content="{{='''hello\\nworld'''}}") 862 'hello\\nworld' 863 >>> render(content='{{for i in range(3):\\n=i\\npass}}') 864 '012' 865 """ 866 # Here to avoid circular Imports 867 try: 868 from globals import Response 869 except: 870 # Working standalone. Build a mock Response object. 871 class Response(): 872 def __init__(self): 873 self.body = cStringIO.StringIO()
874 def write(self, data, escape=True): 875 if not escape: 876 self.body.write(str(data)) 877 elif hasattr(data,'xml') and callable(data.xml): 878 self.body.write(data.xml()) 879 else: 880 # make it a string 881 if not isinstance(data, (str, unicode)): 882 data = str(data) 883 elif isinstance(data, unicode): 884 data = data.encode('utf8', 'xmlcharrefreplace') 885 data = cgi.escape(data, True).replace("'","&#x27;") 886 self.body.write(data) 887 888 # A little helper to avoid escaping. 889 class NOESCAPE(): 890 def __init__(self, text): 891 self.text = text 892 def xml(self): 893 return self.text 894 # Add it to the context so we can use it. 895 context['NOESCAPE'] = NOESCAPE 896 897 # If we don't have anything to render, why bother? 898 if not content and not stream and not filename: 899 raise SyntaxError, "Must specify a stream or filename or content" 900 901 # Here for legacy purposes, probably can be reduced to something more simple. 902 close_stream = False 903 if not stream: 904 if filename: 905 stream = open(filename, 'rb') 906 close_stream = True 907 elif content: 908 stream = cStringIO.StringIO(content) 909 910 # Get a response class. 911 context['response'] = Response() 912 913 # Execute the template. 914 code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters)) 915 try: 916 exec(code) in context 917 except Exception: 918 # for i,line in enumerate(code.split('\n')): print i,line 919 raise 920 921 if close_stream: 922 stream.close() 923 924 # Returned the rendered content. 925 return context['response'].body.getvalue() 926 927 928 if __name__ == '__main__': 929 import doctest 930 doctest.testmod() 931