Source code for simpletal.simpleTAL

# -*- coding: iso-8859-1 -*-


#    Copyright (c) 2016, Jan Brohl <janbrohl@t-online.de>
#    All rights reserved.
#    See LICENSE.txt

#    Copyright (c) 2005 Colin Stewart (http://www.owlfish.com/)
#    All rights reserved.
#
#    Redistribution and use in source and binary forms, with or without
#    modification, are permitted provided that the following conditions
#    are met:
#    1. Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#    3. The name of the author may not be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
#    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
#    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#    If you make any bug fixes or feature enhancements please let me know!

""" simpleTAL Interpreter	
		
		The classes in this module implement the TAL language, expanding
		both XML and HTML templates.
		
"""

from __future__ import unicode_literals
from __future__ import absolute_import

import xml.sax
import cgi
import codecs
import re
import xml.sax.saxutils
import copy
import sys
import simpletal.simpleTALES
from simpletal.simpleTALConstants import *
import logging

import io

if sys.version_info >= (3, 0):
    unicode = str
    unichr = chr
    import html.parser as HTMLParser
    import html.entities as htmlentitydefs
else:
    import HTMLParser
    import htmlentitydefs

name2unicode = dict((k, unichr(v))
                    for k, v in htmlentitydefs.name2codepoint.items())

METAL_NAME_REGEX = re.compile("[a-zA-Z_][a-zA-Z0-9_]*")
SINGLETON_XML_REGEX = re.compile(b'^<[^\s/>]+(?:\s*[^=>]+="[^">]+")*\s*/>')


try:
    # Is PyXML's LexicalHandler available?
    from xml.sax.saxlib import LexicalHandler
    use_lexical_handler = 1
except ImportError:
    use_lexical_handler = 0

[docs] class LexicalHandler(object): "Dummy lexical handdler class" pass
try: # Is PyXML's DOM2SAX available? import xml.dom.ext.Dom2Sax use_dom2sax = 1 except ImportError: use_dom2sax = 0
[docs]class TemplateInterpreter(object): def __init__(self): self.programStack = [] self.commandList = None self.symbolTable = None self.slotParameters = {} self.commandHandler = {TAL_DEFINE: self.cmdDefine, TAL_CONDITION: self.cmdCondition, TAL_REPEAT: self.cmdRepeat, TAL_CONTENT: self.cmdContent, TAL_ATTRIBUTES: self.cmdAttributes, TAL_OMITTAG: self.cmdOmitTag, TAL_START_SCOPE: self.cmdStartScope, TAL_OUTPUT: self.cmdOutput, TAL_STARTTAG: self.cmdOutputStartTag, TAL_ENDTAG_ENDSCOPE: self.cmdEndTagEndScope, METAL_USE_MACRO: self.cmdUseMacro, METAL_DEFINE_SLOT: self.cmdDefineSlot, TAL_NOOP: self.cmdNoOp}
[docs] def tagAsText(self, tag_atts, singletonFlag=0): """ This returns a tag as text. """ (tag, atts) = tag_atts result = ["<"] result.append(tag) for attName, attValue in atts: result.append(' ') result.append(attName) result.append('=') result.append(xml.sax.saxutils.quoteattr(attValue)) if (singletonFlag): result.append(" />") else: result.append(">") return "".join(result)
[docs] def initialise(self, context, outputFile): self.context = context self.file = outputFile
[docs] def cleanState(self): self.scopeStack = [] self.programCounter = 0 self.movePCForward = None self.movePCBack = None self.outputTag = 1 self.originalAttributes = {} self.currentAttributes = [] # Used in repeat only. self.repeatAttributesCopy = [] self.currentSlots = {} self.repeatVariable = None self.tagContent = None # tagState flag as to whether there are any local variables to pop self.localVarsDefined = 0 # Pass in the parameters self.currentSlots = self.slotParameters
[docs] def popProgram(self): v, self.commandList, self.symbolTable = self.programStack.pop() (self.programCounter, self.scopeStack, self.slotParameters, self.currentSlots, self.movePCForward, self.movePCBack, self.outputTag, self.originalAttributes, self.currentAttributes, self.repeatVariable, self.repeatAttributesCopy, self.tagContent, self.localVarsDefined) = v
[docs] def pushProgram(self): v = (self.programCounter, self.scopeStack, self.slotParameters, self.currentSlots, self.movePCForward, self.movePCBack, self.outputTag, self.originalAttributes, self.currentAttributes, self.repeatVariable, self.repeatAttributesCopy, self.tagContent, self.localVarsDefined) self.programStack.append((v, self.commandList, self.symbolTable))
[docs] def execute(self, template): self.cleanState() (self.commandList, self.programCounter, programLength, self.symbolTable) = template.getProgram() cmndList = self.commandList while (self.programCounter < programLength): cmnd = cmndList[self.programCounter] # print "PC: %s - Executing command: %s" % (unicode # (self.programCounter), unicode (cmnd)) self.commandHandler[cmnd[0]](cmnd[0], cmnd[1])
[docs] def cmdDefine(self, command, args): """ args: [(isLocalFlag (Y/n), variableName, variablePath),...] Define variables in either the local or global context """ foundLocals = 0 for isLocal, varName, varPath in args: result = self.context.evaluate(varPath, self.originalAttributes) if (isLocal): if (not foundLocals): foundLocals = 1 self.context.pushLocals() self.context.setLocal(varName, result) else: self.context.addGlobal(varName, result) self.localVarsDefined = foundLocals self.programCounter += 1
[docs] def cmdCondition(self, command, args): """ args: expression, endTagSymbol Conditionally continues with execution of all content contained by it. """ result = self.context.evaluate(args[0], self.originalAttributes) #~ if (result is None or (not result)): conditionFalse = 0 if (result is None): conditionFalse = 1 else: if (not result): conditionFalse = 1 try: temp = len(result) if (temp == 0): conditionFalse = 1 except: # Result is not a sequence. pass if (conditionFalse): # Nothing to output - evaluated to false. self.outputTag = 0 self.tagContent = None self.programCounter = self.symbolTable[args[1]] return self.programCounter += 1
[docs] def cmdRepeat(self, command, args): """ args: (varName, expression, endTagSymbol) Repeats anything in the cmndList """ if (self.repeatVariable is not None): # We are already part way through a repeat # Restore any attributes that might have been changed. if (self.currentAttributes != self.repeatAttributesCopy): self.currentAttributes = copy.copy(self.repeatAttributesCopy) self.outputTag = 1 self.tagContent = None self.movePCForward = None try: self.repeatVariable.increment() self.context.setLocal( args[0], self.repeatVariable.getCurrentValue()) self.programCounter += 1 return except IndexError as e: # We have finished the repeat self.repeatVariable = None self.context.removeRepeat(args[0]) # The locals were pushed in context.addRepeat self.context.popLocals() self.movePCBack = None # Suppress the final close tag and content self.tagContent = None self.outputTag = 0 self.programCounter = self.symbolTable[args[2]] # Restore the state of repeatAttributesCopy in case we are # nested. self.repeatAttributesCopy = self.scopeStack.pop() return # The first time through this command result = self.context.evaluate(args[1], self.originalAttributes) if (result is not None and result is simpletal.simpleTALES.DEFAULTVALUE): # Leave everything un-touched. self.programCounter += 1 return try: # We have three options, either the result is a natural sequence, # an iterator., or something that can produce an iterator. isSequence = len(result) if (isSequence): # Only setup if we have a sequence with length self.repeatVariable = simpletal.simpleTALES.RepeatVariable( result) else: # Delete the tags and their contents self.outputTag = 0 self.programCounter = self.symbolTable[args[2]] return except: # Not a natural sequence, can it produce an iterator? try: it = iter(result) except TypeError: # Just a plain object, let's not loop # Delete the tags and their contents self.outputTag = 0 self.programCounter = self.symbolTable[args[2]] return else: # We can get an iterator! self.repeatVariable = simpletal.simpleTALES.IteratorRepeatVariable( it) try: curValue = self.repeatVariable.getCurrentValue() except IndexError as e: # The iterator ran out of values before we started - treat as an # empty list self.outputTag = 0 self.repeatVariable = None self.programCounter = self.symbolTable[args[2]] return # We really do want to repeat - so lets do it self.movePCBack = self.programCounter self.context.addRepeat(args[0], self.repeatVariable, curValue) # We keep the old state of the repeatAttributesCopy for nested loops self.scopeStack.append(self.repeatAttributesCopy) # Keep a copy of the current attributes for this tag self.repeatAttributesCopy = copy.copy(self.currentAttributes) self.programCounter += 1
[docs] def cmdContent(self, command, args): """ args: (replaceFlag, structureFlag, expression, endTagSymbol) Expands content """ result = self.context.evaluate(args[2], self.originalAttributes) if (result is None): if (args[0]): # Only output tags if this is a content not a replace self.outputTag = 0 # Output none of our content or the existing content, but # potentially the tags self.movePCForward = self.symbolTable[args[3]] self.programCounter += 1 return elif (result is not simpletal.simpleTALES.DEFAULTVALUE): # We have content, so let's suppress the natural content and output # this! if (args[0]): self.outputTag = 0 self.tagContent = (args[1], result) self.movePCForward = self.symbolTable[args[3]] self.programCounter += 1 return else: # Default, let's just run through as normal self.programCounter += 1 return
[docs] def cmdAttributes(self, command, args): """ args: [(attributeName, expression)] Add, leave, or remove attributes from the start tag """ attsToRemove = set() newAtts = [] for attName, attExpr in args: resultVal = self.context.evaluate(attExpr, self.originalAttributes) if (resultVal is None): # Remove this attribute from the current attributes attsToRemove.add(attName) elif (resultVal is not simpletal.simpleTALES.DEFAULTVALUE): # We have a value - let's use it! attsToRemove.add(attName) if (isinstance(resultVal, unicode)): escapedAttVal = resultVal elif (isinstance(resultVal, bytes)): # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not using # Ascii escapedAttVal = unicode(resultVal, 'ascii') else: # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not using # Ascii escapedAttVal = unicode(resultVal) newAtts.append((attName, escapedAttVal)) # Copy over the old attributes for oldAttName, oldAttValue in self.currentAttributes: if (oldAttName not in attsToRemove): newAtts.append((oldAttName, oldAttValue)) self.currentAttributes = newAtts # Evaluate all other commands self.programCounter += 1
[docs] def cmdOmitTag(self, command, args): """ args: expression Conditionally turn off tag output """ result = self.context.evaluate(args, self.originalAttributes) if (result is not None and result): # Turn tag output off self.outputTag = 0 self.programCounter += 1
[docs] def cmdOutputStartTag(self, command, args): # Args: tagName tagName, singletonTag = args if (self.outputTag): if (self.tagContent is None and singletonTag): self.file.write(self.tagAsText( (tagName, self.currentAttributes), 1)) else: self.file.write(self.tagAsText( (tagName, self.currentAttributes))) if (self.movePCForward is not None): self.programCounter = self.movePCForward return self.programCounter += 1 return
[docs] def cmdEndTagEndScope(self, command, args): # Args: tagName, omitFlag, singletonTag if (self.tagContent is not None): contentType, resultVal = self.tagContent if (contentType): if (isinstance(resultVal, Template)): # We have another template in the context, evaluate it! # Save our state! self.pushProgram() resultVal.expandInline(self.context, self.file, self) # Restore state self.popProgram() # End of the macro expansion (if any) so clear the # parameters self.slotParameters = {} else: if (isinstance(resultVal, unicode)): self.file.write(resultVal) elif (isinstance(resultVal, bytes)): # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not # using Ascii self.file.write(unicode(resultVal, 'ascii')) else: # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not # using Ascii self.file.write(unicode(resultVal)) else: if (isinstance(resultVal, unicode)): self.file.write(xml.sax.saxutils.escape(resultVal)) elif (isinstance(resultVal, bytes)): # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not using # Ascii self.file.write(xml.sax.saxutils.escape( unicode(resultVal, 'ascii'))) else: # THIS IS NOT A BUG! # Use Unicode in the Context object if you are not using # Ascii self.file.write( xml.sax.saxutils.escape(unicode(resultVal))) if (self.outputTag and not args[1]): # Do NOT output end tag if a singleton with no content if not (args[2] and self.tagContent is None): self.file.write('</' + args[0] + '>') if (self.movePCBack is not None): self.programCounter = self.movePCBack return if (self.localVarsDefined): self.context.popLocals() self.movePCForward, self.movePCBack, self.outputTag, self.originalAttributes, self.currentAttributes, self.repeatVariable, self.tagContent, self.localVarsDefined = self.scopeStack.pop() self.programCounter += 1
[docs] def cmdOutput(self, command, args): self.file.write(args) self.programCounter += 1
[docs] def cmdStartScope(self, command, args): """ args: (originalAttributes, currentAttributes) Pushes the current state onto the stack, and sets up the new state """ self.scopeStack.append((self.movePCForward, self.movePCBack, self.outputTag, self.originalAttributes, self.currentAttributes, self.repeatVariable, self.tagContent, self.localVarsDefined)) self.movePCForward = None self.movePCBack = None self.outputTag = 1 self.originalAttributes = args[0] self.currentAttributes = args[1] self.repeatVariable = None self.tagContent = None self.localVarsDefined = 0 self.programCounter += 1
[docs] def cmdNoOp(self, command, args): self.programCounter += 1
[docs] def cmdUseMacro(self, command, args): """ args: (macroExpression, slotParams, endTagSymbol) Evaluates the expression, if it resolves to a SubTemplate it then places the slotParams into currentSlots and then jumps to the end tag """ value = self.context.evaluate(args[0], self.originalAttributes) if (value is None): # Don't output anything self.outputTag = 0 # Output none of our content or the existing content self.movePCForward = self.symbolTable[args[2]] self.programCounter += 1 return if (value is not simpletal.simpleTALES.DEFAULTVALUE and isinstance(value, SubTemplate)): # We have a macro, so let's use it self.outputTag = 0 self.slotParameters = args[1] self.tagContent = (1, value) # NOTE: WE JUMP STRAIGHT TO THE END TAG, NO OTHER TAL/METAL # COMMANDS ARE EVALUATED. self.programCounter = self.symbolTable[args[2]] return else: # Default, let's just run through as normal self.programCounter += 1 return
[docs] def cmdDefineSlot(self, command, args): """ args: (slotName, endTagSymbol) If the slotName is filled then that is used, otherwise the original conent is used. """ if (args[0] in self.currentSlots): # This slot is filled, so replace us with that content self.outputTag = 0 self.tagContent = (1, self.currentSlots[args[0]]) # Output none of our content or the existing content # NOTE: NO FURTHER TAL/METAL COMMANDS ARE EVALUATED self.programCounter = self.symbolTable[args[1]] return # Slot isn't filled, so just use our own content self.programCounter += 1 return
[docs]class HTMLTemplateInterpreter (TemplateInterpreter): def __init__(self, minimizeBooleanAtts=0): TemplateInterpreter.__init__(self) self.minimizeBooleanAtts = minimizeBooleanAtts if (minimizeBooleanAtts): # Override the tagAsText method for this instance self.tagAsText = self.tagAsTextMinimizeAtts
[docs] def tagAsTextMinimizeAtts(self, tag_atts, singletonFlag=0): """ This returns a tag as text. """ (tag, atts) = tag_atts result = ["<"] result.append(tag) upperTag = tag.upper() for attName, attValue in atts: if ((upperTag, attName.upper()) in HTML_BOOLEAN_ATTS): # We should output a minimised boolean value result.append(' ') result.append(attName) else: result.append(' ') result.append(attName) result.append('=') result.append(xml.sax.saxutils.quoteattr(attValue)) if (singletonFlag): result.append(" />") else: result.append(">") return "".join(result)
[docs]class Template(object): def __init__(self, commands, macros, symbols, doctype=None): self.commandList = commands self.macros = macros self.symbolTable = symbols self.doctype = doctype # Setup the macros for macro in self.macros.values(): macro.setParentTemplate(self) # Setup the slots for cmnd, arg in self.commandList: if (cmnd == METAL_USE_MACRO): # Set the parent of each slot slotMap = arg[1] for slot in slotMap.values(): slot.setParentTemplate(self)
[docs] def expand(self, context, outputFile, outputEncoding=None, interpreter=None): """ This method will write to the outputFile, using the encoding specified, the expanded version of this template. The context passed in is used to resolve all expressions with the template. """ # This method must wrap outputFile if required by the encoding, and write out # any template pre-amble (DTD, Encoding, etc) self.expandInline(context, outputFile, interpreter)
[docs] def expandInline(self, context, outputFile, interpreter=None): """ Internally used when expanding a template that is part of a context.""" if (interpreter is None): ourInterpreter = TemplateInterpreter() ourInterpreter.initialise(context, outputFile) else: ourInterpreter = interpreter try: ourInterpreter.execute(self) except UnicodeError as unierror: logging.error( "UnicodeError caused by placing a non-Unicode string in the Context object.") raise simpletal.simpleTALES.ContextContentException( "Found non-unicode string in Context!")
[docs] def getProgram(self): """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """ return (self.commandList, 0, len(self.commandList), self.symbolTable)
def __str__(self): result = "Commands:\n" index = 0 for cmd in self.commandList: if (cmd[0] != METAL_USE_MACRO): result = result + "\n[%s] %s" % (unicode(index), unicode(cmd)) else: result = result + \ "\n[%s] %s, (%s{" % (unicode(index), unicode(cmd[0]), unicode(cmd[1][0])) for slot in cmd[1][1].keys(): result = result + \ "%s: %s" % (slot, unicode(cmd[1][1][slot])) result = result + "}, %s)" % unicode(cmd[1][2]) index += 1 result = result + "\n\nSymbols:\n" for symbol in self.symbolTable.keys(): result = (result + "Symbol: " + unicode(symbol) + " points to: " + unicode(self.symbolTable[symbol]) + ", which is command: " + unicode(self.commandList[self.symbolTable[symbol]]) + "\n") result = result + "\n\nMacros:\n" for macro in self.macros.keys(): result = result + "Macro: " + \ unicode(macro) + " value of: " + unicode(self.macros[macro]) return result
[docs]class SubTemplate (Template): """ A SubTemplate is part of another template, and is used for the METAL implementation. The two uses for this class are: 1 - metal:define-macro results in a SubTemplate that is the macro 2 - metal:fill-slot results in a SubTemplate that is a parameter to metal:use-macro """ def __init__(self, startRange, endRangeSymbol): """ The parentTemplate is the template for which we are a sub-template. The startRange and endRange are indexes into the parent templates command list, and defines the range of commands that we can execute """ Template.__init__(self, [], {}, {}) self.startRange = startRange self.endRangeSymbol = endRangeSymbol
[docs] def setParentTemplate(self, parentTemplate): self.parentTemplate = parentTemplate self.commandList = parentTemplate.commandList self.symbolTable = parentTemplate.symbolTable
[docs] def getProgram(self): """ Returns a tuple of (commandList, startPoint, endPoint, symbolTable) """ return (self.commandList, self.startRange, self.symbolTable[self.endRangeSymbol] + 1, self.symbolTable)
def __str__(self): endRange = self.symbolTable[self.endRangeSymbol] result = "SubTemplate from %s to %s\n" % ( unicode(self.startRange), unicode(endRange)) return result
[docs]class HTMLTemplate (Template): """A specialised form of a template that knows how to output HTML """ def __init__(self, commands, macros, symbols, doctype=None, minimizeBooleanAtts=0): self.minimizeBooleanAtts = minimizeBooleanAtts Template.__init__(self, commands, macros, symbols, doctype=None)
[docs] def expand(self, context, outputFile, outputEncoding="utf-8", interpreter=None): """ This method will write to the outputFile, using the encoding specified, the expanded version of this template. The context passed in is used to resolve all expressions with the template. """ # This method must wrap outputFile if required by the encoding, and write out # any template pre-amble (DTD, Encoding, etc) if isinstance(outputFile, io.TextIOBase): encodingFile = outputFile else: writer = codecs.getwriter(outputEncoding) encodingFile = writer(outputFile, errors='xmlcharrefreplace') self.expandInline(context, encodingFile, interpreter)
[docs] def expandInline(self, context, outputFile, interpreter=None): """ Ensure we use the HTMLTemplateInterpreter""" if (interpreter is None): ourInterpreter = HTMLTemplateInterpreter( minimizeBooleanAtts=self.minimizeBooleanAtts) ourInterpreter.initialise(context, outputFile) else: ourInterpreter = interpreter Template.expandInline(self, context, outputFile, ourInterpreter)
[docs]class XMLTemplate (Template): """A specialised form of a template that knows how to output XML """ def __init__(self, commands, macros, symbols, doctype=None): Template.__init__(self, commands, macros, symbols) self.doctype = doctype
[docs] def expand(self, context, outputFile, outputEncoding="utf-8", docType=None, suppressXMLDeclaration=0, interpreter=None): """ This method will write to the outputFile, using the encoding specified, the expanded version of this template. The context passed in is used to resolve all expressions with the template. """ # This method must wrap outputFile if required by the encoding, and write out # any template pre-amble (DTD, Encoding, etc) # Write out the XML prolog if isinstance(outputFile, io.TextIOBase): encodingFile = outputFile else: writer = codecs.getwriter(outputEncoding) encodingFile = writer(outputFile, errors='xmlcharrefreplace') if (not suppressXMLDeclaration): oel = unicode(outputEncoding).lower() if (oel != "utf-8"): encodingFile.write( '<?xml version="1.0" encoding="%s"?>\n' % oel) else: encodingFile.write('<?xml version="1.0"?>\n') if not docType and self.doctype: docType = self.doctype if docType: encodingFile.write(docType) encodingFile.write('\n') self.expandInline(context, encodingFile, interpreter)
[docs]class TemplateCompiler(object): def __init__(self): """ Initialise a template compiler. """ self.commandList = [] self.tagStack = [] self.symbolLocationTable = {} self.macroMap = {} self.endTagSymbol = 1 self.commandHandler = {TAL_DEFINE: self.compileCmdDefine, TAL_CONDITION: self.compileCmdCondition, TAL_REPEAT: self.compileCmdRepeat, TAL_CONTENT: self.compileCmdContent, TAL_REPLACE: self.compileCmdReplace, TAL_ATTRIBUTES: self.compileCmdAttributes, TAL_OMITTAG: self.compileCmdOmitTag, # Metal commands METAL_USE_MACRO: self.compileMetalUseMacro, METAL_DEFINE_SLOT: self.compileMetalDefineSlot, METAL_FILL_SLOT: self.compileMetalFillSlot, METAL_DEFINE_MACRO: self.compileMetalDefineMacro} # Default namespaces self.setTALPrefix('tal') self.tal_namespace_prefix_stack = [] self.metal_namespace_prefix_stack = [] self.tal_namespace_prefix_stack.append('tal') self.setMETALPrefix('metal') self.metal_namespace_prefix_stack.append('metal') self.log = logging.getLogger("simpleTAL.TemplateCompiler")
[docs] def setTALPrefix(self, prefix): self.tal_namespace_prefix = prefix self.tal_namespace_omittag = '%s:omit-tag' % self.tal_namespace_prefix self.tal_attribute_map = {} self.tal_attribute_map['%s:attributes' % prefix] = TAL_ATTRIBUTES self.tal_attribute_map['%s:content' % prefix] = TAL_CONTENT self.tal_attribute_map['%s:define' % prefix] = TAL_DEFINE self.tal_attribute_map['%s:replace' % prefix] = TAL_REPLACE self.tal_attribute_map['%s:omit-tag' % prefix] = TAL_OMITTAG self.tal_attribute_map['%s:condition' % prefix] = TAL_CONDITION self.tal_attribute_map['%s:repeat' % prefix] = TAL_REPEAT
[docs] def setMETALPrefix(self, prefix): self.metal_namespace_prefix = prefix self.metal_attribute_map = {} self.metal_attribute_map['%s:define-macro' % prefix] = METAL_DEFINE_MACRO self.metal_attribute_map['%s:use-macro' % prefix] = METAL_USE_MACRO self.metal_attribute_map['%s:define-slot' % prefix] = METAL_DEFINE_SLOT self.metal_attribute_map['%s:fill-slot' % prefix] = METAL_FILL_SLOT
[docs] def popTALNamespace(self): newPrefix = self.tal_namespace_prefix_stack.pop() self.setTALPrefix(newPrefix)
[docs] def popMETALNamespace(self): newPrefix = self.metal_namespace_prefix_stack.pop() self.setMETALPrefix(newPrefix)
[docs] def tagAsText(self, tag_atts, singletonFlag=0): """ This returns a tag as text. """ (tag, atts) = tag_atts result = ["<"] result.append(tag) for attName, attValue in atts: result.append(' ') result.append(attName) result.append('=') result.append(xml.sax.saxutils.quoteattr(attValue)) if (singletonFlag): result.append(" />") else: result.append(">") return "".join(result)
[docs] def getTemplate(self): template = Template(self.commandList, self.macroMap, self.symbolLocationTable) return template
[docs] def addCommand(self, command): if (command[0] == TAL_OUTPUT and self.commandList and self.commandList[-1][0] == TAL_OUTPUT): # We can combine output commands self.commandList[-1] = (TAL_OUTPUT, self.commandList[-1][1] + command[1]) else: self.commandList.append(command)
[docs] def addTag(self, tag, tagProperties={}): """ Used to add a tag to the stack. Various properties can be passed in the dictionary as being information required by the tag. Currently supported properties are: 'command' - The (command,args) tuple associated with this command 'originalAtts' - The original attributes that include any metal/tal attributes 'endTagSymbol' - The symbol associated with the end tag for this element 'popFunctionList' - A list of functions to execute when this tag is popped 'singletonTag' - A boolean to indicate that this is a singleton flag """ # Add the tag to the tagStack (list of tuples (tag, properties, # useMacroLocation)) self.log.debug("Adding tag %s to stack" % tag[0]) command = tagProperties.get('command', None) originalAtts = tagProperties.get('originalAtts', None) singletonTag = tagProperties.get('singletonTag', 0) if (command is not None): if (command[0] == METAL_USE_MACRO): self.tagStack.append( (tag, tagProperties, len(self.commandList) + 1)) else: self.tagStack.append((tag, tagProperties, None)) else: self.tagStack.append((tag, tagProperties, None)) if (command is not None): # All tags that have a TAL attribute on them start with a 'start # scope' self.addCommand((TAL_START_SCOPE, (originalAtts, tag[1]))) # Now we add the TAL command self.addCommand(command) else: # It's just a straight output, so create an output command and # append it self.addCommand((TAL_OUTPUT, self.tagAsText(tag, singletonTag)))
[docs] def popTag(self, tag, omitTagFlag=0): """ omitTagFlag is used to control whether the end tag should be included in the output or not. In HTML 4.01 there are several tags which should never have end tags, this flag allows the template compiler to specify that these should not be output. """ while self.tagStack: oldTag, tagProperties, useMacroLocation = self.tagStack.pop() endTagSymbol = tagProperties.get('endTagSymbol', None) popCommandList = tagProperties.get('popFunctionList', []) singletonTag = tagProperties.get('singletonTag', 0) for func in popCommandList: func() self.log.debug("Popped tag %s off stack" % oldTag[0]) if (oldTag[0] == tag[0]): # We've found the right tag, now check to see if we have any # TAL commands on it if (endTagSymbol is not None): # We have a command (it's a TAL tag) # Note where the end tag symbol should point (i.e. the next # command) self.symbolLocationTable[ endTagSymbol] = len(self.commandList) # We need a "close scope and tag" command self.addCommand( (TAL_ENDTAG_ENDSCOPE, (tag[0], omitTagFlag, singletonTag))) return elif (omitTagFlag == 0 and singletonTag == 0): # We are popping off an un-interesting tag, just add the # close as text self.addCommand((TAL_OUTPUT, '</' + tag[0] + '>')) return else: # We are suppressing the output of this tag, so just return return else: # We have a different tag, which means something like <br> which never closes is in # between us and the real tag. # If the tag that we did pop off has a command though it means # un-balanced TAL tags! if (endTagSymbol is not None): # ERROR msg = ("TAL/METAL Elements must be balanced - found close tag %s expecting %s" % (tag[0], oldTag[0])) self.log.error(msg) raise TemplateParseException(self.tagAsText(oldTag), msg) self.log.error( "Close tag %s found with no corresponding open tag." % tag[0]) raise TemplateParseException( "</%s>" % tag[0], "Close tag encountered with no corresponding open tag.")
[docs] def parseStartTag(self, tag, attributes, singletonElement=0): # Note down the tag we are handling, it will be used for error handling during # compilation self.currentStartTag = (tag, attributes) # Look for tal/metal attributes foundTALAtts = [] foundMETALAtts = [] foundCommandsArgs = {} cleanAttributes = [] originalAttributes = {} tagProperties = {} popTagFuncList = [] TALElementNameSpace = 0 prefixToAdd = "" tagProperties['singletonTag'] = singletonElement # Determine whether this element is in either the METAL or TAL # namespace if (tag.find(':') > 0): # We have a namespace involved, so let's look to see if its one of # ours namespace = tag[0:tag.find(':')] if (namespace == self.metal_namespace_prefix): TALElementNameSpace = 1 prefixToAdd = self.metal_namespace_prefix + ":" elif (namespace == self.tal_namespace_prefix): TALElementNameSpace = 1 prefixToAdd = self.tal_namespace_prefix + ":" if (TALElementNameSpace): # We should treat this an implicit omit-tag foundTALAtts.append(TAL_OMITTAG) # Will go to default, i.e. yes foundCommandsArgs[TAL_OMITTAG] = "" for att, value in attributes: originalAttributes[att] = value if (TALElementNameSpace and not att.find(':') > 0): # This means that the attribute name does not have a namespace, # so use the prefix for this tag. commandAttName = prefixToAdd + att else: commandAttName = att self.log.debug("Command name is now %s" % commandAttName) if (att[0:5] == "xmlns"): # We have a namespace declaration. prefix = att[6:] if (value == METAL_NAME_URI): # It's a METAL namespace declaration if prefix: self.metal_namespace_prefix_stack.append( self.metal_namespace_prefix) self.setMETALPrefix(prefix) # We want this function called when the scope ends popTagFuncList.append(self.popMETALNamespace) else: # We don't allow METAL/TAL to be declared as a default msg = "Can not use METAL name space by default, a prefix must be provided." raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) elif (value == TAL_NAME_URI): # TAL this time if prefix: self.tal_namespace_prefix_stack.append( self.tal_namespace_prefix) self.setTALPrefix(prefix) # We want this function called when the scope ends popTagFuncList.append(self.popTALNamespace) else: # We don't allow METAL/TAL to be declared as a default msg = "Can not use TAL name space by default, a prefix must be provided." raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) else: # It's nothing special, just an ordinary namespace # declaration cleanAttributes.append((att, value)) elif (commandAttName in self.tal_attribute_map): # It's a TAL attribute cmnd = self.tal_attribute_map[commandAttName] if (cmnd == TAL_OMITTAG and TALElementNameSpace): self.log.warning( "Supressing omit-tag command present on TAL or METAL element") else: foundCommandsArgs[cmnd] = value foundTALAtts.append(cmnd) elif (commandAttName in self.metal_attribute_map): # It's a METAL attribute cmnd = self.metal_attribute_map[commandAttName] foundCommandsArgs[cmnd] = value foundMETALAtts.append(cmnd) else: cleanAttributes.append((att, value)) tagProperties['popFunctionList'] = popTagFuncList # This might be just content if not (foundTALAtts or foundMETALAtts): # Just content, add it to the various stacks self.addTag((tag, cleanAttributes), tagProperties) return # Create a symbol for the end of the tag - we don't know what the # offset is yet self.endTagSymbol += 1 tagProperties['endTagSymbol'] = self.endTagSymbol # Sort the METAL commands foundMETALAtts.sort() # Sort the tags by priority foundTALAtts.sort() # We handle the METAL before the TAL allCommands = foundMETALAtts + foundTALAtts firstTag = 1 for talAtt in allCommands: # Parse and create a command for each cmnd = self.commandHandler[talAtt](foundCommandsArgs[talAtt]) if (cmnd is not None): if (firstTag): # The first one needs to add the tag firstTag = 0 tagProperties['originalAtts'] = originalAttributes tagProperties['command'] = cmnd self.addTag((tag, cleanAttributes), tagProperties) else: # All others just append self.addCommand(cmnd) if (firstTag): tagProperties['originalAtts'] = originalAttributes tagProperties['command'] = (TAL_STARTTAG, (tag, singletonElement)) self.addTag((tag, cleanAttributes), tagProperties) else: # Add the start tag command in as a child of the last TAL command self.addCommand((TAL_STARTTAG, (tag, singletonElement)))
[docs] def parseEndTag(self, tag): """ Just pop the tag and related commands off the stack. """ self.popTag((tag, None))
[docs] def parseData(self, data): # Just add it as an output self.addCommand((TAL_OUTPUT, data))
[docs] def compileCmdDefine(self, argument): # Compile a define command, resulting argument is: # [(isLocalFlag (Y/n), variableName, variablePath),...] # Break up the list of defines first commandArgs = [] # We only want to match semi-colons that are not escaped argumentSplitter = re.compile('(?<!;);(?!;)') for defineStmt in argumentSplitter.split(argument): # remove any leading space and un-escape any semi-colons defineStmt = defineStmt.lstrip().replace(';;', ';') # Break each defineStmt into pieces "[local|global] varName # expression" stmtBits = defineStmt.split(' ') isLocal = 1 if (len(stmtBits) < 2): # Error, badly formed define command msg = ("Badly formed define command '%s'. Define commands must be of the form: '[local|global] varName expression[;[local|global] varName expression]'" % argument) self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Assume to start with that >2 elements means a local|global flag if (len(stmtBits) > 2): if (stmtBits[0] == 'global'): isLocal = 0 varName = stmtBits[1] expression = ' '.join(stmtBits[2:]) elif (stmtBits[0] == 'local'): varName = stmtBits[1] expression = ' '.join(stmtBits[2:]) else: # Must be a space in the expression that caused the >3 # thing varName = stmtBits[0] expression = ' '.join(stmtBits[1:]) else: # Only two bits varName = stmtBits[0] expression = ' '.join(stmtBits[1:]) commandArgs.append((isLocal, varName, expression)) return (TAL_DEFINE, commandArgs)
[docs] def compileCmdCondition(self, argument): # Compile a condition command, resulting argument is: # path, endTagSymbol # Sanity check if not argument: # No argument passed msg = "No argument passed! condition commands must be of the form: 'path'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) return (TAL_CONDITION, (argument, self.endTagSymbol))
[docs] def compileCmdRepeat(self, argument): # Compile a repeat command, resulting argument is: # (varname, expression, endTagSymbol) attProps = argument.split(' ') if (len(attProps) < 2): # Error, badly formed repeat command msg = ("Badly formed repeat command '%s'. Repeat commands must be of the form: 'localVariable path'" % argument) self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) varName = attProps[0] expression = " ".join(attProps[1:]) return (TAL_REPEAT, (varName, expression, self.endTagSymbol))
[docs] def compileCmdContent(self, argument, replaceFlag=0): # Compile a content command, resulting argument is # (replaceFlag, structureFlag, expression, endTagSymbol) # Sanity check if not argument: # No argument passed msg = "No argument passed! content/replace commands must be of the form: 'path'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) structureFlag = 0 attProps = argument.split(' ') if (len(attProps) > 1): if (attProps[0] == "structure"): structureFlag = 1 express = " ".join(attProps[1:]) elif (attProps[0] == "text"): structureFlag = 0 express = " ".join(attProps[1:]) else: # It's not a type selection after all - assume it's part of the # path express = argument else: express = argument return (TAL_CONTENT, (replaceFlag, structureFlag, express, self.endTagSymbol))
[docs] def compileCmdReplace(self, argument): return self.compileCmdContent(argument, replaceFlag=1)
[docs] def compileCmdAttributes(self, argument): # Compile tal:attributes into attribute command # Argument: [(attributeName, expression)] # Break up the list of attribute settings first commandArgs = [] # We only want to match semi-colons that are not escaped argumentSplitter = re.compile('(?<!;);(?!;)') for attributeStmt in argumentSplitter.split(argument): # remove any leading space and un-escape any semi-colons attributeStmt = attributeStmt.lstrip().replace(';;', ';') # Break each attributeStmt into name and expression stmtBits = attributeStmt.split(' ') if (len(stmtBits) < 2): # Error, badly formed attributes command msg = ("Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument) self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) attName = stmtBits[0] attExpr = " ".join(stmtBits[1:]) commandArgs.append((attName, attExpr)) return (TAL_ATTRIBUTES, commandArgs)
[docs] def compileCmdOmitTag(self, argument): # Compile a condition command, resulting argument is: # path # If no argument is given then set the path to default if not argument: expression = "default" else: expression = argument return (TAL_OMITTAG, expression)
# METAL compilation commands go here
[docs] def compileMetalUseMacro(self, argument): # Sanity check if not argument: # No argument passed msg = "No argument passed! use-macro commands must be of the form: 'use-macro: path'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) cmnd = (METAL_USE_MACRO, (argument, {}, self.endTagSymbol)) self.log.debug("Returning METAL_USE_MACRO: %s" % unicode(cmnd)) return cmnd
[docs] def compileMetalDefineMacro(self, argument): if not argument: # No argument passed msg = "No argument passed! define-macro commands must be of the form: 'define-macro: name'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Check that the name of the macro is valid if (METAL_NAME_REGEX.match(argument).end() != len(argument)): msg = "Macro name %s is invalid." % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) if (argument in self.macroMap): msg = "Macro name %s is already defined!" % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # The macro starts at the next command. macro = SubTemplate(len(self.commandList), self.endTagSymbol) self.macroMap[argument] = macro return None
[docs] def compileMetalFillSlot(self, argument): if not argument: # No argument passed msg = "No argument passed! fill-slot commands must be of the form: 'fill-slot: name'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Check that the name of the macro is valid if (METAL_NAME_REGEX.match(argument).end() != len(argument)): msg = "Slot name %s is invalid." % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Determine what use-macro statement this belongs to by working through # the list backwards ourMacroLocation = None location = len(self.tagStack) - 1 while (ourMacroLocation is None): macroLocation = self.tagStack[location][2] if (macroLocation is not None): ourMacroLocation = macroLocation else: location -= 1 if (location < 0): msg = "metal:fill-slot must be used inside a metal:use-macro call" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Get the use-macro command we are going to adjust cmnd, args = self.commandList[ourMacroLocation] self.log.debug("Use macro argument: %s" % unicode(args)) macroName, slotMap, endSymbol = args # Check that the name of the slot is valid if (METAL_NAME_REGEX.match(argument).end() != len(argument)): msg = "Slot name %s is invalid." % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) if (argument in slotMap): msg = "Slot %s has already been filled!" % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # The slot starts at the next command. slot = SubTemplate(len(self.commandList), self.endTagSymbol) slotMap[argument] = slot # Update the command self.commandList[ourMacroLocation] = ( cmnd, (macroName, slotMap, endSymbol)) return None
[docs] def compileMetalDefineSlot(self, argument): if not argument: # No argument passed msg = "No argument passed! define-slot commands must be of the form: 'name'" self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) # Check that the name of the slot is valid if (METAL_NAME_REGEX.match(argument).end() != len(argument)): msg = "Slot name %s is invalid." % argument self.log.error(msg) raise TemplateParseException( self.tagAsText(self.currentStartTag), msg) return (METAL_DEFINE_SLOT, (argument, self.endTagSymbol))
[docs]class TemplateParseException (Exception): def __init__(self, location, errorDescription): self.location = location self.errorDescription = errorDescription def __str__(self): return "[" + self.location + "] " + self.errorDescription
[docs]class HTMLTemplateCompiler (TemplateCompiler, HTMLParser.HTMLParser): def __init__(self): TemplateCompiler.__init__(self) HTMLParser.HTMLParser.__init__(self) self.log = logging.getLogger("simpleTAL.HTMLTemplateCompiler")
[docs] def parseTemplate(self, file, encoding="UTF-8-SIG", minimizeBooleanAtts=0): if isinstance(file, io.TextIOBase): s = file.read() else: s = file.read().decode(encoding, 'replace') self.encoding = encoding self.minimizeBooleanAtts = minimizeBooleanAtts self.feed(s) self.close()
[docs] def tagAsText(self, tag_atts, singletonFlag=0): """ This returns a tag as text. """ (tag, atts) = tag_atts result = ["<"] result.append(tag) upperTag = tag.upper() for attName, attValue in atts: if (self.minimizeBooleanAtts and ((upperTag, attName.upper())) in HTML_BOOLEAN_ATTS): # We should output a minimised boolean value result.append(' ') result.append(attName) else: result.append(' ') result.append(attName) result.append('=') result.append(xml.sax.saxutils.quoteattr(attValue)) if (singletonFlag): result.append(" />") else: result.append(">") return "".join(result)
[docs] def handle_startendtag(self, tag, attributes): self.log.debug("Recieved StartEnd Tag: " + tag + " Attributes: " + unicode(attributes)) atts = [] for att, attValue in attributes: # We need to spot empty tal:omit-tags if (attValue is None): if (att == self.tal_namespace_omittag): atts.append((att, "")) else: atts.append((att, att)) else: atts.append((att, attValue)) self.parseStartTag(tag, atts) self.popTag((tag, None), omitTagFlag=1)
[docs] def handle_starttag(self, tag, attributes): self.log.debug("Recieved Start Tag: " + tag + " Attributes: " + unicode(attributes)) atts = [] for att, attValue in attributes: # We need to spot empty tal:omit-tags if (attValue is None): if (att == self.tal_namespace_omittag): atts.append((att, "")) else: atts.append((att, att)) else: atts.append((att, attValue)) if (tag.upper() in HTML_FORBIDDEN_ENDTAG): # This should have no end tag, so we just do the start and suppress # the end self.parseStartTag(tag, atts) self.log.debug( "End tag forbidden, generating close tag with no output.") self.popTag((tag, None), omitTagFlag=1) else: self.parseStartTag(tag, atts)
[docs] def handle_endtag(self, tag): self.log.debug("Recieved End Tag: " + tag) if (tag.upper() in HTML_FORBIDDEN_ENDTAG): self.log.warning( "HTML forbids end tags for the %s element" % tag) else: # Normal end tag self.popTag((tag, None))
[docs] def handle_data(self, data): self.parseData(xml.sax.saxutils.escape(data))
# These two methods are required so that we expand all character and # entity references prior to parsing the template.
[docs] def handle_charref(self, ref): self.log.debug("Got Ref: %s", ref) self.parseData(unichr(int(ref)))
[docs] def handle_entityref(self, ref): self.log.debug("Got Ref: %s", ref) # Use handle_data so that <&> are re-encoded as required. self.handle_data(name2unicode.get(ref, '\ufffd'))
# Handle document type declarations
[docs] def handle_decl(self, data): self.parseData('<!%s>' % data)
# Pass comments through un-affected.
[docs] def handle_comment(self, data): self.parseData('<!--%s-->' % data)
[docs] def handle_pi(self, data): self.log.debug("Recieved processing instruction.") self.parseData('<?%s>' % data)
[docs] def report_unbalanced(self, tag): self.log.warning("End tag %s present with no corresponding open tag.")
[docs] def getTemplate(self): template = HTMLTemplate(self.commandList, self.macroMap, self.symbolLocationTable, minimizeBooleanAtts=self.minimizeBooleanAtts) return template
[docs]class XMLTemplateCompiler (TemplateCompiler, xml.sax.handler.ContentHandler, xml.sax.handler.DTDHandler, LexicalHandler): def __init__(self): TemplateCompiler.__init__(self) xml.sax.handler.ContentHandler.__init__(self) self.doctype = None self.log = logging.getLogger("simpleTAL.XMLTemplateCompiler") self.singletonElement = 0
[docs] def parseTemplate(self, file): self.ourParser = xml.sax.make_parser() self.log.debug("Setting features of parser") try: self.ourParser.setFeature(xml.sax.handler.feature_external_ges, 0) except: pass if use_lexical_handler: self.ourParser.setProperty( xml.sax.handler.property_lexical_handler, self) self.ourParser.setContentHandler(self) self.ourParser.setDTDHandler(self) self.ourParser.parse(file)
[docs] def parseDOM(self, dom): if (not use_dom2sax): self.log.critical("PyXML is not available, DOM can not be parsed.") self.ourParser = xml.dom.ext.Dom2Sax.Dom2SaxParser() self.log.debug("Setting features of parser") if use_lexical_handler: self.ourParser.setProperty( xml.sax.handler.property_lexical_handler, self) self.ourParser.setContentHandler(self) self.ourParser.setDTDHandler(self) self.ourParser.parse(dom)
[docs] def startDTD(self, name, public_id, system_id): self.log.debug("Recieved DOCTYPE: " + name + " public_id: " + public_id + " system_id: " + system_id) if public_id: self.doctype = '<!DOCTYPE %s PUBLIC "%s" "%s">' % ( name, public_id, system_id,) else: self.doctype = '<!DOCTYPE %s SYSTEM "%s">' % (name, system_id,)
[docs] def startElement(self, tag, attributes): self.log.debug("Recieved Real Start Tag: " + tag + " Attributes: " + unicode(attributes)) try: xmlText = self.ourParser.getProperty( xml.sax.handler.property_xml_string) if ((isinstance(xmlText, unicode) and SINGLETON_XML_REGEX.match(xmlText.encode("utf-8"))) or SINGLETON_XML_REGEX.match(xmlText)): # This is a singleton! self.singletonElement = 1 except xml.sax.SAXException as e: # Parser doesn't support this property pass # Convert attributes into a list of tuples atts = [] for att in attributes.getNames(): self.log.debug("Attribute name %s has value %s" % (att, attributes[att])) atts.append((att, attributes[att])) self.parseStartTag(tag, atts, singletonElement=self.singletonElement)
[docs] def endElement(self, tag): self.log.debug("Recieved Real End Tag: " + tag) self.parseEndTag(tag) self.singletonElement = 0
[docs] def skippedEntity(self, name): self.log.info("Recieved skipped entity: %s" % name) self.characters(name2unicode.get(name, '\ufffd'))
[docs] def characters(self, data): #self.log.debug ("Recieved Real Data: " + data) # Escape any data we recieve - we don't want any: <&> in there. self.parseData(xml.sax.saxutils.escape(data))
[docs] def processingInstruction(self, target, data): self.log.debug("Recieved processing instruction.") self.parseData('<?%s %s?>' % (target, data))
[docs] def comment(self, data): # This is only called if your XML parser supports the LexicalHandler # interface. self.parseData('<!--%s-->' % data)
[docs] def getTemplate(self): template = XMLTemplate( self.commandList, self.macroMap, self.symbolLocationTable, self.doctype) return template
[docs]def compileHTMLTemplate(template, inputEncoding="UTF-8-SIG", minimizeBooleanAtts=0): """ Reads the templateFile and produces a compiled template. To use the resulting template object call: template.expand (context, outputFile) """ if isinstance(template, unicode): # It's a string! templateFile = io.StringIO(template) elif isinstance(template, bytes): templateFile = io.BytesIO(template) else: templateFile = template compiler = HTMLTemplateCompiler() compiler.parseTemplate(templateFile, inputEncoding, minimizeBooleanAtts) return compiler.getTemplate()
[docs]def compileXMLTemplate(template): """ Reads the templateFile and produces a compiled template. To use the resulting template object call: template.expand (context, outputFile) """ if isinstance(template, unicode): # It's a string! templateFile = io.StringIO(template) elif (isinstance(template, bytes)): # It's a string! templateFile = io.BytesIO(template) else: templateFile = template compiler = XMLTemplateCompiler() compiler.parseTemplate(templateFile) return compiler.getTemplate()
[docs]def compileDOMTemplate(template): """ Traverses the DOM and produces a compiled template. To use the resulting template object call: template.expand (context, outputFile) """ compiler = XMLTemplateCompiler() compiler.parseDOM(template) return compiler.getTemplate()