[gen] Wrapper.reindex: allow to specify attribute 'fields' containing the list of fields that must be reindexed. If not given, all object fields are reindexed. [pod] POD ODS templates: into POD results from ODS templates, Python expressions that produce ints, floats or dates (Zope DateTime only for the moment) produce cells with typed values.

This commit is contained in:
Gaetan Delannay 2013-02-06 17:15:01 +01:00
parent 43261fde60
commit 2307a284cc
7 changed files with 1947 additions and 1820 deletions

View file

@ -372,14 +372,20 @@ class AbstractWrapper(object):
exec expression exec expression
return ctx return ctx
def reindex(self): def reindex(self, fields=None, unindex=False):
'''Asks a direct object reindexing. In most cases you don't have to '''Asks a direct object reindexing. In most cases you don't have to
reindex objects "manually" with this method. When an object is reindex objects "manually" with this method. When an object is
modified after some user action has been performed, Appy reindexes modified after some user action has been performed, Appy reindexes
this object automatically. But if your code modifies other objects, this object automatically. But if your code modifies other objects,
Appy may not know that they must be reindexed, too. So use this Appy may not know that they must be reindexed, too. So use this
method in those cases.''' method in those cases.
self.o.reindex() '''
if fields:
# Get names of indexes from field names.
indexes = [Search.getIndexName(name) for name in fields]
else:
indexes = None
self.o.reindex(indexes=indexes, unindex=unindex)
def export(self, at='string', format='xml', include=None, exclude=None): def export(self, at='string', format='xml', include=None, exclude=None):
'''Creates an "exportable" version of this object. p_format is "xml" by '''Creates an "exportable" version of this object. p_format is "xml" by

View file

@ -139,12 +139,24 @@ class Buffer:
def getLength(self): pass # To be overridden def getLength(self): pass # To be overridden
def dumpStartElement(self, elem, attrs={}, ignoreAttrs=()): def dumpStartElement(self, elem, attrs={}, ignoreAttrs=(),
insertAttributesHook=False):
'''Inserts into this buffer the start tag p_elem, with its p_attrs,
excepted those listed in p_ignoreAttrs. If p_insertAttributesHook
is True (works only for MemoryBuffers), we will insert an Attributes
instance at the end of the list of dumped attributes, in order to be
able, when evaluating the buffer, to dump additional attributes, not
known at this dump time.'''
self.write('<%s' % elem) self.write('<%s' % elem)
for name, value in attrs.items(): for name, value in attrs.items():
if ignoreAttrs and (name in ignoreAttrs): continue if ignoreAttrs and (name in ignoreAttrs): continue
self.write(' %s=%s' % (name, quoteattr(value))) self.write(' %s=%s' % (name, quoteattr(value)))
if insertAttributesHook:
res = self.addAttributes()
else:
res = None
self.write('>') self.write('>')
return res
def dumpEndElement(self, elem): def dumpEndElement(self, elem):
self.write('</%s>' % elem) self.write('</%s>' % elem)
@ -179,12 +191,21 @@ class FileBuffer(Buffer):
except UnicodeDecodeError: except UnicodeDecodeError:
self.content.write(something) self.content.write(something)
def addExpression(self, expression): def addExpression(self, expression, tiedHook=None):
# At 2013-02-06, this method was not called within the whole test suite.
try: try:
self.dumpContent(Expression(expression).evaluate(self.env.context)) self.dumpContent(Expression(expression).evaluate(self.env.context))
except Exception, e: except Exception, e:
PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False) PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False)
def addAttributes(self):
# Into a FileBuffer, it is not possible to insert Attributes. Every
# Attributes instance is tied to an Expression; because dumping
# expressions directly into FileBuffer instances seems to be a rather
# theorical case (see comment inside the previous method), it does not
# seem to be a real problem.
pass
def pushSubBuffer(self, subBuffer): pass def pushSubBuffer(self, subBuffer): pass
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -298,14 +319,21 @@ class MemoryBuffer(Buffer):
# Remember where this cell is in the table # Remember where this cell is in the table
newElem.colIndex = newElem.tableInfo.curColIndex newElem.colIndex = newElem.tableInfo.curColIndex
def addExpression(self, expression): def addExpression(self, expression, tiedHook=None):
# Create the POD expression # Create the POD expression
expr = Expression(expression) expr = Expression(expression)
expr.expr = expression if tiedHook: tiedHook.tiedExpression = expr
self.elements[self.getLength()] = expr self.elements[self.getLength()] = expr
self.content += u' '# To be sure that an expr and an elem can't be found self.content += u' '# To be sure that an expr and an elem can't be found
# at the same index in the buffer. # at the same index in the buffer.
def addAttributes(self):
# Create the Attributes instance
attrs = Attributes(self.env)
self.elements[self.getLength()] = attrs
self.content += u' '
return attrs
def createAction(self, statementGroup): def createAction(self, statementGroup):
'''Tries to create an action based on p_statementGroup. If the statement '''Tries to create an action based on p_statementGroup. If the statement
is not correct, r_ is -1. Else, r_ is the index of the element within is not correct, r_ is -1. Else, r_ is the index of the element within
@ -547,6 +575,8 @@ class MemoryBuffer(Buffer):
except Exception, e: except Exception, e:
PodError.dump(result, EVAL_EXPR_ERROR % ( PodError.dump(result, EVAL_EXPR_ERROR % (
evalEntry.expr, e), dumpTb=False) evalEntry.expr, e), dumpTb=False)
elif isinstance(evalEntry, Attributes):
result.write(evalEntry.evaluate(self.env.context))
else: # It is a subBuffer else: # It is a subBuffer
if evalEntry.action: if evalEntry.action:
evalEntry.action.execute() evalEntry.action.execute()

View file

@ -17,6 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from xml.sax.saxutils import quoteattr
from appy.shared.xml_parser import XmlElement from appy.shared.xml_parser import XmlElement
from appy.pod.odf_parser import OdfEnvironment as ns from appy.pod.odf_parser import OdfEnvironment as ns
from appy.pod import PodError from appy.pod import PodError
@ -71,11 +72,38 @@ class Table(PodElement):
self.tableInfo = None # ~OdTable~ self.tableInfo = None # ~OdTable~
class Expression(PodElement): class Expression(PodElement):
'''Instances of this class represent Python expressions that are inserted
into a POD template.'''
OD = None OD = None
def __init__(self, pyExpr): def __init__(self, pyExpr):
# The Python expression
self.expr = pyExpr self.expr = pyExpr
# We will store here the expression's true result (before being
# converted to a string)
self.result = None
# This boolean indicates if this Expression instance has already been
# evaluated or not. Expressions which are tied to attribute hooks are
# already evaluated when the tied hook is evaluated: this boolean
# prevents the expression from being evaluated twice.
self.evaluated = False
def evaluate(self, context): def evaluate(self, context):
res = eval(self.expr, context) '''Evaluates the Python expression (self.expr) with a given
p_context.'''
# Evaluate the expression, or get it from self.result if it has already
# been computed.
if self.evaluated:
res = self.result
# It can happen only once, to ask to evaluate an expression that
# was already evaluated (from the tied hook). We reset here the
# boolean "evaluated" to allow for the next evaluation, probably
# with another context.
self.evaluated = False
else:
# Evaluates the Python expression
res = self.result = eval(self.expr, context)
# Converts the expression result to a string that can be inserted into
# the POD result.
if res == None: if res == None:
res = u'' res = u''
elif isinstance(res, str): elif isinstance(res, str):
@ -85,4 +113,53 @@ class Expression(PodElement):
else: else:
res = unicode(res) res = unicode(res)
return res return res
class Attributes(PodElement):
'''Represents a bunch of XML attributes that will be dumped for a given tag
in the result.'''
OD = None
floatTypes = ('int', 'long', 'float')
dateTypes = ('DateTime',)
def __init__(self, env):
self.attrs = {}
# Depending on the result of a tied expression, we will dump, for
# another tag, the series of attrs that this instance represents.
self.tiedExpression = None
# We will need the env to get the full names of attributes to dump.
self.env = env
def computeAttributes(self, expr):
'''p_expr has been evaluated: its result is in expr.result. Depending
on its type, we will dump the corresponding attributes in
self.attrs.'''
exprType = expr.result.__class__.__name__
tags = self.env.tags
attrs = self.attrs
if exprType in self.floatTypes:
attrs[tags['value-type']] = 'float'
attrs[tags['value']] = str(expr.result)
elif exprType in self.dateTypes:
attrs[tags['value-type']] = 'date'
attrs[tags['value']] = expr.result.strftime('%Y-%m-%d')
else:
attrs[tags['value-type']] = 'string'
def evaluate(self, context):
# Evaluate first the tied expression, in order to determine its type.
try:
self.tiedExpression.evaluate(context)
self.evaluated = True
except Exception, e:
# Don't set "evaluated" to True. This way, when the buffer will
# evaluate the expression directly, we will really evaluate it, so
# the error will be dumped into the pod result.
pass
# Analyse the return type of the expression.
self.computeAttributes(self.tiedExpression)
# Now, self.attrs has been populated. Transform it into a string.
res = ''
for name, value in self.attrs.iteritems():
res += ' %s=%s' % (name, quoteattr(value))
return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -110,6 +110,7 @@ class PodEnvironment(OdfEnvironment):
self.namedIfActions = {} #~{s_statementName: IfAction}~ self.namedIfActions = {} #~{s_statementName: IfAction}~
# Currently parsed expression within an ODS template # Currently parsed expression within an ODS template
self.currentOdsExpression = None self.currentOdsExpression = None
self.currentOdsHook = None
# Names of some tags, that we will compute after namespace propagation # Names of some tags, that we will compute after namespace propagation
self.tags = None self.tags = None
@ -204,6 +205,7 @@ class PodEnvironment(OdfEnvironment):
'table-cell': '%s:table-cell' % ns[self.NS_TABLE], 'table-cell': '%s:table-cell' % ns[self.NS_TABLE],
'formula': '%s:formula' % ns[self.NS_TABLE], 'formula': '%s:formula' % ns[self.NS_TABLE],
'value-type': '%s:value-type' % ns[self.NS_OFFICE], 'value-type': '%s:value-type' % ns[self.NS_OFFICE],
'value': '%s:value' % ns[self.NS_OFFICE],
'string-value': '%s:string-value' % ns[self.NS_OFFICE], 'string-value': '%s:string-value' % ns[self.NS_OFFICE],
'span': '%s:span' % ns[self.NS_TEXT], 'span': '%s:span' % ns[self.NS_TEXT],
'number-columns-spanned': '%s:number-columns-spanned' % \ 'number-columns-spanned': '%s:number-columns-spanned' % \
@ -246,10 +248,12 @@ class PodParser(OdfParser):
e.exprHasStyle = False e.exprHasStyle = False
elif (elem == e.tags['table-cell']) and \ elif (elem == e.tags['table-cell']) and \
attrs.has_key(e.tags['formula']) and \ attrs.has_key(e.tags['formula']) and \
(attrs[e.tags['value-type']] == 'string'): (attrs[e.tags['value-type']] == 'string') and \
attrs[e.tags['formula']].startswith('of:="'):
# In an ODS template, any cell containing a formula of type "string" # In an ODS template, any cell containing a formula of type "string"
# is considered to contain a POD expression. But here it is a # and whose content is expressed as a string between double quotes
# special case: we need to dump the cell; the expression is not # (="...") is considered to contain a POD expression. But here it
# is a special case: we need to dump the cell; the expression is not
# directly contained within this cell; the expression will be # directly contained within this cell; the expression will be
# contained in the next inner paragraph. So we must here dump the # contained in the next inner paragraph. So we must here dump the
# cell, but without some attributes, because the "formula" will be # cell, but without some attributes, because the "formula" will be
@ -257,10 +261,13 @@ class PodParser(OdfParser):
if e.mode == e.ADD_IN_SUBBUFFER: if e.mode == e.ADD_IN_SUBBUFFER:
e.addSubBuffer() e.addSubBuffer()
e.currentBuffer.addElement(e.currentElem.name) e.currentBuffer.addElement(e.currentElem.name)
e.currentBuffer.dumpStartElement(elem, attrs, hook = e.currentBuffer.dumpStartElement(elem, attrs,
ignoreAttrs=(e.tags['formula'], e.tags['string-value'])) ignoreAttrs=(e.tags['formula'], e.tags['string-value'],
e.tags['value-type']),
insertAttributesHook=True)
# We already have the POD expression: remember it on the env. # We already have the POD expression: remember it on the env.
e.currentOdsExpression = attrs[e.tags['string-value']] e.currentOdsExpression = attrs[e.tags['string-value']]
e.currentOdsHook = hook
else: else:
if e.state == e.IGNORING: if e.state == e.IGNORING:
pass pass
@ -304,8 +311,10 @@ class PodParser(OdfParser):
elif e.state == e.READING_CONTENT: elif e.state == e.READING_CONTENT:
# Dump the ODS POD expression if any # Dump the ODS POD expression if any
if e.currentOdsExpression: if e.currentOdsExpression:
e.currentBuffer.addExpression(e.currentOdsExpression) e.currentBuffer.addExpression(e.currentOdsExpression,
tiedHook=e.currentOdsHook)
e.currentOdsExpression = None e.currentOdsExpression = None
e.currentOdsHook = None
# Dump the ending tag # Dump the ending tag
e.currentBuffer.dumpEndElement(elem) e.currentBuffer.dumpEndElement(elem)
if elem in e.impactableElements: if elem in e.impactableElements:

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.