diff --git a/pod/actions.py b/pod/actions.py
index def475c..7e80303 100644
--- a/pod/actions.py
+++ b/pod/actions.py
@@ -45,7 +45,7 @@ class BufferAction:
# of the action.
self.minus = minus # If True, the main buffer element(s) must not be
# dumped.
- self.result = self.buffer.getFileBuffer()
+ self.result = self.buffer.getRootBuffer()
self.source = source # if 'buffer', we must dump the (evaluated) buffer
# content. If 'from', we must dump what comes from the 'from' part of
# the action (='fromExpr')
diff --git a/pod/buffers.py b/pod/buffers.py
index bb91377..7ebf3d1 100644
--- a/pod/buffers.py
+++ b/pod/buffers.py
@@ -207,6 +207,7 @@ class FileBuffer(Buffer):
pass
def pushSubBuffer(self, subBuffer): pass
+ def getRootBuffer(self): return self
# ------------------------------------------------------------------------------
class MemoryBuffer(Buffer):
@@ -227,12 +228,11 @@ class MemoryBuffer(Buffer):
# the same place within this buffer.
return sb
- def getFileBuffer(self):
- if isinstance(self.parent, FileBuffer):
- res = self.parent
- else:
- res = self.parent.getFileBuffer()
- return res
+ def getRootBuffer(self):
+ '''Returns the root buffer. For POD it is always a FileBuffer. For PX,
+ it is a MemoryBuffer.'''
+ if self.parent: return self.parent.getRootBuffer()
+ return self
def getLength(self): return len(self.content)
@@ -253,25 +253,38 @@ class MemoryBuffer(Buffer):
return res
def isMainElement(self, elem):
- res = False
+ '''Is p_elem the main elemen within this buffer?'''
mainElem = self.getMainElement()
- if mainElem and (elem == mainElem.OD.elem):
- res = True
- # Check if this element is not found again within the buffer
- for index, podElem in self.elements.iteritems():
- if podElem.OD:
- if (podElem.OD.elem == mainElem.OD.elem) and (index != 0):
- res = False
- break
- return res
+ if not mainElem: return
+ if hasattr(mainElem, 'OD'): mainElem = mainElem.OD.elem
+ if elem != mainElem: return
+ # elem is the same as the main elem. But is it really the main elem, or
+ # the same elem, found deeper in the buffer?
+ for index, iElem in self.elements.iteritems():
+ foundElem = None
+ if hasattr(iElem, 'OD'):
+ if iElem.OD:
+ foundElem = iElem.OD.elem
+ else:
+ foundElem = iElem
+ if (foundElem == mainElem) and (index != 0):
+ return
+ return True
def unreferenceElement(self, elem):
# Find last occurrence of this element
elemIndex = -1
- for index, podElem in self.elements.iteritems():
- if podElem.OD:
- if (podElem.OD.elem == elem) and (index > elemIndex):
- elemIndex = index
+ for index, iElem in self.elements.iteritems():
+ foundElem = None
+ if hasattr(iElem, 'OD'):
+ # 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]
def pushSubBuffer(self, subBuffer):
@@ -310,22 +323,27 @@ class MemoryBuffer(Buffer):
# Change buffer position wrt parent
self.parent.pushSubBuffer(self)
- def addElement(self, elem):
- newElem = PodElement.create(elem)
- self.elements[self.getLength()] = newElem
- if isinstance(newElem, Cell) or isinstance(newElem, Table):
- newElem.tableInfo = self.env.getTable()
- if isinstance(newElem, Cell):
+ def addElement(self, elem, elemType='pod'):
+ if elemType == 'pod':
+ elem = PodElement.create(elem)
+ self.elements[self.getLength()] = elem
+ if isinstance(elem, Cell) or isinstance(elem, Table):
+ elem.tableInfo = self.env.getTable()
+ if isinstance(elem, Cell):
# 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):
# Create the POD expression
expr = Expression(expression)
if tiedHook: tiedHook.tiedExpression = expr
self.elements[self.getLength()] = expr
- self.content += u' '# To be sure that an expr and an elem can't be found
- # at the same index in the buffer.
+ # To be sure that an expr and an elem can't be found at the same index
+ # in the buffer.
+ self.content += u' '
def addAttributes(self):
# Create the Attributes instance
@@ -423,6 +441,20 @@ class MemoryBuffer(Buffer):
PodError.dump(self, ppe, removeFirstLine=True)
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):
'''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
@@ -554,7 +586,7 @@ class MemoryBuffer(Buffer):
reTagContent = re.compile('<(?P
[\w-]+):(?P[\w-]+)(.*?)>.*(?P=p):' \
'(?P=f)>', re.S)
def evaluate(self, subElements=True, removeMainElems=False):
- result = self.getFileBuffer()
+ result = self.getRootBuffer()
if not subElements:
# Dump the root tag in this buffer, but not its content.
res = self.reTagContent.match(self.content.strip())
@@ -585,4 +617,8 @@ class MemoryBuffer(Buffer):
stopIndex = self.getStopIndex(removeMainElems)
if currentIndex < (stopIndex-1):
result.write(self.content[currentIndex:stopIndex])
+
+ def clean(self):
+ '''Cleans the buffer content.'''
+ self.content = u''
# ------------------------------------------------------------------------------
diff --git a/pod/elements.py b/pod/elements.py
index d97eaca..8abd420 100644
--- a/pod/elements.py
+++ b/pod/elements.py
@@ -30,11 +30,11 @@ class PodElement:
POD_ELEMS = ('text', 'title', 'section', 'table', 'row', 'cell')
MINUS_ELEMS = ('section', 'table') # Elements for which the '-' operator can
# be applied
+ @staticmethod
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.'''
return eval(PodElement.OD_TO_POD[elem])()
- create = staticmethod(create)
class Text(PodElement):
OD = XmlElement('p', nsUri=ns.NS_TEXT)
diff --git a/pod/pod_parser.py b/pod/pod_parser.py
index 67ddf66..a13e333 100644
--- a/pod/pod_parser.py
+++ b/pod/pod_parser.py
@@ -320,7 +320,7 @@ class PodParser(OdfParser):
if elem in e.impactableElements:
if isinstance(e.currentBuffer, MemoryBuffer):
isMainElement = e.currentBuffer.isMainElement(elem)
- # Unreference the element among the 'elements' attribute
+ # Unreference the element among buffer.elements
e.currentBuffer.unreferenceElement(elem)
if isMainElement:
parent = e.currentBuffer.parent
diff --git a/px/__init__.py b/px/__init__.py
new file mode 100644
index 0000000..37f564e
--- /dev/null
+++ b/px/__init__.py
@@ -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 = '%s' % 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
+# ------------------------------------------------------------------------------
diff --git a/px/px_parser.py b/px/px_parser.py
new file mode 100644
index 0000000..fd84119
--- /dev/null
+++ b/px/px_parser.py
@@ -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)
+# ------------------------------------------------------------------------------