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-]+)(.*?)>.*', 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) +# ------------------------------------------------------------------------------