[px] First draft for a new template engine Python-XML, sharing the pod roots.
This commit is contained in:
parent
5cc7884c03
commit
be3cc6ae59
6 changed files with 242 additions and 34 deletions
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…
Add table
Add a link
Reference in a new issue