[px] Made PX thread-safe.

This commit is contained in:
Gaetan Delannay 2013-06-25 12:04:23 +02:00
parent 086f93e845
commit 1d931cfb96
10 changed files with 2086 additions and 2037 deletions

View file

@ -37,6 +37,11 @@ class ToolMixin(BaseMixin):
if res in ('User', 'Group', 'Translation'): res = appName + res if res in ('User', 'Group', 'Translation'): res = appName + res
return res return res
def home(self):
'''Returns the content of px ToolWrapper.pxHome.'''
tool = self.appy()
return tool.pxHome({'self': tool}).encode('utf-8')
def getHomePage(self): def getHomePage(self):
'''Return the home page when a user hits the app.''' '''Return the home page when a user hits the app.'''
# If the app defines a method "getHomePage", call it. # If the app defines a method "getHomePage", call it.

View file

@ -5,6 +5,7 @@ from appy.gen.mail import sendMail
from appy.shared.utils import executeCommand from appy.shared.utils import executeCommand
from appy.gen.wrappers import AbstractWrapper from appy.gen.wrappers import AbstractWrapper
from appy.gen.installer import loggedUsers from appy.gen.installer import loggedUsers
from appy.px import Px
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
_PY = 'Please specify a file corresponding to a Python interpreter ' \ _PY = 'Please specify a file corresponding to a Python interpreter ' \
@ -19,6 +20,10 @@ NOT_UNO_ENABLED_PYTHON = '"%s" is not a UNO-enabled Python interpreter. ' \
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class ToolWrapper(AbstractWrapper): class ToolWrapper(AbstractWrapper):
pxHome = Px('''<p>Hello home</p>''',
template=AbstractWrapper.pxTemplate, hook='content')
def validPythonWithUno(self, value): def validPythonWithUno(self, value):
'''This method represents the validator for field unoEnabledPython.''' '''This method represents the validator for field unoEnabledPython.'''
if value: if value:

View file

@ -7,6 +7,7 @@ import appy.pod
from appy.gen import Type, Search, Ref, String, WorkflowAnonymous from appy.gen import Type, Search, Ref, String, WorkflowAnonymous
from appy.gen.indexer import defaultIndexes from appy.gen.indexer import defaultIndexes
from appy.gen.utils import createObject from appy.gen.utils import createObject
from appy.px import Px
from appy.shared.utils import getOsTempFolder, executeCommand, \ from appy.shared.utils import getOsTempFolder, executeCommand, \
normalizeString, sequenceTypes normalizeString, sequenceTypes
from appy.shared.xml_parser import XmlMarshaller from appy.shared.xml_parser import XmlMarshaller
@ -26,6 +27,15 @@ class AbstractWrapper(object):
'''Any real Appy-managed Zope object has a companion object that is an '''Any real Appy-managed Zope object has a companion object that is an
instance of this class.''' instance of this class.'''
pxTemplate = Px('''
<html>
<head><title>:self.title</title></head>
<body>
<x>:content</x>
</body>
</html>
''', prologue=Px.xhtmlPrologue)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Class methods # Class methods
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------

View file

@ -1,20 +1,18 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language. # This file is part of Appy, a framework for building applications in the Python
# Copyright (C) 2007 Gaetan Delannay # language. Copyright (C) 2007 Gaetan Delannay
# This program is free software; you can redistribute it and/or # Appy is free software; you can redistribute it and/or modify it under the
# modify it under the terms of the GNU General Public License # terms of the GNU General Public License as published by the Free Software
# as published by the Free Software Foundation; either version 2 # Foundation; either version 3 of the License, or (at your option) any later
# of the License, or (at your option) any later version. # version.
# This program is distributed in the hope that it will be useful, # Appy is distributed in the hope that it will be useful, but WITHOUT ANY
# but WITHOUT ANY WARRANTY; without even the implied warranty of # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License along with
# along with this program; if not, write to the Free Software # Appy. If not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from appy import Object from appy import Object
@ -43,30 +41,24 @@ class BufferAction:
# case of a NullAction or ElseAction, for example) # case of a NullAction or ElseAction, for example)
self.elem = elem # The element within the buffer that is the object self.elem = elem # The element within the buffer that is the object
# 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 elem(s) must not be dumped.
# dumped. self.source = source # If 'buffer', we must dump the (evaluated) buffer
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 # content. If 'from', we must dump what comes from the 'from' part of
# the action (='fromExpr') # the action (='fromExpr')
self.fromExpr = fromExpr self.fromExpr = fromExpr
# We store the result of evaluation of expr and fromExpr # When an error occurs, must we raise it or write it into the buffer?
self.exprResult = None self.raiseErrors = not self.buffer.pod
self.fromExprResult = None
# When an error is encountered, must we raise it or write it into the
# buffer?
self.raiseErrors = self.buffer.caller == 'px'
def getExceptionLine(self, e): def getExceptionLine(self, e):
'''Gets the line describing exception p_e, containing the pathname of '''Gets the line describing exception p_e, containing the pathname of
the exception class, the exception's message and line number.''' the exception class, the exception's message and line number.'''
return '%s.%s: %s' % (e.__module__, e.__class__.__name__, str(e)) return '%s.%s: %s' % (e.__module__, e.__class__.__name__, str(e))
def writeError(self, errorMessage, dumpTb=True): def manageError(self, result, context, errorMessage, dumpTb=True):
'''Write the encountered error into the buffer or raise an exception '''Manage the encountered error: dump it into the buffer or raise an
if self.raiseErrors is True.''' exception if self.raiseErrors is True.'''
if self.raiseErrors: if self.raiseErrors:
if self.buffer.caller == 'px': if not self.buffer.pod:
# Add in the error message the line nb where the errors occurs # Add in the error message the line nb where the errors occurs
# within the PX. # within the PX.
locator = self.buffer.env.parser.locator locator = self.buffer.env.parser.locator
@ -76,90 +68,95 @@ class BufferAction:
else: col = ', column %d' % col else: col = ', column %d' % col
errorMessage += ' (line %s%s)' % (locator.getLineNumber(), col) errorMessage += ' (line %s%s)' % (locator.getLineNumber(), col)
raise Exception(errorMessage) raise Exception(errorMessage)
# Empty the buffer # Empty the buffer (pod-only)
self.buffer.__init__(self.buffer.env, self.buffer.parent) self.buffer.__init__(self.buffer.env, self.buffer.parent)
PodError.dump(self.buffer, errorMessage, withinElement=self.elem, PodError.dump(self.buffer, errorMessage, withinElement=self.elem,
dumpTb=dumpTb) dumpTb=dumpTb)
self.buffer.evaluate() self.buffer.evaluate(result, context)
def evaluateExpression(self, expr): def evaluateExpression(self, result, context, expr):
'''Evaluates expression p_expr with the current context. Returns a tuple '''Evaluates expression p_expr with the current p_context. Returns a
(result, errorOccurred).''' tuple (result, errorOccurred).'''
try: try:
res = eval(expr, self.buffer.env.context) res = eval(expr, context)
error = False error = False
except Exception, e: except Exception, e:
res = None res = None
self.writeError(EVAL_ERROR % (expr, self.getExceptionLine(e))) errorMessage = EVAL_ERROR % (expr, self.getExceptionLine(e))
self.manageError(result, context, errorMessage)
error = True error = True
return res, error return res, error
def execute(self): def execute(self, result, context):
'''Executes this action given some p_context and add the result to
p_result.'''
# Check that if minus is set, we have an element which can accept it # Check that if minus is set, we have an element which can accept it
if self.minus and isinstance(self.elem, Table) and \ if self.minus and isinstance(self.elem, Table) and \
(not self.elem.tableInfo.isOneCell()): (not self.elem.tableInfo.isOneCell()):
self.writeError(TABLE_NOT_ONE_CELL % self.expr) self.manageError(result, context, TABLE_NOT_ONE_CELL % self.expr)
else: else:
error = False error = False
# Evaluate self.expr in eRes.
eRes = None
if self.expr: if self.expr:
self.exprResult, error = self.evaluateExpression(self.expr) eRes,error = self.evaluateExpression(result, context, self.expr)
if not error: if not error:
self.do() # Trigger action-specific behaviour.
self.do(result, context, eRes)
def evaluateBuffer(self): def evaluateBuffer(self, result, context):
if self.source == 'buffer': if self.source == 'buffer':
self.buffer.evaluate(removeMainElems = self.minus) self.buffer.evaluate(result, context, removeMainElems=self.minus)
else: else:
# Evaluate fromExpr # Evaluate self.fromExpr in feRes.
self.fromExprResult = None feRes = None
error = False error = False
try: try:
self.fromExprResult= eval(self.fromExpr,self.buffer.env.context) feRes = eval(self.fromExpr, context)
except Exception, e: except Exception, e:
msg= FROM_EVAL_ERROR % (self.fromExpr, self.getExceptionLine(e)) msg = FROM_EVAL_ERROR% (self.fromExpr, self.getExceptionLine(e))
self.writeError(msg, dumpTb=False) self.manageError(result, context, msg, dumpTb=False)
error = True error = True
if not error: if not error:
self.result.write(self.fromExprResult) result.write(feRes)
class IfAction(BufferAction): class IfAction(BufferAction):
'''Action that determines if we must include the content of the buffer in '''Action that determines if we must include the content of the buffer in
the result or not.''' the result or not.'''
def do(self): def do(self, result, context, exprRes):
if self.exprResult: if exprRes:
self.evaluateBuffer() self.evaluateBuffer(result, context)
else: else:
if self.buffer.isMainElement(Cell.OD): if self.buffer.isMainElement(Cell.OD):
# Don't leave the current row with a wrong number of cells # Don't leave the current row with a wrong number of cells
self.result.dumpElement(Cell.OD.elem) result.dumpElement(Cell.OD.elem)
class ElseAction(IfAction): class ElseAction(IfAction):
'''Action that is linked to a previous "if" action. In fact, an "else" '''Action that is linked to a previous "if" action. In fact, an "else"
action works exactly like an "if" action, excepted that instead of action works exactly like an "if" action, excepted that instead of
defining a conditional expression, it is based on the negation of the defining a conditional expression, it is based on the negation of the
conditional expression of the last defined "if" action.''' conditional expression of the last defined "if" action.'''
def __init__(self, name, buffer, expr, elem, minus, source, fromExpr,
ifAction): def __init__(self, name, buff, expr, elem, minus, src, fromExpr, ifAction):
IfAction.__init__(self, name, buffer, None, elem, minus, source, IfAction.__init__(self, name, buff, None, elem, minus, src, fromExpr)
fromExpr)
self.ifAction = ifAction self.ifAction = ifAction
def do(self):
# The result of this "else" action is "not <result from last execution def do(self, result, context, exprRes):
# of linked 'if' action>". # This action is executed if the tied "if" action is not executed.
self.exprResult = not self.ifAction.exprResult ifAction = self.ifAction
IfAction.do(self) iRes,error = ifAction.evaluateExpression(result, context, ifAction.expr)
IfAction.do(self, result, context, not iRes)
class ForAction(BufferAction): class ForAction(BufferAction):
'''Actions that will include the content of the buffer as many times as '''Actions that will include the content of the buffer as many times as
specified by the action parameters.''' specified by the action parameters.'''
def __init__(self, name, buffer, expr, elem, minus, iter, source, fromExpr):
BufferAction.__init__(self, name, buffer, expr, elem, minus, source, def __init__(self, name, buff, expr, elem, minus, iter, src, fromExpr):
fromExpr) BufferAction.__init__(self, name, buff, expr, elem, minus, src,fromExpr)
self.iter = iter # Name of the iterator variable used in the each loop self.iter = iter # Name of the iterator variable used in the each loop
def initialiseLoop(self): def initialiseLoop(self, context, elems):
'''Initialises information about the loop, before entering into it.''' '''Initialises information about the loop, before entering into it.'''
context = self.buffer.env.context
# The "loop" object, made available in the POD context, contains info # The "loop" object, made available in the POD context, contains info
# about all currently walked loops. For every walked loop, a specific # about all currently walked loops. For every walked loop, a specific
# object, le'ts name it curLoop, accessible at getattr(loop, self.iter), # object, le'ts name it curLoop, accessible at getattr(loop, self.iter),
@ -182,21 +179,22 @@ class ForAction(BufferAction):
if 'loop' not in context: if 'loop' not in context:
context['loop'] = Object() context['loop'] = Object()
try: try:
total = len(self.exprResult) total = len(elems)
except: except:
total = 0 total = 0
curLoop = Object(length=total) curLoop = Object(length=total)
setattr(context['loop'], self.iter, curLoop) setattr(context['loop'], self.iter, curLoop)
return curLoop return curLoop
def do(self): def do(self, result, context, elems):
context = self.buffer.env.context '''Performs the "for" action. p_elems is the list of elements to
# Check self.exprResult type walk, evaluated from self.expr.'''
# Check p_exprRes type.
try: try:
# All "iterable" objects are OK. # All "iterable" objects are OK.
iter(self.exprResult) iter(elems)
except TypeError: except TypeError:
self.writeError(WRONG_SEQ_TYPE % self.expr) self.manageError(result, context, WRONG_SEQ_TYPE % self.expr)
return return
# Remember variable hidden by iter if any # Remember variable hidden by iter if any
hasHiddenVariable = False hasHiddenVariable = False
@ -211,14 +209,14 @@ class ForAction(BufferAction):
initialColIndex = self.elem.colIndex initialColIndex = self.elem.colIndex
currentColIndex = initialColIndex currentColIndex = initialColIndex
rowAttributes = self.elem.tableInfo.curRowAttrs rowAttributes = self.elem.tableInfo.curRowAttrs
# If self.exprResult is empty, dump an empty cell to avoid # If p_elems is empty, dump an empty cell to avoid having the wrong
# having the wrong number of cells for the current row # number of cells for the current row.
if not self.exprResult: if not elems:
self.result.dumpElement(Cell.OD.elem) result.dumpElement(Cell.OD.elem)
# Enter the "for" loop # Enter the "for" loop.
loop = self.initialiseLoop() loop = self.initialiseLoop(context, elems)
i = -1 i = -1
for item in self.exprResult: for item in elems:
i += 1 i += 1
loop.nb = i loop.nb = i
loop.first = i == 0 loop.first = i == 0
@ -226,20 +224,20 @@ class ForAction(BufferAction):
context[self.iter] = item context[self.iter] = item
# Cell: add a new row if we are at the end of a row # Cell: add a new row if we are at the end of a row
if isCell and (currentColIndex == nbOfColumns): if isCell and (currentColIndex == nbOfColumns):
self.result.dumpEndElement(Row.OD.elem) result.dumpEndElement(Row.OD.elem)
self.result.dumpStartElement(Row.OD.elem, rowAttributes) result.dumpStartElement(Row.OD.elem, rowAttributes)
currentColIndex = 0 currentColIndex = 0
self.evaluateBuffer() self.evaluateBuffer(result, context)
# Cell: increment the current column index # Cell: increment the current column index
if isCell: if isCell:
currentColIndex += 1 currentColIndex += 1
# Cell: leave the last row with the correct number of cells # Cell: leave the last row with the correct number of cells
if isCell and self.exprResult: if isCell and elems:
wrongNbOfCells = (currentColIndex-1) - initialColIndex wrongNbOfCells = (currentColIndex-1) - initialColIndex
if wrongNbOfCells < 0: # Too few cells for last row if wrongNbOfCells < 0: # Too few cells for last row
for i in range(abs(wrongNbOfCells)): for i in range(abs(wrongNbOfCells)):
context[self.iter] = '' context[self.iter] = ''
self.buffer.evaluate(subElements=False) self.buffer.evaluate(result, context, subElements=False)
# This way, the cell is dumped with the correct styles # This way, the cell is dumped with the correct styles
elif wrongNbOfCells > 0: # Too many cells for last row elif wrongNbOfCells > 0: # Too many cells for last row
# Finish current row # Finish current row
@ -248,15 +246,15 @@ class ForAction(BufferAction):
nbOfMissingCells = nbOfColumns - currentColIndex nbOfMissingCells = nbOfColumns - currentColIndex
context[self.iter] = '' context[self.iter] = ''
for i in range(nbOfMissingCells): for i in range(nbOfMissingCells):
self.buffer.evaluate(subElements=False) self.buffer.evaluate(result, context, subElements=False)
self.result.dumpEndElement(Row.OD.elem) result.dumpEndElement(Row.OD.elem)
# Create additional row with remaining cells # Create additional row with remaining cells
self.result.dumpStartElement(Row.OD.elem, rowAttributes) result.dumpStartElement(Row.OD.elem, rowAttributes)
nbOfRemainingCells = wrongNbOfCells + nbOfMissingCells nbOfRemainingCells = wrongNbOfCells + nbOfMissingCells
nbOfMissingCellsLastLine = nbOfColumns - nbOfRemainingCells nbOfMissingCellsLastLine = nbOfColumns - nbOfRemainingCells
context[self.iter] = '' context[self.iter] = ''
for i in range(nbOfMissingCellsLastLine): for i in range(nbOfMissingCellsLastLine):
self.buffer.evaluate(subElements=False) self.buffer.evaluate(result, context, subElements=False)
# Delete the object representing info about the current loop. # Delete the object representing info about the current loop.
try: try:
delattr(context['loop'], self.iter) delattr(context['loop'], self.iter)
@ -266,40 +264,40 @@ class ForAction(BufferAction):
if hasHiddenVariable: if hasHiddenVariable:
context[self.iter] = hiddenVariable context[self.iter] = hiddenVariable
else: else:
if self.exprResult: if elems:
if self.iter in context: # May not be the case on error. if self.iter in context: # May not be the case on error.
del context[self.iter] del context[self.iter]
class NullAction(BufferAction): class NullAction(BufferAction):
'''Action that does nothing. Used in conjunction with a "from" clause, it '''Action that does nothing. Used in conjunction with a "from" clause, it
allows to insert in a buffer arbitrary odt content.''' allows to insert in a buffer arbitrary odt content.'''
def do(self): def do(self, result, context, exprRes):
self.evaluateBuffer() self.evaluateBuffer(result, context)
class VariablesAction(BufferAction): class VariablesAction(BufferAction):
'''Action that allows to define a set of variables somewhere in the '''Action that allows to define a set of variables somewhere in the
template.''' template.'''
def __init__(self, name, buffer, elem, minus, variables, source, fromExpr): def __init__(self, name, buff, elem, minus, variables, src, fromExpr):
# We do not use the default Buffer.expr attribute for storing the Python # We do not use the default Buffer.expr attribute for storing the Python
# expression, because here we will have several expressions, one for # expression, because here we will have several expressions, one for
# every defined variable. # every defined variable.
BufferAction.__init__(self, name, buffer, None, elem, minus, source, BufferAction.__init__(self,name, buff, None, elem, minus, src, fromExpr)
fromExpr)
# Definitions of variables: ~[(s_name, s_expr)]~ # Definitions of variables: ~[(s_name, s_expr)]~
self.variables = variables self.variables = variables
def do(self): def do(self, result, context, exprRes):
context = self.buffer.env.context '''Evaluate the variables' expressions: because there are several
# Evaluate the variables' expressions: because there are several expressions, we do not use the standard, single-expression-minded
# expressions, we did not use the standard, single-expression-minded BufferAction code for evaluating our expressions.
# BufferAction code for evaluating our expressions.
# Also: we remember the names and values of the variables that we will We remember the names and values of the variables that we will hide
# hide in the context: after execution of this buffer we will restore in the context: after execution of this buffer we will restore those
# those values. values.
'''
hidden = None hidden = None
for name, expr in self.variables: for name, expr in self.variables:
# Evaluate the expression # Evaluate variable expression in vRes.
result, error = self.evaluateExpression(expr) vRes, error = self.evaluateExpression(result, context, expr)
if error: return if error: return
# Remember the variable previous value if already in the context # Remember the variable previous value if already in the context
if name in context: if name in context:
@ -308,9 +306,9 @@ class VariablesAction(BufferAction):
else: else:
hidden[name] = context[name] hidden[name] = context[name]
# Store the result into the context # Store the result into the context
context[name] = result context[name] = vRes
# Evaluate the buffer # Evaluate the buffer
self.evaluateBuffer() self.evaluateBuffer(result, context)
# Restore hidden variables if any # Restore hidden variables if any
if hidden: context.update(hidden) if hidden: context.update(hidden)
# Delete not-hidden variables # Delete not-hidden variables

View file

@ -1,24 +1,21 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language. # This file is part of Appy, a framework for building applications in the Python
# Copyright (C) 2007 Gaetan Delannay # language. Copyright (C) 2007 Gaetan Delannay
# This program is free software; you can redistribute it and/or # Appy is free software; you can redistribute it and/or modify it under the
# modify it under the terms of the GNU General Public License # terms of the GNU General Public License as published by the Free Software
# as published by the Free Software Foundation; either version 2 # Foundation; either version 3 of the License, or (at your option) any later
# of the License, or (at your option) any later version. # version.
# This program is distributed in the hope that it will be useful, # Appy is distributed in the hope that it will be useful, but WITHOUT ANY
# but WITHOUT ANY WARRANTY; without even the implied warranty of # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License along with
# along with this program; if not, write to the Free Software # Appy. If not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re import re
from xml.sax.saxutils import quoteattr from xml.sax.saxutils import quoteattr
from appy.shared.xml_parser import xmlPrologue, escapeXml from appy.shared.xml_parser import xmlPrologue, escapeXml
from appy.pod import PodError from appy.pod import PodError
@ -121,8 +118,8 @@ class Buffer:
self.parent = parent self.parent = parent
self.subBuffers = {} # ~{i_bufferIndex: Buffer}~ self.subBuffers = {} # ~{i_bufferIndex: Buffer}~
self.env = env self.env = env
# Are we computing for pod or for px ? # Are we computing for pod (True) or px (False)
self.caller= (env.__class__.__name__=='PxEnvironment') and 'px' or 'pod' self.pod = env.__class__.__name__ != 'PxEnvironment'
def addSubBuffer(self, subBuffer=None): def addSubBuffer(self, subBuffer=None):
if not subBuffer: if not subBuffer:
@ -184,7 +181,7 @@ class Buffer:
def dumpContent(self, content): def dumpContent(self, content):
'''Dumps string p_content into the buffer.''' '''Dumps string p_content into the buffer.'''
if self.caller == 'pod': if self.pod:
# Take care of converting line breaks to odf line breaks. # Take care of converting line breaks to odf line breaks.
content = escapeXml(content, format='odf', content = escapeXml(content, format='odf',
nsText=self.env.namespaces[self.env.NS_TEXT]) nsText=self.env.namespaces[self.env.NS_TEXT])
@ -214,7 +211,8 @@ class FileBuffer(Buffer):
def addExpression(self, expression, tiedHook=None): def addExpression(self, expression, tiedHook=None):
# At 2013-02-06, this method was not called within the whole test suite. # At 2013-02-06, this method was not called within the whole test suite.
try: try:
res, escape = Expression(expression).evaluate(self.env.context) expr = Expression(expression, self.pod)
res, escape = expr.evaluate(self.env.context)
if escape: self.dumpContent(res) if escape: self.dumpContent(res)
else: self.write(res) else: self.write(res)
except Exception, e: except Exception, e:
@ -329,7 +327,7 @@ class MemoryBuffer(Buffer):
# First unreference all elements # First unreference all elements
for index in self.getElementIndexes(expressions=False): for index in self.getElementIndexes(expressions=False):
del self.elements[index] del self.elements[index]
self.evaluate() self.evaluate(self.parent, self.env.context)
else: else:
# Transfer content in itself # Transfer content in itself
oldParentLength = self.parent.getLength() oldParentLength = self.parent.getLength()
@ -337,7 +335,7 @@ class MemoryBuffer(Buffer):
# Transfer elements # Transfer elements
for index, podElem in self.elements.iteritems(): for index, podElem in self.elements.iteritems():
self.parent.elements[oldParentLength + index] = podElem self.parent.elements[oldParentLength + index] = podElem
# Transfer subBuffers # Transfer sub-buffers
for index, buf in self.subBuffers.iteritems(): for index, buf in self.subBuffers.iteritems():
self.parent.subBuffers[oldParentLength + index] = buf self.parent.subBuffers[oldParentLength + index] = buf
# Empty the buffer # Empty the buffer
@ -360,7 +358,7 @@ class MemoryBuffer(Buffer):
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, self.pod)
if tiedHook: tiedHook.tiedExpression = expr if tiedHook: tiedHook.tiedExpression = expr
self.elements[self.getLength()] = expr self.elements[self.getLength()] = expr
# To be sure that an expr and an elem can't be found at the same index # To be sure that an expr and an elem can't be found at the same index
@ -621,8 +619,11 @@ 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, result, context, subElements=True,
result = self.getRootBuffer() removeMainElems=False):
'''Evaluates this buffer given the current p_context and add the result
into p_result. With pod, p_result is the root file buffer; with px
it is a memory buffer.'''
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())
@ -639,20 +640,20 @@ class MemoryBuffer(Buffer):
currentIndex = index + 1 currentIndex = index + 1
if isinstance(evalEntry, Expression): if isinstance(evalEntry, Expression):
try: try:
res, escape = evalEntry.evaluate(self.env.context) res, escape = evalEntry.evaluate(context)
if escape: result.dumpContent(res) if escape: result.dumpContent(res)
else: result.write(res) else: result.write(res)
except Exception, e: except Exception, e:
if self.caller == 'pod': if self.pod:
PodError.dump(result, EVAL_EXPR_ERROR % ( PodError.dump(result, EVAL_EXPR_ERROR % (
evalEntry.expr, e), dumpTb=False) evalEntry.expr, e), dumpTb=False)
else: # px else: # px
raise Exception(EVAL_EXPR_ERROR %(evalEntry.expr,e)) raise Exception(EVAL_EXPR_ERROR %(evalEntry.expr,e))
elif isinstance(evalEntry, Attributes): elif isinstance(evalEntry, Attributes):
result.write(evalEntry.evaluate(self.env.context)) result.write(evalEntry.evaluate(context))
else: # It is a subBuffer else: # It is a subBuffer
if evalEntry.action: if evalEntry.action:
evalEntry.action.execute() evalEntry.action.execute(result, context)
else: else:
result.write(evalEntry.content) result.write(evalEntry.content)
stopIndex = self.getStopIndex(removeMainElems) stopIndex = self.getStopIndex(removeMainElems)

View file

@ -1,20 +1,18 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language. # This file is part of Appy, a framework for building applications in the Python
# Copyright (C) 2007 Gaetan Delannay # language. Copyright (C) 2007 Gaetan Delannay
# This program is free software; you can redistribute it and/or # Appy is free software; you can redistribute it and/or modify it under the
# modify it under the terms of the GNU General Public License # terms of the GNU General Public License as published by the Free Software
# as published by the Free Software Foundation; either version 2 # Foundation; either version 3 of the License, or (at your option) any later
# of the License, or (at your option) any later version. # version.
# This program is distributed in the hope that it will be useful, # Appy is distributed in the hope that it will be useful, but WITHOUT ANY
# but WITHOUT ANY WARRANTY; without even the implied warranty of # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License along with
# along with this program; if not, write to the Free Software # Appy. If not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from xml.sax.saxutils import quoteattr from xml.sax.saxutils import quoteattr
@ -28,8 +26,8 @@ class PodElement:
'table': 'Table', 'table-row': 'Row', 'table-cell': 'Cell', 'table': 'Table', 'table-row': 'Row', 'table-cell': 'Cell',
None: 'Expression'} None: 'Expression'}
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 # Elements for which the '-' operator can be applied.
# be applied MINUS_ELEMS = ('section', 'table')
@staticmethod @staticmethod
def create(elem): def create(elem):
'''Used to create any POD elem that has an equivalent OD element. Not '''Used to create any POD elem that has an equivalent OD element. Not
@ -38,8 +36,9 @@ class PodElement:
class Text(PodElement): class Text(PodElement):
OD = XmlElement('p', nsUri=ns.NS_TEXT) OD = XmlElement('p', nsUri=ns.NS_TEXT)
subTags = [] # When generating an error we may need to surround the error # When generating an error we may need to surround it with a given tag and
# with a given tag and subtags # sub-tags.
subTags = []
class Title(PodElement): class Title(PodElement):
OD = XmlElement('h', nsUri=ns.NS_TEXT) OD = XmlElement('h', nsUri=ns.NS_TEXT)
@ -48,8 +47,9 @@ class Title(PodElement):
class Section(PodElement): class Section(PodElement):
OD = XmlElement('section', nsUri=ns.NS_TEXT) OD = XmlElement('section', nsUri=ns.NS_TEXT)
subTags = [Text.OD] subTags = [Text.OD]
DEEPEST_TO_REMOVE = OD # When we must remove the Section element from a # When we must remove the Section element from a buffer, the deepest element
# buffer, the deepest element to remove is the Section element itself # to remove is the Section element itself.
DEEPEST_TO_REMOVE = OD
class Cell(PodElement): class Cell(PodElement):
OD = XmlElement('table-cell', nsUri=ns.NS_TABLE) OD = XmlElement('table-cell', nsUri=ns.NS_TABLE)
@ -65,19 +65,19 @@ class Row(PodElement):
class Table(PodElement): class Table(PodElement):
OD = XmlElement('table', nsUri=ns.NS_TABLE) OD = XmlElement('table', nsUri=ns.NS_TABLE)
subTags = [Row.OD, Cell.OD, Text.OD] subTags = [Row.OD, Cell.OD, Text.OD]
DEEPEST_TO_REMOVE = Cell.OD # When we must remove the Table element from a # When we must remove the Table element from a buffer, the deepest element
# buffer, the deepest element to remove is the Cell (it can only be done for # to remove is the Cell (it can only be done for one-row, one-cell tables).
# one-row, one-cell tables DEEPEST_TO_REMOVE = Cell.OD
def __init__(self): def __init__(self):
self.tableInfo = None # ~OdTable~ self.tableInfo = None # ~OdTable~
class Expression(PodElement): class Expression(PodElement):
'''Instances of this class represent Python expressions that are inserted '''Represents a Python expression that is found in a pod or px.'''
into a POD template.'''
OD = None OD = None
def __init__(self, pyExpr): def __init__(self, pyExpr, pod):
# The Python expression # The Python expression
self.expr = pyExpr.strip() self.expr = pyExpr.strip()
self.pod = pod # True if I work for pod, False if I work for px.
# Must we, when evaluating the expression, escape XML special chars # Must we, when evaluating the expression, escape XML special chars
# or not? # or not?
if self.expr.startswith(':'): if self.expr.startswith(':'):
@ -85,14 +85,18 @@ class Expression(PodElement):
self.escapeXml = False self.escapeXml = False
else: else:
self.escapeXml = True self.escapeXml = True
# We will store here the expression's true result (before being if self.pod:
# converted to a string) # pod-only: store here the expression's true result (before being
self.result = None # converted to a string).
# This boolean indicates if this Expression instance has already been self.result = None
# evaluated or not. Expressions which are tied to attribute hooks are # pod-only: the following bool indicates if this Expression instance
# already evaluated when the tied hook is evaluated: this boolean # has already been evaluated or not. Expressions which are tied to
# prevents the expression from being evaluated twice. # attribute hooks are already evaluated when the tied hook is
self.evaluated = False # evaluated: this boolean prevents the expression from being
# evaluated twice.
self.evaluated = False
# self.result and self.evaluated are not used by PX, because they
# are not thread-safe.
def evaluate(self, context): def evaluate(self, context):
'''Evaluates the Python expression (self.expr) with a given '''Evaluates the Python expression (self.expr) with a given
@ -102,7 +106,7 @@ class Expression(PodElement):
escapeXml = self.escapeXml escapeXml = self.escapeXml
# Evaluate the expression, or get it from self.result if it has already # Evaluate the expression, or get it from self.result if it has already
# been computed. # been computed.
if self.evaluated: if self.pod and self.evaluated:
res = self.result res = self.result
# It can happen only once, to ask to evaluate an expression that # It can happen only once, to ask to evaluate an expression that
# was already evaluated (from the tied hook). We reset here the # was already evaluated (from the tied hook). We reset here the
@ -111,9 +115,11 @@ class Expression(PodElement):
self.evaluated = False self.evaluated = False
else: else:
# Evaluates the Python expression # Evaluates the Python expression
res = self.result = eval(self.expr, context) res = eval(self.expr, context)
# Converts the expression result to a string that can be inserted into # pod-only: cache the expression result.
# the POD/PX result. if self.pod: self.result = res
# Converts the expr result to a string that can be inserted in the
# pod/px result.
resultType = res.__class__.__name__ resultType = res.__class__.__name__
if resultType == 'NoneType': if resultType == 'NoneType':
res = u'' res = u''
@ -124,7 +130,7 @@ class Expression(PodElement):
elif resultType == 'Px': elif resultType == 'Px':
# A PX that must be called within the current PX. Call it with the # A PX that must be called within the current PX. Call it with the
# current context. # current context.
res = res(context) res = res(context, applyTemplate=False)
# Force escapeXml to False. # Force escapeXml to False.
escapeXml = False escapeXml = False
else: else:
@ -133,7 +139,7 @@ class Expression(PodElement):
class Attributes(PodElement): class Attributes(PodElement):
'''Represents a bunch of XML attributes that will be dumped for a given tag '''Represents a bunch of XML attributes that will be dumped for a given tag
in the result.''' in the result. pod-only.'''
OD = None OD = None
floatTypes = ('int', 'long', 'float') floatTypes = ('int', 'long', 'float')
dateTypes = ('DateTime',) dateTypes = ('DateTime',)
@ -166,7 +172,7 @@ class Attributes(PodElement):
# Evaluate first the tied expression, in order to determine its type. # Evaluate first the tied expression, in order to determine its type.
try: try:
self.tiedExpression.evaluate(context) self.tiedExpression.evaluate(context)
self.evaluated = True self.tiedExpression.evaluated = True
except Exception, e: except Exception, e:
# Don't set "evaluated" to True. This way, when the buffer will # Don't set "evaluated" to True. This way, when the buffer will
# evaluate the expression directly, we will really evaluate it, so # evaluate the expression directly, we will really evaluate it, so

View file

@ -1,20 +1,18 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Appy is a framework for building applications in the Python language. # This file is part of Appy, a framework for building applications in the Python
# Copyright (C) 2007 Gaetan Delannay # language. Copyright (C) 2007 Gaetan Delannay
# This program is free software; you can redistribute it and/or # Appy is free software; you can redistribute it and/or modify it under the
# modify it under the terms of the GNU General Public License # terms of the GNU General Public License as published by the Free Software
# as published by the Free Software Foundation; either version 2 # Foundation; either version 3 of the License, or (at your option) any later
# of the License, or (at your option) any later version. # version.
# This program is distributed in the hope that it will be useful, # Appy is distributed in the hope that it will be useful, but WITHOUT ANY
# but WITHOUT ANY WARRANTY; without even the implied warranty of # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License along with
# along with this program; if not, write to the Free Software # Appy. If not, see <http://www.gnu.org/licenses/>.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import re import re
@ -65,17 +63,13 @@ class PodEnvironment(OdfEnvironment):
# continue to dump it in the current buffer # continue to dump it in the current buffer
ADD_IN_BUFFER = 0 ADD_IN_BUFFER = 0
# ADD_IN_SUBBUFFER: when encountering an impactable element, we must # ADD_IN_SUBBUFFER: when encountering an impactable element, we must
# create a new subbuffer and dump it in it. # create a new sub-buffer and dump it in it.
ADD_IN_SUBBUFFER = 1 ADD_IN_SUBBUFFER = 1
# Possible states # Possible states
IGNORING = 0 # We are ignoring what we are currently reading IGNORING = 0 # We are ignoring what we are currently reading
READING_CONTENT = 1 # We are reading "normal" content READING_CONTENT = 1 # We are reading "normal" content
READING_STATEMENT = 2 READING_STATEMENT = 2 # We are reading a POD statement (for, if...)
# We are reading a POD statement (for, if...), which is located within a READING_EXPRESSION = 3 # We are reading a POD expression.
# office:annotation element
READING_EXPRESSION = 3
# We are reading a POD expression, which is located between
# a text:change-start and a text:change-end elements
def __init__(self, context, inserts=[]): def __init__(self, context, inserts=[]):
OdfEnvironment.__init__(self) OdfEnvironment.__init__(self)
# Buffer where we must dump the content we are currently reading # Buffer where we must dump the content we are currently reading
@ -335,7 +329,8 @@ class PodParser(OdfParser):
if isinstance(parent, FileBuffer): if isinstance(parent, FileBuffer):
# Execute buffer action and delete the # Execute buffer action and delete the
# buffer. # buffer.
e.currentBuffer.action.execute() e.currentBuffer.action.execute(parent,
e.context)
parent.removeLastSubBuffer() parent.removeLastSubBuffer()
e.currentBuffer = parent e.currentBuffer = parent
e.mode = e.ADD_IN_SUBBUFFER e.mode = e.ADD_IN_SUBBUFFER

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from px_parser import PxParser, PxEnvironment from px_parser import PxParser, PxEnvironment
from appy.pod.buffers import MemoryBuffer
# Exception class -------------------------------------------------------------- # Exception class --------------------------------------------------------------
class PxError(Exception): pass class PxError(Exception): pass
@ -11,11 +12,23 @@ class PxError(Exception): pass
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Px: class Px:
'''Represents a (chunk of) PX code.''' '''Represents a (chunk of) PX code.'''
def __init__(self, content, isFileName=False, partial=True): xhtmlPrologue = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '\
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
def __init__(self, content, isFileName=False, partial=True,
template=None, hook=None, prologue=None):
'''p_content is the PX code, as a string, or a file name if p_isFileName '''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 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 False. Else, we must surround p_content with a root tag to be able
to parse it with a SAX parser.''' 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.
'''
# Get the PX content # Get the PX content
if isFileName: if isFileName:
f = file(content) f = file(content)
@ -33,15 +46,35 @@ class Px:
# Parses self.content (a PX code in a string) with self.parser, to # Parses self.content (a PX code in a string) with self.parser, to
# produce a tree of memory buffers. # produce a tree of memory buffers.
self.parser.parse(self.content) self.parser.parse(self.content)
# 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
def __call__(self, context): def __call__(self, context, applyTemplate=True):
# p_context must be a dict. Store it in the PX environment. '''Renders the PX.
self.parser.env.context = context
# Render the PX result and return it If the PX is based on a template PX, we have 2 possibilities.
env = self.parser.env 1. p_applyTemplate is True. This case corresponds to the initial
env.ast.evaluate() call to the current PX. In this case we call the template with a
res = env.result.content context containing, in the hook variable, the current PX.
# Clean the res, for the next evaluation 2. p_applyTemplate is False. In this case, we are currently executing
env.result.clean() the PX template, and, at the hook, we must include the current PX,
return res as is, without re-applying the template (else, an infinite
recursion would occur).
'''
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
return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -7,28 +7,19 @@ class PxEnvironment(XmlEnvironment):
'''Environment for the PX parser.''' '''Environment for the PX parser.'''
def __init__(self): def __init__(self):
# We try to mimic POD. POD has a root buffer that is a FileBuffer, which # In the following buffer, we will create a single memory sub-buffer
# is the final result buffer, into which the result of evaluating all # that will hold the result of parsing the PX = a hierarchy of memory
# memory buffers, defined as sub-buffers of this file buffer, is # buffers = PX's AST (Abstract Syntax Tree).
# 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 # 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 # generates the result in the same step: one AST is generated, and then
# directly produces a single evaluation, in the root file buffer. PX # directly produces a single evaluation, in the root file buffer. PX
# works in 2 steps: the AST is initially created in self.ast. Then, # works in 2 steps: the AST is initially created in self.ast. Then,
# several evaluations can be generated, in self.result, without # several (concurrent) evaluations can occur, without re-generating the
# re-generating the AST. After every evaluation, self.result will be # AST.
# cleaned, to be reusable for the next evaluation. self.ast = MemoryBuffer(self, None)
# Context will come afterwards
self.context = None
# Buffer where we must dump the content we are currently reading # Buffer where we must dump the content we are currently reading
self.currentBuffer = self.ast self.currentBuffer = self.ast
# Tag content we are currently reading. We will put soomething in this # Tag content we are currently reading. We will put something in this
# attribute only if we encounter content that is Python code. # attribute only if we encounter content that is Python code.
# Else, we will directly dump the parsed content into the current # Else, we will directly dump the parsed content into the current
# buffer. # buffer.