2013-03-15 10:50:28 -05:00
|
|
|
'''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.'''
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2013-06-26 10:06:06 -05:00
|
|
|
import xml.sax
|
2013-03-15 10:50:28 -05:00
|
|
|
from px_parser import PxParser, PxEnvironment
|
2013-06-25 05:04:23 -05:00
|
|
|
from appy.pod.buffers import MemoryBuffer
|
2013-07-11 09:41:45 -05:00
|
|
|
from appy.shared.xml_parser import xmlPrologue, xhtmlPrologue
|
2013-03-15 10:50:28 -05:00
|
|
|
|
|
|
|
# Exception class --------------------------------------------------------------
|
|
|
|
class PxError(Exception): pass
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class Px:
|
|
|
|
'''Represents a (chunk of) PX code.'''
|
2013-07-11 09:41:45 -05:00
|
|
|
xmlPrologue = xmlPrologue
|
|
|
|
xhtmlPrologue = xhtmlPrologue
|
2013-06-25 05:04:23 -05:00
|
|
|
|
|
|
|
def __init__(self, content, isFileName=False, partial=True,
|
2013-07-11 09:41:45 -05:00
|
|
|
template=None, hook=None, prologue=None, unicode=True):
|
2013-03-15 10:50:28 -05:00
|
|
|
'''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
|
2013-06-25 05:04:23 -05:00
|
|
|
to parse it with a SAX parser.
|
|
|
|
|
|
|
|
If this PX is based on another PX template, specify the PX template
|
|
|
|
in p_template and the name of the p_hook where to insert this PX into
|
|
|
|
the template PX.
|
|
|
|
|
|
|
|
If a p_prologue is specified, it will be rendered at the start of the
|
|
|
|
PX result.
|
2013-07-11 09:41:45 -05:00
|
|
|
|
|
|
|
By default, a PX's result will be a unicode. If you want to get an
|
|
|
|
encoded str instead, use p_unicode=False.
|
2013-06-25 05:04:23 -05:00
|
|
|
'''
|
2013-03-15 10:50:28 -05:00
|
|
|
# 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.
|
2013-06-26 10:06:06 -05:00
|
|
|
try:
|
|
|
|
self.parser.parse(self.content)
|
|
|
|
except xml.sax.SAXParseException, spe:
|
|
|
|
self.completeErrorMessage(spe)
|
|
|
|
raise spe
|
2013-06-25 05:04:23 -05:00
|
|
|
# Is this PX based on a template PX?
|
|
|
|
self.template = template
|
|
|
|
self.hook = hook
|
|
|
|
# Is there some (XML, XHTML...) prologue to dump?
|
|
|
|
self.prologue = prologue
|
2013-07-11 09:41:45 -05:00
|
|
|
# Will the result be unicode or str?
|
|
|
|
self.unicode = unicode
|
2013-03-15 10:50:28 -05:00
|
|
|
|
2013-06-26 10:06:06 -05:00
|
|
|
def completeErrorMessage(self, parsingError):
|
|
|
|
'''A p_parsingError occurred. Complete the error message with the
|
|
|
|
erroneous line from self.content.'''
|
|
|
|
# Split lines from self.content
|
|
|
|
splitted = self.content.split('\n')
|
|
|
|
i = parsingError.getLineNumber() - 1
|
|
|
|
# Get the erroneous line, and add a subsequent line for indicating
|
|
|
|
# the erroneous column.
|
|
|
|
column = ' ' * (parsingError.getColumnNumber()-1) + '^'
|
|
|
|
lines = [splitted[i], column]
|
|
|
|
# Get the previous and next lines when present.
|
|
|
|
if i > 0: lines.insert(0, splitted[i-1])
|
|
|
|
if i < len(splitted)-1: lines.append(splitted[i+1])
|
|
|
|
parsingError._msg += '\n%s' % '\n'.join(lines)
|
|
|
|
|
2013-06-25 05:04:23 -05:00
|
|
|
def __call__(self, context, applyTemplate=True):
|
|
|
|
'''Renders the PX.
|
|
|
|
|
|
|
|
If the PX is based on a template PX, we have 2 possibilities.
|
|
|
|
1. p_applyTemplate is True. This case corresponds to the initial
|
|
|
|
call to the current PX. In this case we call the template with a
|
|
|
|
context containing, in the hook variable, the current PX.
|
|
|
|
2. p_applyTemplate is False. In this case, we are currently executing
|
|
|
|
the PX template, and, at the hook, we must include the current PX,
|
|
|
|
as is, without re-applying the template (else, an infinite
|
|
|
|
recursion would occur).
|
|
|
|
'''
|
2013-09-20 10:42:07 -05:00
|
|
|
# Developer, forget the following line forever.
|
|
|
|
if '_ctx_' not in context: context['_ctx_'] = context
|
|
|
|
|
2013-06-25 05:04:23 -05:00
|
|
|
if self.hook and applyTemplate:
|
|
|
|
# Call the template PX, filling the hook with the current PX.
|
|
|
|
context[self.hook] = self
|
|
|
|
return self.template(context)
|
|
|
|
else:
|
|
|
|
# Create a Memory buffer for storing the result.
|
|
|
|
env = self.parser.env
|
|
|
|
result = MemoryBuffer(env, None)
|
|
|
|
env.ast.evaluate(result, context)
|
|
|
|
res = result.content
|
|
|
|
if self.prologue:
|
|
|
|
res = self.prologue + res
|
2013-07-11 09:41:45 -05:00
|
|
|
if not self.unicode:
|
|
|
|
res = res.encode('utf-8')
|
2013-06-25 05:04:23 -05:00
|
|
|
return res
|
2013-03-15 10:50:28 -05:00
|
|
|
# ------------------------------------------------------------------------------
|