[px] First draft for a new template engine Python-XML, sharing the pod roots.
This commit is contained in:
parent
5cc7884c03
commit
be3cc6ae59
|
@ -45,7 +45,7 @@ class BufferAction:
|
||||||
# of the action.
|
# of the action.
|
||||||
self.minus = minus # If True, the main buffer element(s) must not be
|
self.minus = minus # If True, the main buffer element(s) must not be
|
||||||
# dumped.
|
# dumped.
|
||||||
self.result = self.buffer.getFileBuffer()
|
self.result = self.buffer.getRootBuffer()
|
||||||
self.source = source # if 'buffer', we must dump the (evaluated) buffer
|
self.source = source # if 'buffer', we must dump the (evaluated) buffer
|
||||||
# content. If 'from', we must dump what comes from the 'from' part of
|
# content. If 'from', we must dump what comes from the 'from' part of
|
||||||
# the action (='fromExpr')
|
# the action (='fromExpr')
|
||||||
|
|
|
@ -207,6 +207,7 @@ class FileBuffer(Buffer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pushSubBuffer(self, subBuffer): pass
|
def pushSubBuffer(self, subBuffer): pass
|
||||||
|
def getRootBuffer(self): return self
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class MemoryBuffer(Buffer):
|
class MemoryBuffer(Buffer):
|
||||||
|
@ -227,12 +228,11 @@ class MemoryBuffer(Buffer):
|
||||||
# the same place within this buffer.
|
# the same place within this buffer.
|
||||||
return sb
|
return sb
|
||||||
|
|
||||||
def getFileBuffer(self):
|
def getRootBuffer(self):
|
||||||
if isinstance(self.parent, FileBuffer):
|
'''Returns the root buffer. For POD it is always a FileBuffer. For PX,
|
||||||
res = self.parent
|
it is a MemoryBuffer.'''
|
||||||
else:
|
if self.parent: return self.parent.getRootBuffer()
|
||||||
res = self.parent.getFileBuffer()
|
return self
|
||||||
return res
|
|
||||||
|
|
||||||
def getLength(self): return len(self.content)
|
def getLength(self): return len(self.content)
|
||||||
|
|
||||||
|
@ -253,25 +253,38 @@ class MemoryBuffer(Buffer):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def isMainElement(self, elem):
|
def isMainElement(self, elem):
|
||||||
res = False
|
'''Is p_elem the main elemen within this buffer?'''
|
||||||
mainElem = self.getMainElement()
|
mainElem = self.getMainElement()
|
||||||
if mainElem and (elem == mainElem.OD.elem):
|
if not mainElem: return
|
||||||
res = True
|
if hasattr(mainElem, 'OD'): mainElem = mainElem.OD.elem
|
||||||
# Check if this element is not found again within the buffer
|
if elem != mainElem: return
|
||||||
for index, podElem in self.elements.iteritems():
|
# elem is the same as the main elem. But is it really the main elem, or
|
||||||
if podElem.OD:
|
# the same elem, found deeper in the buffer?
|
||||||
if (podElem.OD.elem == mainElem.OD.elem) and (index != 0):
|
for index, iElem in self.elements.iteritems():
|
||||||
res = False
|
foundElem = None
|
||||||
break
|
if hasattr(iElem, 'OD'):
|
||||||
return res
|
if iElem.OD:
|
||||||
|
foundElem = iElem.OD.elem
|
||||||
|
else:
|
||||||
|
foundElem = iElem
|
||||||
|
if (foundElem == mainElem) and (index != 0):
|
||||||
|
return
|
||||||
|
return True
|
||||||
|
|
||||||
def unreferenceElement(self, elem):
|
def unreferenceElement(self, elem):
|
||||||
# Find last occurrence of this element
|
# Find last occurrence of this element
|
||||||
elemIndex = -1
|
elemIndex = -1
|
||||||
for index, podElem in self.elements.iteritems():
|
for index, iElem in self.elements.iteritems():
|
||||||
if podElem.OD:
|
foundElem = None
|
||||||
if (podElem.OD.elem == elem) and (index > elemIndex):
|
if hasattr(iElem, 'OD'):
|
||||||
elemIndex = index
|
# A POD element
|
||||||
|
if iElem.OD:
|
||||||
|
foundElem = iElem.OD.elem
|
||||||
|
else:
|
||||||
|
# A PX elem
|
||||||
|
foundElem = iElem
|
||||||
|
if (foundElem == elem) and (index > elemIndex):
|
||||||
|
elemIndex = index
|
||||||
del self.elements[elemIndex]
|
del self.elements[elemIndex]
|
||||||
|
|
||||||
def pushSubBuffer(self, subBuffer):
|
def pushSubBuffer(self, subBuffer):
|
||||||
|
@ -310,22 +323,27 @@ class MemoryBuffer(Buffer):
|
||||||
# Change buffer position wrt parent
|
# Change buffer position wrt parent
|
||||||
self.parent.pushSubBuffer(self)
|
self.parent.pushSubBuffer(self)
|
||||||
|
|
||||||
def addElement(self, elem):
|
def addElement(self, elem, elemType='pod'):
|
||||||
newElem = PodElement.create(elem)
|
if elemType == 'pod':
|
||||||
self.elements[self.getLength()] = newElem
|
elem = PodElement.create(elem)
|
||||||
if isinstance(newElem, Cell) or isinstance(newElem, Table):
|
self.elements[self.getLength()] = elem
|
||||||
newElem.tableInfo = self.env.getTable()
|
if isinstance(elem, Cell) or isinstance(elem, Table):
|
||||||
if isinstance(newElem, Cell):
|
elem.tableInfo = self.env.getTable()
|
||||||
|
if isinstance(elem, Cell):
|
||||||
# Remember where this cell is in the table
|
# Remember where this cell is in the table
|
||||||
newElem.colIndex = newElem.tableInfo.curColIndex
|
elem.colIndex = elem.tableInfo.curColIndex
|
||||||
|
if elem == 'x':
|
||||||
|
# See comment on similar statement in the following below.
|
||||||
|
self.content += u' '
|
||||||
|
|
||||||
def addExpression(self, expression, tiedHook=None):
|
def addExpression(self, expression, tiedHook=None):
|
||||||
# Create the POD expression
|
# Create the POD expression
|
||||||
expr = Expression(expression)
|
expr = Expression(expression)
|
||||||
if tiedHook: tiedHook.tiedExpression = expr
|
if tiedHook: tiedHook.tiedExpression = expr
|
||||||
self.elements[self.getLength()] = expr
|
self.elements[self.getLength()] = expr
|
||||||
self.content += u' '# To be sure that an expr and an elem can't be found
|
# To be sure that an expr and an elem can't be found at the same index
|
||||||
# at the same index in the buffer.
|
# in the buffer.
|
||||||
|
self.content += u' '
|
||||||
|
|
||||||
def addAttributes(self):
|
def addAttributes(self):
|
||||||
# Create the Attributes instance
|
# Create the Attributes instance
|
||||||
|
@ -423,6 +441,20 @@ class MemoryBuffer(Buffer):
|
||||||
PodError.dump(self, ppe, removeFirstLine=True)
|
PodError.dump(self, ppe, removeFirstLine=True)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def createPxAction(self, elem, actionType, statement):
|
||||||
|
res = 0
|
||||||
|
if actionType == 'for':
|
||||||
|
forRes = MemoryBuffer.forRex.match(statement.strip())
|
||||||
|
if not forRes:
|
||||||
|
raise ParsingError(BAD_FOR_EXPRESSION % statement)
|
||||||
|
iter, subExpr = forRes.groups()
|
||||||
|
self.action = ForAction('for', self, subExpr, elem, False, iter,
|
||||||
|
'buffer', None)
|
||||||
|
elif actionType == 'if':
|
||||||
|
self.action = IfAction('if', self, statement, elem, False,
|
||||||
|
'buffer', None)
|
||||||
|
return res
|
||||||
|
|
||||||
def cut(self, index, keepFirstPart):
|
def cut(self, index, keepFirstPart):
|
||||||
'''Cuts this buffer into 2 parts. Depending on p_keepFirstPart, the 1st
|
'''Cuts this buffer into 2 parts. Depending on p_keepFirstPart, the 1st
|
||||||
(from 0 to index-1) or the second (from index to the end) part of the
|
(from 0 to index-1) or the second (from index to the end) part of the
|
||||||
|
@ -554,7 +586,7 @@ class MemoryBuffer(Buffer):
|
||||||
reTagContent = re.compile('<(?P<p>[\w-]+):(?P<f>[\w-]+)(.*?)>.*</(?P=p):' \
|
reTagContent = re.compile('<(?P<p>[\w-]+):(?P<f>[\w-]+)(.*?)>.*</(?P=p):' \
|
||||||
'(?P=f)>', re.S)
|
'(?P=f)>', re.S)
|
||||||
def evaluate(self, subElements=True, removeMainElems=False):
|
def evaluate(self, subElements=True, removeMainElems=False):
|
||||||
result = self.getFileBuffer()
|
result = self.getRootBuffer()
|
||||||
if not subElements:
|
if not subElements:
|
||||||
# Dump the root tag in this buffer, but not its content.
|
# Dump the root tag in this buffer, but not its content.
|
||||||
res = self.reTagContent.match(self.content.strip())
|
res = self.reTagContent.match(self.content.strip())
|
||||||
|
@ -585,4 +617,8 @@ class MemoryBuffer(Buffer):
|
||||||
stopIndex = self.getStopIndex(removeMainElems)
|
stopIndex = self.getStopIndex(removeMainElems)
|
||||||
if currentIndex < (stopIndex-1):
|
if currentIndex < (stopIndex-1):
|
||||||
result.write(self.content[currentIndex:stopIndex])
|
result.write(self.content[currentIndex:stopIndex])
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
'''Cleans the buffer content.'''
|
||||||
|
self.content = u''
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -30,11 +30,11 @@ class PodElement:
|
||||||
POD_ELEMS = ('text', 'title', 'section', 'table', 'row', 'cell')
|
POD_ELEMS = ('text', 'title', 'section', 'table', 'row', 'cell')
|
||||||
MINUS_ELEMS = ('section', 'table') # Elements for which the '-' operator can
|
MINUS_ELEMS = ('section', 'table') # Elements for which the '-' operator can
|
||||||
# be applied
|
# be applied
|
||||||
|
@staticmethod
|
||||||
def create(elem):
|
def create(elem):
|
||||||
'''Used to create any POD elem that has a equivalent OD element. Not
|
'''Used to create any POD elem that has an equivalent OD element. Not
|
||||||
for creating expressions, for example.'''
|
for creating expressions, for example.'''
|
||||||
return eval(PodElement.OD_TO_POD[elem])()
|
return eval(PodElement.OD_TO_POD[elem])()
|
||||||
create = staticmethod(create)
|
|
||||||
|
|
||||||
class Text(PodElement):
|
class Text(PodElement):
|
||||||
OD = XmlElement('p', nsUri=ns.NS_TEXT)
|
OD = XmlElement('p', nsUri=ns.NS_TEXT)
|
||||||
|
|
|
@ -320,7 +320,7 @@ class PodParser(OdfParser):
|
||||||
if elem in e.impactableElements:
|
if elem in e.impactableElements:
|
||||||
if isinstance(e.currentBuffer, MemoryBuffer):
|
if isinstance(e.currentBuffer, MemoryBuffer):
|
||||||
isMainElement = e.currentBuffer.isMainElement(elem)
|
isMainElement = e.currentBuffer.isMainElement(elem)
|
||||||
# Unreference the element among the 'elements' attribute
|
# Unreference the element among buffer.elements
|
||||||
e.currentBuffer.unreferenceElement(elem)
|
e.currentBuffer.unreferenceElement(elem)
|
||||||
if isMainElement:
|
if isMainElement:
|
||||||
parent = e.currentBuffer.parent
|
parent = e.currentBuffer.parent
|
||||||
|
|
57
px/__init__.py
Normal file
57
px/__init__.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
'''PX stands for *P*ython *X*ML. It is a templating engine that reuses the pod
|
||||||
|
engine to produce XML (including XHTML) from templates written as a mix of
|
||||||
|
Python and XML.'''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from UserDict import UserDict
|
||||||
|
from px_parser import PxParser, PxEnvironment
|
||||||
|
from appy.pod.renderer import BAD_CONTEXT
|
||||||
|
|
||||||
|
# Exception class --------------------------------------------------------------
|
||||||
|
class PxError(Exception): pass
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class Px:
|
||||||
|
'''Represents a (chunk of) PX code.'''
|
||||||
|
def __init__(self, content, isFileName=False, partial=True):
|
||||||
|
'''p_content is the PX code, as a string, or a file name if p_isFileName
|
||||||
|
is True. If this code represents a complete XML file, p_partial is
|
||||||
|
False. Else, we must surround p_content with a root tag to be able
|
||||||
|
to parse it with a SAX parser.'''
|
||||||
|
# Get the PX content
|
||||||
|
if isFileName:
|
||||||
|
f = file(content)
|
||||||
|
self.content = f.read()
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
self.content = content
|
||||||
|
# It this content a complete XML file, or just some part of it?
|
||||||
|
if partial:
|
||||||
|
# Surround the partial chunk with a root tag: it must be valid XML.
|
||||||
|
self.content = '<x>%s</x>' % self.content
|
||||||
|
self.partial = partial
|
||||||
|
# Create a PX parser
|
||||||
|
self.parser = PxParser(PxEnvironment(), self)
|
||||||
|
# Parses self.content (a PX code in a string) with self.parser, to
|
||||||
|
# produce a tree of memory buffers.
|
||||||
|
self.parser.parse(self.content)
|
||||||
|
|
||||||
|
def __call__(self, context):
|
||||||
|
# Get the context in a standardized form.
|
||||||
|
evalContext = {}
|
||||||
|
if hasattr(context, '__dict__'):
|
||||||
|
evalContext.update(context.__dict__)
|
||||||
|
elif isinstance(context, dict) or isinstance(context, UserDict):
|
||||||
|
evalContext.update(context)
|
||||||
|
else:
|
||||||
|
raise PxError(BAD_CONTEXT)
|
||||||
|
# Store the context on the PX environment
|
||||||
|
self.parser.env.context = evalContext
|
||||||
|
# Render the PX result and return it
|
||||||
|
env = self.parser.env
|
||||||
|
env.ast.evaluate()
|
||||||
|
res = env.result.content
|
||||||
|
# Clean the res, for the next evaluation
|
||||||
|
env.result.clean()
|
||||||
|
return res
|
||||||
|
# ------------------------------------------------------------------------------
|
115
px/px_parser.py
Normal file
115
px/px_parser.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from appy.shared.xml_parser import XmlEnvironment, XmlParser
|
||||||
|
from appy.pod.buffers import MemoryBuffer
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class PxEnvironment(XmlEnvironment):
|
||||||
|
'''Environment for the PX parser.'''
|
||||||
|
# PX-specific attributes must not be dumped into the result.
|
||||||
|
undumpableAttrs = ('for', 'if')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# We try to mimic POD. POD has a root buffer that is a FileBuffer, which
|
||||||
|
# is the final result buffer, into which the result of evaluating all
|
||||||
|
# memory buffers, defined as sub-buffers of this file buffer, is
|
||||||
|
# generated. For PX, we will define a result buffer, but as a memory
|
||||||
|
# buffer instead of a file buffer.
|
||||||
|
self.result = MemoryBuffer(self, None)
|
||||||
|
# In this buffer, we will create a single memory sub-buffer that will
|
||||||
|
# hold the result of parsing the PX = a hierarchy of memory buffers =
|
||||||
|
# PX's AST (Abstract Syntax Tree).
|
||||||
|
self.ast = MemoryBuffer(self, self.result)
|
||||||
|
# A major difference between POD and PX: POD creates the AST and
|
||||||
|
# generates the result in the same step: one AST is generated, and then
|
||||||
|
# directly produces a single evaluation, in the root file buffer. PX
|
||||||
|
# works in 2 steps: the AST is initially created in self.ast. Then,
|
||||||
|
# several evaluations can be generated, in self.result, without
|
||||||
|
# re-generating the AST. After every evaluation, self.result will be
|
||||||
|
# cleaned, to be reusable for the next evaluation.
|
||||||
|
# Context will come afterwards
|
||||||
|
self.context = None
|
||||||
|
# Buffer where we must dump the content we are currently reading
|
||||||
|
self.currentBuffer = self.ast
|
||||||
|
# Tag content we are currently reading. We will put soomething in this
|
||||||
|
# attribute only if we encounter content that is Python code.
|
||||||
|
# Else, we will directly dump the parsed content into the current
|
||||||
|
# buffer.
|
||||||
|
self.currentContent = ''
|
||||||
|
# The currently walked element. We redefine it here. This attribute is
|
||||||
|
# normally managed by the parent XmlEnvironment, but we do not use the
|
||||||
|
# standard machinery from this environmment and from the default
|
||||||
|
# XmlParser for better performance. Indeed, the base parser and env
|
||||||
|
# process namespaces, and we do not need this for the PX parser.
|
||||||
|
self.currentElem = None
|
||||||
|
|
||||||
|
def addSubBuffer(self):
|
||||||
|
subBuffer = self.currentBuffer.addSubBuffer()
|
||||||
|
self.currentBuffer = subBuffer
|
||||||
|
|
||||||
|
def isActionElem(self, elem):
|
||||||
|
'''Returns True if the currently walked p_elem is the same elem as the
|
||||||
|
main buffer elem.'''
|
||||||
|
action = self.currentBuffer.action
|
||||||
|
return action and (action.elem == elem)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class PxParser(XmlParser):
|
||||||
|
'''PX parser that is specific for parsing PX data.'''
|
||||||
|
|
||||||
|
def __init__(self, env, caller=None):
|
||||||
|
XmlParser.__init__(self, env, caller)
|
||||||
|
|
||||||
|
def startElement(self, elem, attrs):
|
||||||
|
'''A start p_elem with p_attrs is encountered in the PX.'''
|
||||||
|
e = self.env
|
||||||
|
self.currentElem = elem
|
||||||
|
if attrs.has_key('for'):
|
||||||
|
# Dump the element in a new sub-buffer
|
||||||
|
e.addSubBuffer()
|
||||||
|
# Create the action for this buffer
|
||||||
|
e.currentBuffer.createPxAction(elem, 'for', attrs['for'])
|
||||||
|
elif attrs.has_key('if'):
|
||||||
|
e.addSubBuffer()
|
||||||
|
e.currentBuffer.createPxAction(elem, 'if', attrs['if'])
|
||||||
|
if e.isActionElem(elem):
|
||||||
|
# Add a temp element in the buffer (that will be unreferenced
|
||||||
|
# later). This way, when encountering the corresponding end element,
|
||||||
|
# we will be able to check whether the end element corresponds to
|
||||||
|
# the main element or to a sub-element.
|
||||||
|
e.currentBuffer.addElement(elem, elemType='px')
|
||||||
|
if elem != 'x':
|
||||||
|
e.currentBuffer.dumpStartElement(elem, attrs,
|
||||||
|
ignoreAttrs=e.undumpableAttrs)
|
||||||
|
|
||||||
|
def endElement(self, elem):
|
||||||
|
e = self.env
|
||||||
|
# Manage the potentially collected Python expression in
|
||||||
|
# e.currentContent.
|
||||||
|
if e.currentContent:
|
||||||
|
e.currentBuffer.addExpression(e.currentContent)
|
||||||
|
e.currentContent = ''
|
||||||
|
# Dump the end element into the current buffer
|
||||||
|
if elem != 'x': e.currentBuffer.dumpEndElement(elem)
|
||||||
|
# If this element is the main element of the current buffer, we must
|
||||||
|
# pop it and continue to work in the parent buffer.
|
||||||
|
if e.isActionElem(elem):
|
||||||
|
# Is it the buffer main element?
|
||||||
|
isMainElement = e.currentBuffer.isMainElement(elem)
|
||||||
|
# Unreference the element among buffer.elements
|
||||||
|
e.currentBuffer.unreferenceElement(elem)
|
||||||
|
if isMainElement:
|
||||||
|
# Continue to work in the parent buffer
|
||||||
|
e.currentBuffer = e.currentBuffer.parent
|
||||||
|
|
||||||
|
def characters(self, content):
|
||||||
|
e = self.env
|
||||||
|
if not e.currentContent and content.startswith(':'):
|
||||||
|
# This content is not static content to dump as-is into the result:
|
||||||
|
# it is a Python expression.
|
||||||
|
e.currentContent += content[1:]
|
||||||
|
elif e.currentContent:
|
||||||
|
# We continue to dump the Python expression.
|
||||||
|
e.currentContent += content
|
||||||
|
else:
|
||||||
|
e.currentBuffer.dumpContent(content)
|
||||||
|
# ------------------------------------------------------------------------------
|
Loading…
Reference in a new issue