# ------------------------------------------------------------------------------ # Appy is a framework for building applications in the Python language. # Copyright (C) 2007 Gaetan Delannay # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. # ------------------------------------------------------------------------------ from appy import Object from appy.pod import PodError from appy.pod.elements import * # ------------------------------------------------------------------------------ EVAL_ERROR = 'Error while evaluating expression "%s".' FROM_EVAL_ERROR = 'Error while evaluating the expression "%s" defined in the ' \ '"from" part of a statement.' WRONG_SEQ_TYPE = 'Expression "%s" is not iterable.' TABLE_NOT_ONE_CELL = "The table you wanted to populate with '%s' " \ "can\'t be dumped with the '-' option because it has " \ "more than one cell in it." # ------------------------------------------------------------------------------ class BufferAction: '''Abstract class representing a action (=statement) that must be performed on the content of a buffer (if, for...).''' def __init__(self, name, buffer, expr, elem, minus, source, fromExpr): self.name = name # Actions may be named. Currently, the name of an # action is only used for giving a name to "if" actions; thanks to this # name, "else" actions that are far away may reference their "if". self.buffer = buffer # The object of the action self.expr = expr # Python expression to evaluate (may be None in the # case of a NullAction or ElseAction, for example) self.elem = elem # The element within the buffer that is the object # of the action. self.minus = minus # If True, the main buffer element(s) must not be # dumped. self.result = self.buffer.getFileBuffer() 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') self.fromExpr = fromExpr # We store the result of evaluation of expr and fromExpr self.exprResult = None self.fromExprResult = None def writeError(self, errorMessage, dumpTb=True): # Empty the buffer self.buffer.__init__(self.buffer.env, self.buffer.parent) PodError.dump(self.buffer, errorMessage, withinElement=self.elem, dumpTb=dumpTb) self.buffer.evaluate() def execute(self): # Check that if minus is set, we have an element which can accept it if self.minus and isinstance(self.elem, Table) and \ (not self.elem.tableInfo.isOneCell()): self.writeError(TABLE_NOT_ONE_CELL % self.expr) else: errorOccurred = False if self.expr: try: self.exprResult = eval(self.expr, self.buffer.env.context) except: self.exprResult = None self.writeError(EVAL_ERROR % self.expr) errorOccurred = True if not errorOccurred: self.do() def evaluateBuffer(self): if self.source == 'buffer': self.buffer.evaluate(removeMainElems = self.minus) else: # Evaluate fromExpr self.fromExprResult = None errorOccurred = False try: self.fromExprResult= eval(self.fromExpr,self.buffer.env.context) except PodError, pe: self.writeError(FROM_EVAL_ERROR % self.fromExpr + ' ' + str(pe), dumpTb=False) errorOccurred = True except: self.writeError(FROM_EVAL_ERROR % self.fromExpr) errorOccurred = True if not errorOccurred: self.result.write(self.fromExprResult) class IfAction(BufferAction): '''Action that determines if we must include the content of the buffer in the result or not.''' def do(self): if self.exprResult: self.evaluateBuffer() else: if self.buffer.isMainElement(Cell.OD): # Don't leave the current row with a wrong number of cells self.result.dumpElement(Cell.OD.elem) class ElseAction(IfAction): '''Action that is linked to a previous "if" action. In fact, an "else" action works exactly like an "if" action, excepted that instead of defining a conditional expression, it is based on the negation of the conditional expression of the last defined "if" action.''' def __init__(self, name, buffer, expr, elem, minus, source, fromExpr, ifAction): IfAction.__init__(self, name, buffer, None, elem, minus, source, fromExpr) self.ifAction = ifAction def do(self): # The result of this "else" action is "not ". self.exprResult = not self.ifAction.exprResult IfAction.do(self) class ForAction(BufferAction): '''Actions that will include the content of the buffer as many times as 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, fromExpr) self.iter = iter # Name of the iterator variable used in the each loop def initialiseLoop(self): '''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 # about all currently walked loops. For every walked loop, a specific # object, le'ts name it curLoop, accessible at getattr(loop, self.iter), # stores info about its status: # * curLoop.length gives the total number of walked elements withhin # the loop # * curLoop.nb gives the index (starting at 0) if the currently # walked element. # For example, if you have a "for" statement like this: # for elem in myListOfElements # Within the part of the ODT document impacted by this statement, you # may access to: # * loop.elem.length to know the total length of myListOfElements # * loop.elem.nb to know the index of the current elem within # myListOfElements. if 'loop' not in context: context['loop'] = Object() try: total = len(self.exprResult) except: total = 0 curLoop = Object(length=total) setattr(context['loop'], self.iter, curLoop) return curLoop def do(self): context = self.buffer.env.context # Check self.exprResult type try: iter(self.exprResult) # All "iterable" objects are OK. Thanks to Bernhard Bender for this # improvement. except TypeError: self.writeError(WRONG_SEQ_TYPE % self.expr) return # Remember variable hidden by iter if any hasHiddenVariable = False if context.has_key(self.iter): hiddenVariable = context[self.iter] hasHiddenVariable = True # In the case of cells, initialize some values isCell = False if isinstance(self.elem, Cell): isCell = True nbOfColumns = self.elem.tableInfo.nbOfColumns initialColIndex = self.elem.colIndex currentColIndex = initialColIndex rowAttributes = self.elem.tableInfo.curRowAttrs # If self.exprResult is empty, dump an empty cell to avoid # having the wrong number of cells for the current row if not self.exprResult: self.result.dumpElement(Cell.OD.elem) # Enter the "for" loop loop = self.initialiseLoop() i = -1 for item in self.exprResult: i += 1 loop.nb = i context[self.iter] = item # Cell: add a new row if we are at the end of a row if isCell and (currentColIndex == nbOfColumns): self.result.dumpEndElement(Row.OD.elem) self.result.dumpStartElement(Row.OD.elem, rowAttributes) currentColIndex = 0 self.evaluateBuffer() # Cell: increment the current column index if isCell: currentColIndex += 1 # Cell: leave the last row with the correct number of cells if isCell and self.exprResult: wrongNbOfCells = (currentColIndex-1) - initialColIndex if wrongNbOfCells < 0: # Too few cells for last row for i in range(abs(wrongNbOfCells)): context[self.iter] = '' self.buffer.evaluate(subElements=False) # This way, the cell is dumped with the correct styles elif wrongNbOfCells > 0: # Too many cells for last row # Finish current row nbOfMissingCells = 0 if currentColIndex < nbOfColumns: nbOfMissingCells = nbOfColumns - currentColIndex context[self.iter] = '' for i in range(nbOfMissingCells): self.buffer.evaluate(subElements=False) self.result.dumpEndElement(Row.OD.elem) # Create additional row with remaining cells self.result.dumpStartElement(Row.OD.elem, rowAttributes) nbOfRemainingCells = wrongNbOfCells + nbOfMissingCells nbOfMissingCellsLastLine = nbOfColumns - nbOfRemainingCells context[self.iter] = '' for i in range(nbOfMissingCellsLastLine): self.buffer.evaluate(subElements=False) # Delete the object representing info about the current loop. try: delattr(context['loop'], self.iter) except AttributeError: pass # Restore hidden variable if any if hasHiddenVariable: context[self.iter] = hiddenVariable else: if self.exprResult: if self.iter in context: # May not be the case on error. del context[self.iter] class NullAction(BufferAction): '''Action that does nothing. Used in conjunction with a "from" clause, it allows to insert in a buffer arbitrary odt content.''' def do(self): self.evaluateBuffer() class VariableAction(BufferAction): '''Action that allows to define a variable somewhere in the template.''' def __init__(self, name, buffer, expr, elem, minus, varName, source, fromExpr): BufferAction.__init__(self, name, buffer, expr, elem, minus, source, fromExpr) self.varName = varName # Name of the variable def do(self): context = self.buffer.env.context # Remember the variable hidden by our variable definition, if any hasHiddenVariable = False if context.has_key(self.varName): hiddenVariable = context[self.varName] hasHiddenVariable = True # Add the variable to the context context[self.varName] = self.exprResult # Evaluate the buffer self.evaluateBuffer() # Restore hidden variable if any if hasHiddenVariable: context[self.varName] = hiddenVariable else: del context[self.varName] # ------------------------------------------------------------------------------