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