Added the possibility to define POD templates for any search result (Pod field with param view='search'), bugfix while getting default value for a Ref field, added Computed fields that computes a ZPT macro given as a string to param 'method', added the possibility to define a global style mapping for every Pod field, stopped to generate a field-specific set of i18n labels for pod output formats, carry portal_status_message even through page redirections, added 'deprecatedAddRemove' tags in generated configure.zcml, onEdit can now return a customized message, added possibility to normalize strings for other usages than 'fileName', in appy.shared.utils.normalizeString (for alpha and alphanum usages)

This commit is contained in:
Gaetan Delannay 2011-01-28 14:36:30 +01:00
parent 38f71be89a
commit 90553381a3
18 changed files with 250 additions and 59 deletions

View file

@ -506,7 +506,8 @@ class Type:
search results (p_usage="search") or when sorting reference fields
(p_usage="ref")?'''
if usage == 'search':
return self.indexed and not self.isMultiValued()
return self.indexed and not self.isMultiValued() and not \
((self.type == 'String') and self.isSelection())
elif usage == 'ref':
return self.type in ('Integer', 'Float', 'Boolean', 'Date') or \
((self.type == 'String') and (self.format == 0))
@ -514,7 +515,6 @@ class Type:
def isShowable(self, obj, layoutType):
'''When displaying p_obj on a given p_layoutType, must we show this
field?'''
isEdit = layoutType == 'edit'
# Do not show field if it is optional and not selected in tool
if self.optional:
tool = obj.getTool().appy()
@ -524,7 +524,7 @@ class Type:
return False
# Check if the user has the permission to view or edit the field
user = obj.portal_membership.getAuthenticatedMember()
if isEdit:
if layoutType == 'edit':
perm = self.writePermission
else:
perm = self.readPermission
@ -535,14 +535,9 @@ class Type:
res = self.callMethod(obj, self.show)
else:
res = self.show
# Take into account possible values 'view' and 'edit' for 'show' param.
if res == 'view':
if isEdit: res = False
else: res = True
elif res == 'edit':
if isEdit: res = True
else: res = False
return res
# Take into account possible values 'view', 'edit', 'search'...
if res in ('view', 'edit', 'result'): return res == layoutType
return bool(res)
def isClientVisible(self, obj):
'''This method returns True if this field is visible according to
@ -1641,6 +1636,16 @@ class Ref(Type):
toDelete.append(uid)
for uid in toDelete:
uids.remove(uid)
if not uids:
# Maybe is there a default value?
defValue = Type.getValue(self, obj)
if defValue:
# I must prefix call to function "type" with "__builtins__"
# because this name was overridden by a method parameter.
if __builtins__['type'](defValue) in sequenceTypes:
uids = [o.o.UID() for o in defValue]
else:
uids = [defValue.o.UID()]
# Prepare the result: an instance of SomeObjects, that, in this case,
# represent a subset of all referred objects
res = SomeObjects()
@ -1751,6 +1756,10 @@ class Computed(Type):
self.method = method
# Does field computation produce plain text or XHTML?
self.plainText = plainText
if isinstance(method, basestring):
# When field computation is done with a macro, we know the result
# will be HTML.
self.plainText = False
Type.__init__(self, None, multiplicity, index, default, optional,
False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
@ -1758,9 +1767,29 @@ class Computed(Type):
sync)
self.validable = False
def callMacro(self, obj, macroPath):
'''Returns the macro corresponding to p_macroPath. The base folder
where we search is "skyn".'''
# Get the special page in Appy that allows to call a macro
macroPage = obj.skyn.callMacro
# Get, from p_macroPath, the page where the macro lies, and the macro
# name.
names = self.method.split('/')
# Get the page where the macro lies
page = obj.skyn
for name in names[:-1]:
page = getattr(page, name)
macroName = names[-1]
return macroPage(obj, contextObj=obj, page=page, macroName=macroName)
def getValue(self, obj):
'''Computes the value instead of getting it in the database.'''
if not self.method: return
if isinstance(self.method, basestring):
# self.method is a path to a macro that will produce the field value
return self.callMacro(obj, self.method)
else:
# self.method is a method that will return the field value
return self.callMethod(obj, self.method, raiseOnError=False)
def getFormattedValue(self, obj, value):
@ -1864,7 +1893,7 @@ class Pod(Type):
specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False,
historized=False, template=None, context=None, action=None,
askAction=False):
askAction=False, stylesMapping=None):
# The following param stores the path to a POD template
self.template = template
# The context is a dict containing a specific pod context, or a method
@ -1876,6 +1905,8 @@ class Pod(Type):
# If askAction is True, the action will be triggered only if the user
# checks a checkbox, which, by default, will be unchecked.
self.askAction = askAction
# A global styles mapping that would apply to the whole template
self.stylesMapping = stylesMapping
Type.__init__(self, None, (0,1), index, default, optional,
False, show, page, group, layouts, move, indexed,
searchable, specificReadPermission,

View file

@ -434,7 +434,7 @@ class ToolClassDescriptor(ClassDescriptor):
self.addField(fieldName, fieldType)
# Add the field that will store the output format(s)
fieldName = 'formatsFor%s_%s' % (className, fieldDescr.fieldName)
fieldType = String(validator=('odt', 'pdf', 'doc', 'rtf'),
fieldType = String(validator=Selection('getPodOutputFormats'),
multiplicity=(1,None), default=('odt',), **pg)
self.addField(fieldName, fieldType)

View file

@ -18,8 +18,14 @@ COMMON_METHODS = '''
def getTool(self): return self.%s
def getProductConfig(self): return Products.%s.config
def skynView(self):
"""Redirects to skyn/view."""
return self.REQUEST.RESPONSE.redirect(self.getUrl())
"""Redirects to skyn/view. Transfers the status message if any."""
rq = self.REQUEST
msg = rq.get('portal_status_message', '')
if msg:
url = self.getUrl(portal_status_message=msg)
else:
url = self.getUrl()
return rq.RESPONSE.redirect(url)
'''
# ------------------------------------------------------------------------------
class Generator(AbstractGenerator):
@ -140,6 +146,10 @@ class Generator(AbstractGenerator):
msg('field_invalid', '', msg.FIELD_INVALID),
msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED),
msg('odt', '', msg.FORMAT_ODT),
msg('pdf', '', msg.FORMAT_PDF),
msg('doc', '', msg.FORMAT_DOC),
msg('rtf', '', msg.FORMAT_RTF),
]
# Create a label for every role added by this application
for role in self.getAllUsedRoles():
@ -153,7 +163,7 @@ class Generator(AbstractGenerator):
if self.config.frontPage:
self.generateFrontPage()
self.copyFile('Install.py', self.repls, destFolder='Extensions')
self.copyFile('configure.zcml', self.repls)
self.generateConfigureZcml()
self.copyFile('import_steps.xml', self.repls,
destFolder='profiles/default')
self.copyFile('ProfileInit.py', self.repls, destFolder='profiles',
@ -304,6 +314,17 @@ class Generator(AbstractGenerator):
if isBack: res += '.back'
return res
def generateConfigureZcml(self):
'''Generates file configure.zcml.'''
repls = self.repls.copy()
# Note every class as "deprecated".
depr = ''
for klass in self.getClasses(include='all'):
depr += '<five:deprecatedManageAddDelete class=".%s.%s"/>\n' % \
(klass.name, klass.name)
repls['deprecated'] = depr
self.copyFile('configure.zcml', repls)
def generateConfig(self):
repls = self.repls.copy()
# Get some lists of classes

View file

@ -45,6 +45,7 @@ class ToolMixin(BaseMixin):
res['title'] = self.translate(appyType.labelId)
res['context'] = appyType.context
res['action'] = appyType.action
res['stylesMapping'] = appyType.stylesMapping
return res
def getSiteUrl(self):
@ -68,7 +69,7 @@ class ToolMixin(BaseMixin):
template = podInfo['template'].content
podTitle = podInfo['title']
if podInfo['context']:
if type(podInfo['context']) == types.FunctionType:
if callable(podInfo['context']):
specificPodContext = podInfo['context'](appyObj)
else:
specificPodContext = podInfo['context']
@ -76,16 +77,38 @@ class ToolMixin(BaseMixin):
# Temporary file where to generate the result
tempFileName = '%s/%s_%f.%s' % (
getOsTempFolder(), obj.UID(), time.time(), format)
# Define parameters to pass to the appy.pod renderer
# Define parameters to give to the appy.pod renderer
currentUser = self.portal_membership.getAuthenticatedMember()
podContext = {'tool': appyTool, 'user': currentUser, 'self': appyObj,
'now': self.getProductConfig().DateTime(),
'projectFolder': appyTool.getDiskFolder(),
}
# If the POD document is related to a query, get it from the request,
# execute it and put the result in the context.
if rq['queryData']:
# Retrieve query params from the request
cmd = ', '.join(self.queryParamNames)
cmd += " = rq['queryData'].split(';')"
exec cmd
# (re-)execute the query, but without any limit on the number of
# results; return Appy objects.
objs = self.executeQuery(type_name, searchName=search,
sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey,
filterValue=filterValue, maxResults='NO_LIMIT')
podContext['objects'] = [o.appy() for o in objs['objects']]
if specificPodContext:
podContext.update(specificPodContext)
# Define a potential global styles mapping
stylesMapping = None
if podInfo['stylesMapping']:
if callable(podInfo['stylesMapping']):
stylesMapping = podInfo['stylesMapping'](appyObj)
else:
stylesMapping = podInfo['stylesMapping']
rendererParams = {'template': StringIO.StringIO(template),
'context': podContext, 'result': tempFileName}
if stylesMapping:
rendererParams['stylesMapping'] = stylesMapping
if appyTool.unoEnabledPython:
rendererParams['pythonWithUnoPath'] = appyTool.unoEnabledPython
if appyTool.openOfficePort:
@ -105,7 +128,13 @@ class ToolMixin(BaseMixin):
f = file(tempFileName, 'rb')
res = f.read()
# Identify the filename to return
fileName = u'%s-%s' % (obj.Title().decode('utf-8'), podTitle)
if rq['queryData']:
# This is a POD for a bunch of objects
fileName = podTitle
else:
# This is a POD for a single object: personalize the file name with
# the object title.
fileName = '%s-%s' % (obj.Title(), podTitle)
fileName = appyTool.normalize(fileName)
response = obj.REQUEST.RESPONSE
response.setHeader('Content-Type', mimeTypes[format])
@ -189,6 +218,19 @@ class ToolMixin(BaseMixin):
return {'fields': fields, 'nbOfColumns': nbOfColumns,
'fieldDicts': fieldDicts}
queryParamNames = ('type_name', 'search', 'sortKey', 'sortOrder',
'filterKey', 'filterValue')
def getQueryInfo(self):
'''If we are showing search results, this method encodes in a string all
the params in the request that are required for re-triggering the
search.'''
rq = self.REQUEST
res = ''
if rq.has_key('search'):
res = ';'.join([rq.get(key,'').replace(';','') \
for key in self.queryParamNames])
return res
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
p_contentType.'''
@ -863,4 +905,10 @@ class ToolMixin(BaseMixin):
os.remove(fileName)
return content
return 'File does not exist'
def getResultPodFields(self, contentType):
'''Finds, among fields defined on p_contentType, which ones are Pod
fields that need to be shown on a page displaying query results.'''
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')]
# ------------------------------------------------------------------------------

View file

@ -3,7 +3,7 @@
- mixins/ToolMixin is mixed in with the generated application Tool class.'''
# ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes
import os, os.path, sys, types, mimetypes, urllib
import appy.gen
from appy.gen import Type, String, Selection, Role
from appy.gen.utils import *
@ -67,13 +67,15 @@ class BaseMixin:
initiator.appy().link(fieldName, obj)
# Call the custom "onEdit" if available
msg = None # The message to display to the user. It can be set by onEdit
if obj.wrapperClass:
appyObject = obj.appy()
if hasattr(appyObject, 'onEdit'): appyObject.onEdit(created)
if hasattr(appyObject, 'onEdit'):
msg = appyObject.onEdit(created)
# Manage "add" permissions and reindex the object
obj._appy_managePermissions()
obj.reindexObject()
return obj
return obj, msg
def delete(self):
'''This methods is self's suicide.'''
@ -219,10 +221,21 @@ class BaseMixin:
return self.skyn.edit(self)
# Create or update the object in the database
obj = self.createOrUpdate(isNew, values)
obj, msg = self.createOrUpdate(isNew, values)
# Redirect the user to the appropriate page
msg = obj.translate('Changes saved.', domain='plone')
if not msg: msg = obj.translate('Changes saved.', domain='plone')
# If the object has already been deleted (ie, it is a kind of transient
# object like a one-shot form and has already been deleted in method
# onEdit), redirect to the main site page.
if not getattr(obj.getParentNode(), obj.id, None):
obj.unindexObject()
return self.goto(tool.getSiteUrl(), msg)
# If the user can't access the object anymore, redirect him to the
# main site page.
user = self.portal_membership.getAuthenticatedMember()
if not user.has_permission('View', obj):
return self.goto(tool.getSiteUrl(), msg)
if rq.get('buttonOk.x', None) or saveConfirmed:
# Go to the consult view for this object
obj.plone_utils.addPortalMessage(msg)
@ -321,8 +334,10 @@ class BaseMixin:
if previousData:
self.addDataChange(previousData)
def goto(self, url, addParams=False):
def goto(self, url, msg=None):
'''Brings the user to some p_url after an action has been executed.'''
if msg:
url += '?' + urllib.urlencode([('portal_status_message',msg)])
return self.REQUEST.RESPONSE.redirect(url)
def showField(self, name, layoutType='view'):

View file

@ -0,0 +1,13 @@
<tal:comment replace="nothing">
This page allows to call any macro from Python code, for example.
</tal:comment>
<tal:call tal:define="macroName options/macroName;
page python: options['page'];
contextObj python: options['contextObj'];
tool python: contextObj.getTool();
layoutType python:'view';
putils python: contextObj.plone_utils;
portal python: contextObj.portal_url.getPortalObject();
portal_url python: contextObj.portal_url();">
<metal:callMacro use-macro="python: page.macros[macroName]"/>
</tal:call>

View file

@ -141,7 +141,7 @@
params['filterValue'] = filterWidget.value;
}
}
askAjaxChunk(hookId,'GET',objectUrl,'macros','queryResult',params);
askAjaxChunk(hookId,'GET',objectUrl, 'result', 'queryResult', params);
}
function askObjectHistory(hookId, objectUrl, startNumber) {
@ -244,12 +244,13 @@
createCookie(cookieId, newState);
}
// Function that allows to generate a document from a pod template.
function generatePodDocument(contextUid, fieldName, podFormat) {
function generatePodDocument(contextUid, fieldName, podFormat, queryData) {
var theForm = document.getElementsByName("podTemplateForm")[0];
theForm.objectUid.value = contextUid;
theForm.fieldName.value = fieldName;
theForm.podFormat.value = podFormat;
theForm.askAction.value = "False";
theForm.queryData.value = queryData;
var askActionWidget = document.getElementById(contextUid + '_' + fieldName);
if (askActionWidget && askActionWidget.checked) {
theForm.askAction.value = "True";
@ -376,6 +377,7 @@
<input type="hidden" name="fieldName"/>
<input type="hidden" name="podFormat"/>
<input type="hidden" name="askAction"/>
<input type="hidden" name="queryData"/>
</form>
</div>
@ -536,6 +538,7 @@
</tal:comment>
<div metal:define-macro="header"
tal:define="showCommonInfo python: layoutType == 'view';
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type);
hasHistory contextObj/hasHistory;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
creator contextObj/Creator"
@ -717,7 +720,7 @@
<metal:message define-macro="message" i18n:domain="plone" >
<tal:comment replace="nothing">Single message from portal_status_message request key</tal:comment>
<div tal:define="msg request/portal_status_message | nothing"
tal:condition="msg" class="portalMessage" tal:content="msg" i18n:translate=""></div>
tal:condition="msg" class="portalMessage" tal:content="structure msg" i18n:translate=""></div>
<tal:comment replace="nothing">Messages added via plone_utils</tal:comment>
<tal:messages define="messages putils/showPortalMessages" condition="messages">

View file

@ -34,9 +34,20 @@
</tal:newSearch>
</legend>
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
<table align="right" cellpadding="0" cellspacing="0"
tal:define="widgets python: tool.getResultPodFields(contentType);
layoutType python:'view'"
tal:condition="python: objs and widgets">
<tr><td tal:define="contextObj python: objs[0]"
tal:repeat="widget widgets">
<metal:pod use-macro="here/skyn/widgets/show/macros/field"/>&nbsp;&nbsp;&nbsp;
</td></tr>
</table>
<table cellpadding="0" cellspacing="0" width="100%"><tr>
<td tal:define="descr python: tool.translate(searchDescr)"
tal:condition="python: searchName and descr">
tal:condition="python: searchName and descr.strip()">
<span class="discreet" tal:content="descr"></span><br/><br/>
</td>
<td align="right" width="25%">

View file

@ -25,8 +25,7 @@
appName appFolder/getId;
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
page request/page|python:'main';
phase phaseInfo/name;
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type)">
phase phaseInfo/name;">
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
<metal:show use-macro="here/skyn/page/macros/show"/>
<metal:footer use-macro="here/skyn/page/macros/footer"/>

View file

@ -9,7 +9,7 @@
</tal:askAction>
<img tal:repeat="podFormat python: tool.getPodInfo(contextObj, name)['formats']"
tal:attributes="src string: $portal_url/skyn/${podFormat}.png;
onClick python: 'generatePodDocument(\'%s\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat);
onClick python: 'generatePodDocument(\'%s\',\'%s\',\'%s\',\'%s\')' % (contextObj.UID(), name, podFormat, tool.getQueryInfo());
title podFormat/capitalize"
style="cursor:pointer"/>
</metal:view>

View file

@ -239,8 +239,9 @@
refUids python: [o.UID() for o in contextObj.getAppyRefs(name)['objects']];
isBeingCreated python: contextObj.isTemporary() or ('/portal_factory/' in contextObj.absolute_url())">
<select tal:attributes="name rname; size widget/height;
multiple python: isMultiple and 'multiple' or ''">
<select tal:attributes="name rname;
size python: test(isMultiple, widget['height'], '');
multiple python: test(isMultiple, 'multiple', '')">
<option tal:condition="not: isMultiple" i18n:translate="choose_a_value"></option>
<tal:ref repeat="refObj allObjects">
<option tal:define="uid python: contextObj.getReferenceUid(refObj)"

View file

@ -6,7 +6,7 @@
layout The layout object that will dictate how object content
will be rendered.
Options:
contextMacro The base on folder containing the macros to call for
contextMacro The base folder containing the macros to call for
rendering the elements within the layout.
Defaults to portal.skyn
</tal:comment>

View file

@ -4,8 +4,7 @@
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="<!applicationName!>">
<!--five:deprecatedManageAddDelete class=".Meeting.Meeting"/-->
<!deprecated!>
<genericsetup:registerProfile name="default"
title="<!applicationName!>" description=""
provides="Products.GenericSetup.interfaces.EXTENSION"

View file

@ -30,6 +30,11 @@ class ToolWrapper(AbstractWrapper):
return NOT_UNO_ENABLED_PYTHON % value
return None
podOutputFormats = ('odt', 'pdf', 'doc', 'rtf')
def getPodOutputFormats(self):
'''Gets the available output formats for POD documents.'''
return [(of, self.translate(of)) for of in self.podOutputFormats]
def getInitiator(self):
'''Retrieves the object that triggered the creation of the object
being currently created (if any).'''

View file

@ -16,8 +16,8 @@ WRONG_FILE_TUPLE = 'This is not the way to set a file. You can specify a ' \
# ------------------------------------------------------------------------------
class AbstractWrapper:
'''Any real web framework object has a companion object that is an instance
of this class.'''
'''Any real Zope object has a companion object that is an instance of this
class.'''
def __init__(self, o): self.__dict__['o'] = o
def __setattr__(self, name, value):
@ -118,6 +118,7 @@ class AbstractWrapper:
# Update the Archetypes reference field.
exec 'objs = self.o.g%s()' % postfix
if not objs: return
if type(objs) not in sequenceTypes: objs = [objs]
# Remove p_obj from existing objects
if type(obj) in sequenceTypes:
for o in obj:
@ -190,14 +191,6 @@ class AbstractWrapper:
appyObj = ploneObj.appy()
# Set object attributes
for attrName, attrValue in kwargs.iteritems():
if isinstance(attrValue, AbstractWrapper):
try:
refAppyType = getattr(appyObj.__class__.__bases__[-1],
attrName)
appyObj.link(attrName, attrValue.o)
except AttributeError, ae:
pass
else:
setattr(appyObj, attrName, attrValue)
if isField:
# Link the object to this one
@ -272,7 +265,6 @@ class AbstractWrapper:
elif mType == 'error': mType = 'stop'
self.o.plone_utils.addPortalMessage(message, type=mType)
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
def normalize(self, s, usage='fileName'):
'''Returns a version of string p_s whose special chars have been
replaced with normal chars.'''

View file

@ -109,6 +109,10 @@ class PoMessage:
FILE_REQUIRED = 'Please select a file.'
FIELD_INVALID = 'Please fill or correct this.'
IMAGE_REQUIRED = 'The uploaded file must be an image.'
FORMAT_ODT = 'ODT'
FORMAT_PDF = 'PDF'
FORMAT_DOC = 'DOC'
FORMAT_RTF = 'RTF'
def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False):

View file

@ -84,8 +84,10 @@ class BufferIterator:
self.remainingElemIndexes = self.buffer.elements.keys()
self.remainingSubBufferIndexes.sort()
self.remainingElemIndexes.sort()
def hasNext(self):
return self.remainingSubBufferIndexes or self.remainingElemIndexes
def next(self):
nextSubBufferIndex = None
if self.remainingSubBufferIndexes:
@ -113,36 +115,45 @@ class BufferIterator:
class Buffer:
'''Abstract class representing any buffer used during rendering.'''
elementRex = re.compile('([\w-]+:[\w-]+)\s*(.*?)>', re.S)
def __init__(self, env, parent):
self.parent = parent
self.subBuffers = {} # ~{i_bufferIndex: Buffer}~
self.env = env
def addSubBuffer(self, subBuffer=None):
if not subBuffer:
subBuffer = MemoryBuffer(self.env, self)
self.subBuffers[self.getLength()] = subBuffer
subBuffer.parent = self
return subBuffer
def removeLastSubBuffer(self):
subBufferIndexes = self.subBuffers.keys()
subBufferIndexes.sort()
lastIndex = subBufferIndexes.pop()
del self.subBuffers[lastIndex]
def write(self, something): pass # To be overridden
def getLength(self): pass # To be overridden
def dumpStartElement(self, elem, attrs={}):
self.write('<%s' % elem)
for name, value in attrs.items():
self.write(' %s="%s"' % (name, value))
self.write('>')
def dumpEndElement(self, elem):
self.write('</%s>' % elem)
def dumpElement(self, elem, content=None, attrs={}):
'''For dumping a whole element at once.'''
self.dumpStartElement(elem, attrs)
if content:
self.dumpContent(content)
self.dumpEndElement(elem)
def dumpContent(self, content):
'''Dumps string p_content into the buffer.'''
for c in content:
@ -150,6 +161,7 @@ class Buffer:
self.write(XML_SPECIAL_CHARS[c])
else:
self.write(c)
def dumpAttribute(self, name, value):
self.write(''' %s="%s" ''' % (name, value))
@ -160,17 +172,21 @@ class FileBuffer(Buffer):
self.result = result
self.content = file(result, 'w')
self.content.write(xmlPrologue)
def getLength(self): return 0
# getLength is used to manage insertions into sub-buffers. But in the case
# of a FileBuffer, we will only have 1 sub-buffer at a time, and we don't
# care about where it will be inserted into the FileBuffer.
def getLength(self): return 0
def write(self, something):
self.content.write(something.encode('utf-8'))
def addExpression(self, expression):
try:
self.dumpContent(Expression(expression).evaluate(self.env.context))
except Exception, e:
PodError.dump(self, EVAL_EXPR_ERROR % (expression, e), dumpTb=False)
def pushSubBuffer(self, subBuffer): pass
# ------------------------------------------------------------------------------
@ -179,24 +195,30 @@ class MemoryBuffer(Buffer):
'(?:\s+(for|if|else|with)\s*(.*))?')
forRex = re.compile('\s*([\w\-_]+)\s+in\s+(.*)')
varRex = re.compile('\s*([\w\-_]+)\s*=\s*(.*)')
def __init__(self, env, parent):
Buffer.__init__(self, env, parent)
self.content = u''
self.elements = {}
self.action = None
def addSubBuffer(self, subBuffer=None):
sb = Buffer.addSubBuffer(self, subBuffer)
self.content += ' ' # To avoid having several subbuffers referenced at
# the same place within this buffer.
return sb
def getFileBuffer(self):
if isinstance(self.parent, FileBuffer):
res = self.parent
else:
res = self.parent.getFileBuffer()
return res
def getLength(self): return len(self.content)
def write(self, thing): self.content += thing
def getIndex(self, podElemName):
res = -1
for index, podElem in self.elements.iteritems():
@ -204,11 +226,13 @@ class MemoryBuffer(Buffer):
if index > res:
res = index
return res
def getMainElement(self):
res = None
if self.elements.has_key(0):
res = self.elements[0]
return res
def isMainElement(self, elem):
res = False
mainElem = self.getMainElement()
@ -221,6 +245,7 @@ class MemoryBuffer(Buffer):
res = False
break
return res
def unreferenceElement(self, elem):
# Find last occurrence of this element
elemIndex = -1
@ -229,6 +254,7 @@ class MemoryBuffer(Buffer):
if (podElem.OD.elem == elem) and (index > elemIndex):
elemIndex = index
del self.elements[elemIndex]
def pushSubBuffer(self, subBuffer):
'''Sets p_subBuffer at the very end of the buffer.'''
subIndex = None
@ -242,6 +268,7 @@ class MemoryBuffer(Buffer):
del self.subBuffers[subIndex]
self.subBuffers[self.getLength()] = subBuffer
self.content += u' '
def transferAllContent(self):
'''Transfer all content to parent.'''
if isinstance(self.parent, FileBuffer):
@ -263,18 +290,21 @@ class MemoryBuffer(Buffer):
MemoryBuffer.__init__(self, self.env, self.parent)
# Change buffer position wrt parent
self.parent.pushSubBuffer(self)
def addElement(self, elem):
newElem = PodElement.create(elem)
self.elements[self.getLength()] = newElem
if isinstance(newElem, Cell) or isinstance(newElem, Table):
newElem.tableInfo = self.env.getTable()
def addExpression(self, expression):
# Create the POD expression
expr = Expression(expression)
expr.expr = expression
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.
def createAction(self, statementGroup):
'''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
@ -363,6 +393,7 @@ class MemoryBuffer(Buffer):
except ParsingError, ppe:
PodError.dump(self, ppe, removeFirstLine=True)
return res
def cut(self, index, keepFirstPart):
'''Cuts this buffer into 2 parts. Depending on p_keepFirstPart, the 1st
(from 0 to index-1) or the second (from index to the end) part of the
@ -418,6 +449,7 @@ class MemoryBuffer(Buffer):
res.write(self.content[:index])
self.content = self.content[index:]
return res
def getElementIndexes(self, expressions=True):
res = []
for index, elem in self.elements.iteritems():
@ -427,6 +459,7 @@ class MemoryBuffer(Buffer):
if condition:
res.append(index)
return res
def transferActionIndependentContent(self, actionElemIndex):
# Manage content to transfer to parent buffer
if actionElemIndex != 0:
@ -451,6 +484,7 @@ class MemoryBuffer(Buffer):
else:
res = self
return res
def getStartIndex(self, removeMainElems):
'''When I must dump the buffer, sometimes (if p_removeMainElems is
True), I must dump only a subset of it. This method returns the start
@ -476,6 +510,7 @@ class MemoryBuffer(Buffer):
else:
res = 0
return res
def getStopIndex(self, removeMainElems):
'''This method returns the stop index of the buffer part I must dump.'''
if removeMainElems:

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------
import os, os.path, sys, traceback, unicodedata, shutil
import os, os.path, re, sys, traceback, unicodedata, shutil
# ------------------------------------------------------------------------------
class FolderDeleter:
@ -151,21 +151,35 @@ def executeCommand(cmd):
# ------------------------------------------------------------------------------
unwantedChars = ('\\', '/', ':', '*', '?', '"', '<', '>', '|', ' ')
alphaRex = re.compile('[a-zA-Z]')
alphanumRex = re.compile('[a-zA-Z0-9]')
def normalizeString(s, usage='fileName'):
'''Returns a version of string p_s whose special chars have been
replaced with normal chars.'''
'''Returns a version of string p_s whose special chars (like accents) have
been replaced with normal chars. Moreover, if p_usage is:
* fileName: it removes any char that can't be part of a file name;
* alphanum: it removes any non-alphanumeric char;
* alpha: it removes any non-letter char.
'''
# We work in unicode. Convert p_s to unicode if not unicode.
if isinstance(s, str): s = s.decode('utf-8')
elif not isinstance(s, unicode): s = unicode(s)
# Remove any special char like accents.
s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore')
# Remove any other char, depending on p_usage.
if usage == 'fileName':
# Remove any char that can't be found within a file name under
# Windows or that could lead to problems with OpenOffice.
res = ''
for char in s:
if char not in unwantedChars:
res += char
s = res
return unicodedata.normalize('NFKD', s).encode("ascii","ignore")
if char not in unwantedChars: res += char
elif usage.startswith('alpha'):
exec 'rex = %sRex' % usage
res = ''
for char in s:
if rex.match(char): res += char
else:
res = s
return res
# ------------------------------------------------------------------------------
typeLetters = {'b': bool, 'i': int, 'j': long, 'f':float, 's':str, 'u':unicode,