parameters: (self, template, fpos=0)
return_statement: return parsetree
docstring: Recursive part of `parse()`.
  
          :Parameters:
              - template
              - fpos: position of ``template`` in the complete template (for error-messages)
docstring_summary: Recursive part of `parse()`.
docstring_tokens: Recursive part of parse () .
function: def _parse(self, template, fpos=0):
          """Recursive part of `parse()`.
  
          :Parameters:
              - template
              - fpos: position of ``template`` in the complete template (for error-messages)
          """
          # blank out comments
          # (So that its content does not collide with other syntax, and
          #  because removing them completely would falsify the character-
          #  position ("match.start()") of error-messages)
          template = self._reComment.sub(lambda match: self._comment_start+" "*len(match.group(1))+match.group(2), template)
  
          # init parser
          parsetree = []
          curr = 0            # current position (= end of previous block)
          block_type = None   # block type: if,for,macro,raw,...
          block_indent = None # None: single-line, >=0: multi-line
  
          # find blocks
          for match in self._reBlock.finditer(template):
              start = match.start()
              # process template-part before this block
              if start > curr:
                  self._parse_sub(parsetree, template[curr:start], fpos)
  
              # analyze block syntax (incl. error-checking and -messages)
              keyword = None
              block = match.groupdict()
              pos__ = fpos + start                # shortcut
              if   block["sKeyw"] is not None:    # single-line block tag
                  block_indent = None
                  keyword = block["sKeyw"]
                  param   = block["sParam"]
                  content = block["sContent"]
                  if block["sSpace"]:             # restore spaces before start-tag
                      if len(parsetree) > 0 and parsetree[-1][0] == "str":
                          parsetree[-1] = ("str", parsetree[-1][1] + block["sSpace"])
                      else:
                          parsetree.append(("str", block["sSpace"]))
                  pos_p = fpos + match.start("sParam")    # shortcuts
                  pos_c = fpos + match.start("sContent")
              elif block["mKeyw"] is not None:    # multi-line block tag
                  block_indent = len(block["indent"])
                  keyword = block["mKeyw"]
                  param   = block["mParam"]
                  content = block["mContent"]
                  pos_p = fpos + match.start("mParam")
                  pos_c = fpos + match.start("mContent")
                  ignored = block["mIgnored"].strip()
                  if ignored  and  ignored != self._comment_start:
                      raise TemplateSyntaxError("No code allowed after block-tag.", self._errpos(fpos+match.start("mIgnored")))
              elif block["mEnd"] is not None:     # multi-line block end
                  if block_type is None:
                      raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos(pos__) )
                  if block_indent != len(block["mEnd"]):
                      raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos(pos__) )
                  ignored = block["meIgnored"].strip()
                  if ignored  and  ignored != self._comment_start:
                      raise TemplateSyntaxError("No code allowed after end-tag.", self._errpos(fpos+match.start("meIgnored")))
                  block_type = None
              elif block["sEnd"] is not None:     # single-line block end
                  if block_type is None:
                      raise TemplateSyntaxError("No block to end here/invalid indent.", self._errpos(pos__))
                  if block_indent is not None:
                      raise TemplateSyntaxError("Invalid indent for end-tag.", self._errpos(pos__))
                  block_type = None
              else:
                  raise TemplateException("FATAL: Block regexp error. Please contact the author. (%s)" % match.group())
  
              # analyze block content (mainly error-checking and -messages)
              if keyword:
                  keyword = keyword.lower()
                  if   'for'   == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'for'
                      cond = self._reForParam.match(param)
                      if cond is None:
                          raise TemplateSyntaxError("Invalid 'for ...' at '%s'." %(param), self._errpos(pos_p))
                      names = tuple(n.strip()  for n in cond.group("names").split(","))
                      self._testexpr(cond.group("iter"), pos_p+cond.start("iter"))
                      parsetree.append(("for", names, cond.group("iter"), self._parse(content, pos_c)))
                  elif 'if'    == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block at '%s'." %(match.group()), self._errpos(pos__))
                      if not param:
                          raise TemplateSyntaxError("Missing condition for 'if' at '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'if'
                      self._testexpr(param, pos_p)
                      parsetree.append(("if", param, self._parse(content, pos_c)))
                  elif 'elif'  == keyword:
                      if block_type != 'if':
                          raise TemplateSyntaxError("'elif' may only appear after 'if' at '%s'." %(match.group()), self._errpos(pos__))
                      if not param:
                          raise TemplateSyntaxError("Missing condition for 'elif' at '%s'." %(match.group()), self._errpos(pos__))
                      self._testexpr(param, pos_p)
                      parsetree.append(("elif", param, self._parse(content, pos_c)))
                  elif 'else'  == keyword:
                      if block_type not in ('if', 'for'):
                          raise TemplateSyntaxError("'else' may only appear after 'if' or 'for' at '%s'." %(match.group()), self._errpos(pos__))
                      if param:
                          raise TemplateSyntaxError("'else' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
                      parsetree.append(("else", self._parse(content, pos_c)))
                  elif 'macro' == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'macro'
                      # make sure param is "\w+" (instead of ".+")
                      if not param:
                          raise TemplateSyntaxError("Missing name for 'macro' at '%s'." %(match.group()), self._errpos(pos__))
                      if not self._reMacroParam.match(param):
                          raise TemplateSyntaxError("Invalid name for 'macro' at '%s'." %(match.group()), self._errpos(pos__))
                      #remove last newline
                      if len(content) > 0 and content[-1] == '\n':
                          content = content[:-1]
                      if len(content) > 0 and content[-1] == '\r':
                          content = content[:-1]
                      parsetree.append(("macro", param, self._parse(content, pos_c)))
  
                  # parser-commands
                  elif 'raw'   == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
                      if param:
                          raise TemplateSyntaxError("'raw' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'raw'
                      parsetree.append(("str", content))
                  elif 'include' == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
                      if param:
                          raise TemplateSyntaxError("'include' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'include'
                      try:
                          u = self._load(content.strip())
                      except Exception as err:
                          raise TemplateIncludeError(err, self._errpos(pos__))
                      self._includestack.append((content.strip(), u))  # current filename/template for error-msg.
                      p = self._parse(u)
                      self._includestack.pop()
                      parsetree.extend(p)
                  elif 'set_escape' == keyword:
                      if block_type is not None:
                          raise TemplateSyntaxError("Missing block-end-tag before new block '%s'." %(match.group()), self._errpos(pos__))
                      if param:
                          raise TemplateSyntaxError("'set_escape' may not have parameters at '%s'." %(match.group()), self._errpos(pos__))
                      block_type = 'set_escape'
                      esc = content.strip().upper()
                      if esc not in ESCAPE_SUPPORTED:
                          raise TemplateSyntaxError("Unsupported escape '%s'." %(esc), self._errpos(pos__))
                      self.escape = ESCAPE_SUPPORTED[esc]
                  else:
                      raise TemplateSyntaxError("Invalid keyword '%s'." %(keyword), self._errpos(pos__))
              curr = match.end()
  
          if block_type is not None:
              raise TemplateSyntaxError("Missing end-tag.", self._errpos(pos__))
  
          if len(template) > curr:            # process template-part after last block
              self._parse_sub(parsetree, template[curr:], fpos+curr)
  
          return parsetree
function_tokens: def _parse ( self , template , fpos = 0 ) : # blank out comments # (So that its content does not collide with other syntax, and #  because removing them completely would falsify the character- #  position ("match.start()") of error-messages) template = self . _reComment . sub ( lambda match : self . _comment_start + " " * len ( match . group ( 1 ) ) + match . group ( 2 ) , template ) # init parser parsetree = [ ] curr = 0 # current position (= end of previous block) block_type = None # block type: if,for,macro,raw,... block_indent = None # None: single-line, >=0: multi-line # find blocks for match in self . _reBlock . finditer ( template ) : start = match . start ( ) # process template-part before this block if start > curr : self . _parse_sub ( parsetree , template [ curr : start ] , fpos ) # analyze block syntax (incl. error-checking and -messages) keyword = None block = match . groupdict ( ) pos__ = fpos + start # shortcut if block [ "sKeyw" ] is not None : # single-line block tag block_indent = None keyword = block [ "sKeyw" ] param = block [ "sParam" ] content = block [ "sContent" ] if block [ "sSpace" ] : # restore spaces before start-tag if len ( parsetree ) > 0 and parsetree [ - 1 ] [ 0 ] == "str" : parsetree [ - 1 ] = ( "str" , parsetree [ - 1 ] [ 1 ] + block [ "sSpace" ] ) else : parsetree . append ( ( "str" , block [ "sSpace" ] ) ) pos_p = fpos + match . start ( "sParam" ) # shortcuts pos_c = fpos + match . start ( "sContent" ) elif block [ "mKeyw" ] is not None : # multi-line block tag block_indent = len ( block [ "indent" ] ) keyword = block [ "mKeyw" ] param = block [ "mParam" ] content = block [ "mContent" ] pos_p = fpos + match . start ( "mParam" ) pos_c = fpos + match . start ( "mContent" ) ignored = block [ "mIgnored" ] . strip ( ) if ignored and ignored != self . _comment_start : raise TemplateSyntaxError ( "No code allowed after block-tag." , self . _errpos ( fpos + match . start ( "mIgnored" ) ) ) elif block [ "mEnd" ] is not None : # multi-line block end if block_type is None : raise TemplateSyntaxError ( "No block to end here/invalid indent." , self . _errpos ( pos__ ) ) if block_indent != len ( block [ "mEnd" ] ) : raise TemplateSyntaxError ( "Invalid indent for end-tag." , self . _errpos ( pos__ ) ) ignored = block [ "meIgnored" ] . strip ( ) if ignored and ignored != self . _comment_start : raise TemplateSyntaxError ( "No code allowed after end-tag." , self . _errpos ( fpos + match . start ( "meIgnored" ) ) ) block_type = None elif block [ "sEnd" ] is not None : # single-line block end if block_type is None : raise TemplateSyntaxError ( "No block to end here/invalid indent." , self . _errpos ( pos__ ) ) if block_indent is not None : raise TemplateSyntaxError ( "Invalid indent for end-tag." , self . _errpos ( pos__ ) ) block_type = None else : raise TemplateException ( "FATAL: Block regexp error. Please contact the author. (%s)" % match . group ( ) ) # analyze block content (mainly error-checking and -messages) if keyword : keyword = keyword . lower ( ) if 'for' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'for' cond = self . _reForParam . match ( param ) if cond is None : raise TemplateSyntaxError ( "Invalid 'for ...' at '%s'." % ( param ) , self . _errpos ( pos_p ) ) names = tuple ( n . strip ( ) for n in cond . group ( "names" ) . split ( "," ) ) self . _testexpr ( cond . group ( "iter" ) , pos_p + cond . start ( "iter" ) ) parsetree . append ( ( "for" , names , cond . group ( "iter" ) , self . _parse ( content , pos_c ) ) ) elif 'if' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if not param : raise TemplateSyntaxError ( "Missing condition for 'if' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'if' self . _testexpr ( param , pos_p ) parsetree . append ( ( "if" , param , self . _parse ( content , pos_c ) ) ) elif 'elif' == keyword : if block_type != 'if' : raise TemplateSyntaxError ( "'elif' may only appear after 'if' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if not param : raise TemplateSyntaxError ( "Missing condition for 'elif' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) self . _testexpr ( param , pos_p ) parsetree . append ( ( "elif" , param , self . _parse ( content , pos_c ) ) ) elif 'else' == keyword : if block_type not in ( 'if' , 'for' ) : raise TemplateSyntaxError ( "'else' may only appear after 'if' or 'for' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if param : raise TemplateSyntaxError ( "'else' may not have parameters at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) parsetree . append ( ( "else" , self . _parse ( content , pos_c ) ) ) elif 'macro' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'macro' # make sure param is "\w+" (instead of ".+") if not param : raise TemplateSyntaxError ( "Missing name for 'macro' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if not self . _reMacroParam . match ( param ) : raise TemplateSyntaxError ( "Invalid name for 'macro' at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) #remove last newline if len ( content ) > 0 and content [ - 1 ] == '\n' : content = content [ : - 1 ] if len ( content ) > 0 and content [ - 1 ] == '\r' : content = content [ : - 1 ] parsetree . append ( ( "macro" , param , self . _parse ( content , pos_c ) ) ) # parser-commands elif 'raw' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if param : raise TemplateSyntaxError ( "'raw' may not have parameters at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'raw' parsetree . append ( ( "str" , content ) ) elif 'include' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if param : raise TemplateSyntaxError ( "'include' may not have parameters at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'include' try : u = self . _load ( content . strip ( ) ) except Exception as err : raise TemplateIncludeError ( err , self . _errpos ( pos__ ) ) self . _includestack . append ( ( content . strip ( ) , u ) ) # current filename/template for error-msg. p = self . _parse ( u ) self . _includestack . pop ( ) parsetree . extend ( p ) elif 'set_escape' == keyword : if block_type is not None : raise TemplateSyntaxError ( "Missing block-end-tag before new block '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) if param : raise TemplateSyntaxError ( "'set_escape' may not have parameters at '%s'." % ( match . group ( ) ) , self . _errpos ( pos__ ) ) block_type = 'set_escape' esc = content . strip ( ) . upper ( ) if esc not in ESCAPE_SUPPORTED : raise TemplateSyntaxError ( "Unsupported escape '%s'." % ( esc ) , self . _errpos ( pos__ ) ) self . escape = ESCAPE_SUPPORTED [ esc ] else : raise TemplateSyntaxError ( "Invalid keyword '%s'." % ( keyword ) , self . _errpos ( pos__ ) ) curr = match . end ( ) if block_type is not None : raise TemplateSyntaxError ( "Missing end-tag." , self . _errpos ( pos__ ) ) if len ( template ) > curr : # process template-part after last block self . _parse_sub ( parsetree , template [ curr : ] , fpos + curr ) return parsetree