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      default_delimiters = ('{{','}}')
    232      r_tag = re.compile(r'(\{\{.*?\}\})', re.DOTALL)
    233
    234      r_multiline = re.compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', re.DOTALL)
    235
    236      # These are used for re-indentation.
    237      # Indent + 1
    238      re_block = re.compile('^(elif |else:|except:|except |finally:).*$',
    239                        re.DOTALL)
    240      # Indent - 1
    241      re_unblock = re.compile('^(return|continue|break|raise)( .*)?$', re.DOTALL)
    242      # Indent - 1
    243      re_pass = re.compile('^pass( .*)?$', re.DOTALL)
    244
    245 -    def __init__(self, text,
    246                   name    = "ParserContainer",
    247                   context = dict(),
    248                   path    = 'views/',
    249                   writer  = 'response.write',
    250                   lexers  = {},
    251                   delimiters = ('{{','}}'),
    252                   _super_nodes = [],
    253                   ):
    254          """
    255          text -- text to parse
    256          context -- context to parse in
    257          path -- folder path to templates
    258          writer -- string of writer class to use
    259          lexers -- dict of custom lexers to use.
    260          delimiters -- for example ('{{','}}')
    261          _super_nodes -- a list of nodes to check for inclusion
    262                          this should only be set by "self.extend"
    263                          It contains a list of SuperNodes from a child
    264                          template that need to be handled.
    265          """
    266
    267          # Keep a root level name.
    268          self.name = name
    269          # Raw text to start parsing.
    270          self.text = text
    271          # Writer to use (refer to the default for an example).
    272          # This will end up as
    273          # "%s(%s, escape=False)" % (self.writer, value)
    274          self.writer = writer
    275
    276          # Dictionary of custom name lexers to use.
    277          if isinstance(lexers, dict):
    278              self.lexers = lexers
    279          else:
    280              self.lexers = {}
    281
    282          # Path of templates
    283          self.path = path
    284          # Context for templates.
    285          self.context = context
    286
    287          # allow optional alternative delimiters
    288          self.delimiters = delimiters
    289          if delimiters != self.default_delimiters:
    290              escaped_delimiters = (re.escape(delimiters[0]),re.escape(delimiters[1]))
    291              self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters, re.DOTALL)
    292          elif context.has_key('response'):
    293              if context['response'].delimiters != self.default_delimiters:
    294                  escaped_delimiters = (re.escape(context['response'].delimiters[0]),
    295                                        re.escape(context['response'].delimiters[1]))
    296                  self.r_tag = re.compile(r'(%s.*?%s)' % escaped_delimiters,re.DOTALL)
    297
    298          # Create a root level Content that everything will go into.
    299          self.content = Content(name=name)
    300
    301          # Stack will hold our current stack of nodes.
    302          # As we descend into a node, it will be added to the stack
    303          # And when we leave, it will be removed from the stack.
    304          # self.content should stay on the stack at all times.
    305          self.stack = [self.content]
    306
    307          # This variable will hold a reference to every super block
    308          # that we come across in this template.
    309          self.super_nodes = []
    310
    311          # This variable will hold a reference to the child
    312          # super nodes that need handling.
    313          self.child_super_nodes = _super_nodes
    314
    315          # This variable will hold a reference to every block
    316          # that we come across in this template
    317          self.blocks = {}
    318
    319          # Begin parsing.
    320          self.parse(text)
    321
    322 -    def to_string(self):
    323          """
    324          Return the parsed template with correct indentation.
    325
    326          Used to make it easier to port to python3.
    327          """
    328          return self.reindent(str(self.content))
    329
    330 -    def __str__(self):
    331          "Make sure str works exactly the same as python 3"
    332          return self.to_string()
    333
    334 -    def __unicode__(self):
    335          "Make sure str works exactly the same as python 3"
    336          return self.to_string()
    337
    338 -    def reindent(self, text):
    339          """
    340          Reindents a string of unindented python code.
    341          """
    342
    343          # Get each of our lines into an array.
    344          lines = text.split('\n')
    345
    346          # Our new lines
    347          new_lines = []
    348
    349          # Keeps track of how many indents we have.
    350          # Used for when we need to drop a level of indentation
    351          # only to reindent on the next line.
    352          credit = 0
    353
    354          # Current indentation
    355          k = 0
    356
    357          #################
    358          # THINGS TO KNOW
    359          #################
    360
    361          # k += 1 means indent
    362          # k -= 1 means unindent
    363          # credit = 1 means unindent on the next line.
    364
    365          for raw_line in lines:
    366              line = raw_line.strip()
    367
    368              # ignore empty lines
    369              if not line:
    370                  continue
    371
    372              # If we have a line that contains python code that
    373              # should be unindented for this line of code.
    374              # and then reindented for the next line.
    375              if TemplateParser.re_block.match(line):
    376                  k = k + credit - 1
    377
    378              # We obviously can't have a negative indentation
    379              k = max(k,0)
    380
    381              # Add the indentation!
    382              new_lines.append(' '*(4*k)+line)
    383
    384              # Bank account back to 0 again :(
    385              credit = 0
    386
    387              # If we are a pass block, we obviously de-dent.
    388              if TemplateParser.re_pass.match(line):
    389                  k -= 1
    390
    391              # If we are any of the following, de-dent.
    392              # However, we should stay on the same level
    393              # But the line right after us will be de-dented.
    394              # So we add one credit to keep us at the level
    395              # while moving back one indentation level.
    396              if TemplateParser.re_unblock.match(line):
    397                  credit = 1
    398                  k -= 1
    399
    400              # If we are an if statement, a try, or a semi-colon we
    401              # probably need to indent the next line.
    402              if line.endswith(':') and not line.startswith('#'):
    403                  k += 1
    404
    405          # This must come before so that we can raise an error with the
    406          # right content.
    407          new_text = '\n'.join(new_lines)
    408
    409          if k > 0:
    410              self._raise_error('missing "pass" in view', new_text)
    411          elif k < 0:
    412              self._raise_error('too many "pass" in view', new_text)
    413
    414          return new_text
    415
    416 -    def _raise_error(self, message='', text=None):
    417          """
    418          Raise an error using itself as the filename and textual content.
    419          """
    420          raise RestrictedError(self.name, text or self.text, message)
    421
    422 -    def _get_file_text(self, filename):
    423          """
    424          Attempt to open ``filename`` and retrieve its text.
    425
    426          This will use self.path to search for the file.
    427          """
    428
    429          # If they didn't specify a filename, how can we find one!
    430          if not filename.strip():
    431              self._raise_error('Invalid template filename')
    432
    433          # Get the filename; filename looks like ``"template.html"``.
    434          # We need to eval to remove the quotes and get the string type.
    435          filename = eval(filename, self.context)
    436
    437          # Get the path of the file on the system.
    438          filepath = os.path.join(self.path, filename)
    439
    440          # try to read the text.
    441          try:
    442              fileobj = open(filepath, 'rb')
    443              text = fileobj.read()
    444              fileobj.close()
    445          except IOError:
    446              self._raise_error('Unable to open included view file: ' + filepath)
    447
    448          return text
    449
    450 -    def include(self, content, filename):
    451          """
    452          Include ``filename`` here.
    453          """
    454          text = self._get_file_text(filename)
    455
    456          t = TemplateParser(text,
    457                             name    = filename,
    458                             context = self.context,
    459                             path    = self.path,
    460                             writer  = self.writer,
    461                             delimiters = self.delimiters)
    462
    463          content.append(t.content)
    464
    465 -    def extend(self, filename):
    466          """
    467          Extend ``filename``. Anything not declared in a block defined by the
    468          parent will be placed in the parent templates ``{{include}}`` block.
    469          """
    470          text = self._get_file_text(filename)
    471
    472          # Create out nodes list to send to the parent
    473          super_nodes = []
    474          # We want to include any non-handled nodes.
    475          super_nodes.extend(self.child_super_nodes)
    476          # And our nodes as well.
    477          super_nodes.extend(self.super_nodes)
    478
    479          t = TemplateParser(text,
    480                      name         = filename,
    481                      context      = self.context,
    482                      path         = self.path,
    483                      writer       = self.writer,
    484                      delimiters   = self.delimiters,
    485                      _super_nodes = super_nodes)
    486
    487          # Make a temporary buffer that is unique for parent
    488          # template.
    489          buf = BlockNode(name='__include__' + filename, delimiters=self.delimiters)
    490          pre = []
    491
    492          # Iterate through each of our nodes
    493          for node in self.content.nodes:
    494              # If a node is a block
    495              if isinstance(node, BlockNode):
    496                  # That happens to be in the parent template
    497                  if node.name in t.content.blocks:
    498                      # Do not include it
    499                      continue
    500
    501              if isinstance(node, Node):
    502                  # Or if the node was before the extension
    503                  # we should not include it
    504                  if node.pre_extend:
    505                      pre.append(node)
    506                      continue
    507
    508              # Otherwise, it should go int the
    509              # Parent templates {{include}} section.
    510                  buf.append(node)
    511              else:
    512                  buf.append(node)
    513
    514          # Clear our current nodes. We will be replacing this with
    515          # the parent nodes.
    516          self.content.nodes = []
    517
    518          # Set our include, unique by filename
    519          t.content.blocks['__include__' + filename] = buf
    520
    521          # Make sure our pre_extended nodes go first
    522          t.content.insert(pre)
    523
    524          # Then we extend our blocks
    525          t.content.extend(self.content)
    526
    527          # Work off the parent node.
    528          self.content = t.content
    529
    530 -    def parse(self, text):
    531
    532          # Basically, r_tag.split will split the text into
    533          # an array containing, 'non-tag', 'tag', 'non-tag', 'tag'
    534          # so if we alternate this variable, we know
    535          # what to look for. This is alternate to
    536          # line.startswith("{{")
    537          in_tag = False
    538          extend = None
    539          pre_extend = True
    540
    541          # Use a list to store everything in
    542          # This is because later the code will "look ahead"
    543          # for missing strings or brackets.
    544          ij = self.r_tag.split(text)
    545          # j = current index
    546          # i = current item
    547          for j in range(len(ij)):
    548              i = ij[j]
    549
    550              if i:
    551                  if len(self.stack) == 0:
    552                      self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
    553
    554                  # Our current element in the stack.
    555                  top = self.stack[-1]
    556
    557                  if in_tag:
    558                      line = i
    559
    560                      # If we are missing any strings!!!!
    561                      # This usually happens with the following example
    562                      # template code
    563                      #
    564                      # {{a = '}}'}}
    565                      # or
    566                      # {{a = '}}blahblah{{'}}
    567                      #
    568                      # This will fix these
    569                      # This is commented out because the current template
    570                      # system has this same limitation. Since this has a
    571                      # performance hit on larger templates, I do not recommend
    572                      # using this code on production systems. This is still here
    573                      # for "i told you it *can* be fixed" purposes.
    574                      #
    575                      #
    576  #                    if line.count("'") % 2 != 0 or line.count('"') % 2 != 0:
    577  #
    578  #                        # Look ahead
    579  #                        la = 1
    580  #                        nextline = ij[j+la]
    581  #
    582  #                        # As long as we have not found our ending
    583  #                        # brackets keep going
    584  #                        while '}}' not in nextline:
    585  #                            la += 1
    586  #                            nextline += ij[j+la]
    587  #                            # clear this line, so we
    588  #                            # don't attempt to parse it
    589  #                            # this is why there is an "if i"
    590  #                            # around line 530
    591  #                            ij[j+la] = ''
    592  #
    593  #                        # retrieve our index.
    594  #                        index = nextline.index('}}')
    595  #
    596  #                        # Everything before the new brackets
    597  #                        before = nextline[:index+2]
    598  #
    599  #                        # Everything after
    600  #                        after = nextline[index+2:]
    601  #
    602  #                        # Make the next line everything after
    603  #                        # so it parses correctly, this *should* be
    604  #                        # all html
    605  #                        ij[j+1] = after
    606  #
    607  #                        # Add everything before to the current line
    608  #                        line += before
    609
    610                      # Get rid of '{{' and '}}'
    611                      line = line[2:-2].strip()
    612
    613                      # This is bad juju, but let's do it anyway
    614                      if not line:
    615                          continue
    616
    617                      # We do not want to replace the newlines in code,
    618                      # only in block comments.
    619                      def remove_newline(re_val):
    620                          # Take the entire match and replace newlines with
    621                          # escaped newlines.
    622                          return re_val.group(0).replace('\n', '\\n')
    623
    624                      # Perform block comment escaping.
    625                      # This performs escaping ON anything
    626                      # in between """ and """
    627                      line = re.sub(TemplateParser.r_multiline,
    628                                  remove_newline,
    629                                  line)
    630
    631                      if line.startswith('='):
    632                          # IE: {{=response.title}}
    633                          name, value = '=', line[1:].strip()
    634                      else:
    635                          v = line.split(' ', 1)
    636                          if len(v) == 1:
    637                              # Example
    638                              # {{ include }}
    639                              # {{ end }}
    640                              name = v[0]
    641                              value = ''
    642                          else:
    643                              # Example
    644                              # {{ block pie }}
    645                              # {{ include "layout.html" }}
    646                              # {{ for i in range(10): }}
    647                              name = v[0]
    648                              value = v[1]
    649
    650                      # This will replace newlines in block comments
    651                      # with the newline character. This is so that they
    652                      # retain their formatting, but squish down to one
    653                      # line in the rendered template.
    654
    655                      # First check if we have any custom lexers
    656                      if name in self.lexers:
    657                          # Pass the information to the lexer
    658                          # and allow it to inject in the environment
    659
    660                          # You can define custom names such as
    661                          # '{{<<variable}}' which could potentially
    662                          # write unescaped version of the variable.
    663                          self.lexers[name](parser    = self,
    664                                            value     = value,
    665                                            top       = top,
    666                                            stack     = self.stack,)
    667
    668                      elif name == '=':
    669                          # So we have a variable to insert into
    670                          # the template
    671                          buf = "\n%s(%s)" % (self.writer, value)
    672                          top.append(Node(buf, pre_extend = pre_extend))
    673
    674                      elif name == 'block' and not value.startswith('='):
    675                          # Make a new node with name.
    676                          node = BlockNode(name = value.strip(),
    677                                           pre_extend = pre_extend,
    678                                           delimiters = self.delimiters)
    679
    680                          # Append this node to our active node
    681                          top.append(node)
    682
    683                          # Make sure to add the node to the stack.
    684                          # so anything after this gets added
    685                          # to this node. This allows us to
    686                          # "nest" nodes.
    687                          self.stack.append(node)
    688
    689                      elif name == 'end' and not value.startswith('='):
    690                          # We are done with this node.
    691
    692                          # Save an instance of it
    693                          self.blocks[top.name] = top
    694
    695                          # Pop it.
    696                          self.stack.pop()
    697
    698                      elif name == 'super' and not value.startswith('='):
    699                          # Get our correct target name
    700                          # If they just called {{super}} without a name
    701                          # attempt to assume the top blocks name.
    702                          if value:
    703                              target_node = value
    704                          else:
    705                              target_node = top.name
    706
    707                          # Create a SuperNode instance
    708                          node = SuperNode(name = target_node,
    709                                              pre_extend = pre_extend)
    710
    711                          # Add this to our list to be taken care of
    712                          self.super_nodes.append(node)
    713
    714                          # And put in in the tree
    715                          top.append(node)
    716
    717                      elif name == 'include' and not value.startswith('='):
    718                          # If we know the target file to include
    719                          if value:
    720                              self.include(top, value)
    721
    722                          # Otherwise, make a temporary include node
    723                          # That the child node will know to hook into.
    724                          else:
    725                              include_node = BlockNode(name = '__include__' + self.name,
    726                                                       pre_extend = pre_extend,
    727                                                       delimiters = self.delimiters)
    728                              top.append(include_node)
    729
    730                      elif name == 'extend' and not value.startswith('='):
    731                          # We need to extend the following
    732                          # template.
    733                          extend = value
    734                          pre_extend = False
    735
    736                      else:
    737                          # If we don't know where it belongs
    738                          # we just add it anyways without formatting.
    739                          if line and in_tag:
    740
    741                              # Split on the newlines >.<
    742                              tokens = line.split('\n')
    743
    744                              # We need to look for any instances of
    745                              # for i in range(10):
    746                              #   = i
    747                              # pass
    748                              # So we can properly put a response.write() in place.
    749                              continuation = False
    750                              len_parsed = 0
    751                              for k in range(len(tokens)):
    752
    753                                  tokens[k] = tokens[k].strip()
    754                                  len_parsed += len(tokens[k])
    755
    756                                  if tokens[k].startswith('='):
    757                                      if tokens[k].endswith('\\'):
    758                                          continuation = True
    759                                          tokens[k] = "\n%s(%s" % (self.writer, tokens[k][1:].strip())
    760                                      else:
    761                                          tokens[k] = "\n%s(%s)" % (self.writer, tokens[k][1:].strip())
    762                                  elif continuation:
    763                                      tokens[k] += ')'
    764                                      continuation = False
    765
    766
    767                              buf = "\n%s" % '\n'.join(tokens)
    768                              top.append(Node(buf, pre_extend = pre_extend))
    769
    770                  else:
    771                      # It is HTML so just include it.
    772                      buf = "\n%s(%r, escape=False)" % (self.writer, i)
    773                      top.append(Node(buf, pre_extend = pre_extend))
    774
    775              # Remember: tag, not tag, tag, not tag
    776              in_tag = not in_tag
    777
    778          # Make a list of items to remove from child
    779          to_rm = []
    780
    781          # Go through each of the children nodes
    782          for node in self.child_super_nodes:
    783              # If we declared a block that this node wants to include
    784              if node.name in self.blocks:
    785                  # Go ahead and include it!
    786                  node.value = self.blocks[node.name]
    787                  # Since we processed this child, we don't need to
    788                  # pass it along to the parent
    789                  to_rm.append(node)
    790
    791          # Remove some of the processed nodes
    792          for node in to_rm:
    793              # Since this is a pointer, it works beautifully.
    794              # Sometimes I miss C-Style pointers... I want my asterisk...
    795              self.child_super_nodes.remove(node)
    796
    797          # If we need to extend a template.
    798          if extend:
    799              self.extend(extend)
    800
    801  # We need this for integration with gluon
    802 -def parse_template(filename,
    803                     path    = 'views/',
    804                     context = dict(),
    805                     lexers  = {},
    806                     delimiters = ('{{','}}')
    807                     ):
    808      """
    809      filename can be a view filename in the views folder or an input stream
    810      path is the path of a views folder
    811      context is a dictionary of symbols used to render the template
    812      """
    813
    814      # First, if we have a str try to open the file
    815      if isinstance(filename, str):
    816          try:
    817              fp = open(os.path.join(path, filename), 'rb')
    818              text = fp.read()
    819              fp.close()
    820          except IOError:
    821              raise RestrictedError(filename, '', 'Unable to find the file')
    822      else:
    823          text = filename.read()
    824
    825      # Use the file contents to get a parsed template and return it.
    826      return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
    827
    828 -def get_parsed(text):
    829      """
    830      Returns the indented python code of text. Useful for unit testing.
    831
    832      """
    833      return str(TemplateParser(text))
    834
    835  # And this is a generic render function.
    836  # Here for integration with gluon.
    837 -def render(content = "hello world",
    838             stream = None,
    839             filename = None,
    840             path = None,
    841             context = {},
    842             lexers  = {},
    843             delimiters = ('{{','}}')
    844             ):
    845      """
    846      >>> render()
    847      'hello world'
    848      >>> render(content='abc')
    849      'abc'
    850      >>> render(content='abc\\'')
    851      "abc'"
    852      >>> render(content='a"\\'bc')
    853      'a"\\'bc'
    854      >>> render(content='a\\nbc')
    855      'a\\nbc'
    856      >>> render(content='a"bcd"e')
    857      'a"bcd"e'
    858      >>> render(content="'''a\\nc'''")
    859      "'''a\\nc'''"
    860      >>> render(content="'''a\\'c'''")
    861      "'''a\'c'''"
    862      >>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
    863      '0<br />1<br />2<br />3<br />4<br />'
    864      >>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
    865      '0<br />1<br />2<br />3<br />4<br />'
    866      >>> render(content="{{='''hello\\nworld'''}}")
    867      'hello\\nworld'
    868      >>> render(content='{{for i in range(3):\\n=i\\npass}}')
    869      '012'
    870      """
    871      # Here to avoid circular Imports
    872      try:
    873          from globals import Response
    874      except:
    875          # Working standalone. Build a mock Response object.
    876          class Response():
    877              def __init__(self):
    878                  self.body = cStringIO.StringIO()
    879              def write(self, data, escape=True):
    880                  if not escape:
    881                      self.body.write(str(data))
    882                  elif hasattr(data,'xml') and callable(data.xml):
    883                      self.body.write(data.xml())
    884                  else:
    885                      # make it a string
    886                      if not isinstance(data, (str, unicode)):
    887                          data = str(data)
    888                      elif isinstance(data, unicode):
    889                          data = data.encode('utf8', 'xmlcharrefreplace')
    890                      data = cgi.escape(data, True).replace("'","&#x27;")
    891                      self.body.write(data)
    892
    893          # A little helper to avoid escaping.
    894          class NOESCAPE():
    895              def __init__(self, text):
    896                  self.text = text
    897              def xml(self):
    898                  return self.text
    899          # Add it to the context so we can use it.
    900          context['NOESCAPE'] = NOESCAPE
    901
    902      # If we don't have anything to render, why bother?
    903      if not content and not stream and not filename:
    904          raise SyntaxError, "Must specify a stream or filename or content"
    905
    906      # Here for legacy purposes, probably can be reduced to something more simple.
    907      close_stream = False
    908      if not stream:
    909          if filename:
    910              stream = open(filename, 'rb')
    911              close_stream = True
    912          elif content:
    913              stream = cStringIO.StringIO(content)
    914
    915      # Get a response class.
    916      context['response'] = Response()
    917
    918      # Execute the template.
    919      code = str(TemplateParser(stream.read(), context=context, path=path, lexers=lexers, delimiters=delimiters))
    920      try:
    921          exec(code) in context
    922      except Exception:
    923          # for i,line in enumerate(code.split('\n')): print i,line
    924          raise
    925
    926      if close_stream:
    927          stream.close()
    928
    929      # Returned the rendered content.
    930      return context['response'].body.getvalue()
    931
    932
    933  if __name__ == '__main__':
    934      import doctest
    935      doctest.testmod()
    936