More input and output formats for OO conversion in converter.py and bugfix in skyn edit.

This commit is contained in:
Gaetan Delannay 2009-12-17 21:14:52 +01:00
parent fff2b6a329
commit e89eda4838
7 changed files with 190 additions and 113 deletions

View file

@ -355,7 +355,7 @@ class Ref(Type):
class Computed(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True,
default=None, optional=False, editDefault=False, show='view',
page='main', group=None, move=0, indexed=False,
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None,

View file

@ -33,9 +33,13 @@ class AbstractMixin:
# creates the final object from the temp object.
previousData = None
if not created: previousData = self.rememberPreviousData()
# We do not process form data (=real update on the object) if the tool
# itself is being created.
if obj._appy_meta_type != 'tool': obj.processForm()
# Perform the change on the object, unless self is a tool being created.
if (obj._appy_meta_type == 'tool') and created:
# We do not process form data (=real update on the object) if the
# tool itself is being created.
pass
else:
obj.processForm()
if previousData:
# Keep in history potential changes on historized fields
self.historizeData(previousData)
@ -389,7 +393,7 @@ class AbstractMixin:
fieldDescr = fieldDescr.__dict__
appyType = fieldDescr['appyType']
if isEdit and (appyType['type']=='Ref') and appyType['add']:return False
if isEdit and (appyType['type'] in ('Action', 'Computed')): return False
if isEdit and (appyType['type'] == 'Action'): return False
if (fieldDescr['widgetType'] == 'backField') and \
not self.getBRefs(fieldDescr['fieldRel']): return False
# Do not show field if it is optional and not selected in flavour

View file

@ -3,23 +3,19 @@
errors request/errors | python:{};
Iterator python:modules['Products.Archetypes'].IndexIterator;
schematas contextObj/Schemata;
fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(here, visible_only=True))];
fieldsets python:[key for key in schematas.keys() if (key != 'metadata') and (schematas[key].editableFields(contextObj, visible_only=True))];
default_fieldset python:(not schematas or schematas.has_key('default')) and 'default' or fieldsets[0];
fieldset request/fieldset|options/fieldset|default_fieldset;
fields python:schematas[fieldset].editableFields(here);
dummy python:here.at_isEditable(fields);
portal_type python:here.getPortalTypeName().lower().replace(' ', '_');
type_name here/getPortalTypeName|here/archetype_name;
lockable python:hasattr(here, 'wl_isLocked');
isLocked python:lockable and here.wl_isLocked();
tabindex tabindex|python:Iterator(pos=7000);
fields python:schematas[fieldset].editableFields(contextObj);
lockable python:hasattr(contextObj, 'wl_isLocked');
isLocked python:lockable and contextObj.wl_isLocked();
isEdit python:True;
tool contextObj/getTool;
flavour python: tool.getFlavour(contextObj);
appFolder tool/getAppFolder;
appName appFolder/id;
css python:here.getUniqueWidgetAttr(fields, 'helper_css');
js python:here.getUniqueWidgetAttr(fields, 'helper_js');
css python:contextObj.getUniqueWidgetAttr(fields, 'helper_css');
js python:contextObj.getUniqueWidgetAttr(fields, 'helper_js');
phaseInfo python: contextObj.getAppyPhases(fieldset=fieldset, forPlone=True);
phase request/phase|phaseInfo/name;
pageName python: contextObj.getAppyPage(isEdit, phaseInfo);">
@ -91,24 +87,20 @@
<tal:previousButton condition="python: pageIndex &gt; 0">
<input class="context" type="submit" name="buttonPrevious"
i18n:attributes="value label_previous;" i18n:domain="plone"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"/>
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input type="hidden" name="previousPage" tal:attributes="value python: pages[pageIndex-1]"/>
</tal:previousButton>
<tal:nextButton condition="python: pageIndex &lt; numberOfPages - 1">
<input class="context" type="submit" name="buttonNext" value="Next"
i18n:attributes="value label_next;" i18n:domain="plone"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"/>
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input type="hidden" name="nextPage" tal:attributes="value python: pages[pageIndex+1]"/>
</tal:nextButton>
<input class="context" type="submit" name="buttonOk"
i18n:attributes="value label_save;" i18n:domain="plone"
tal:attributes="tabindex tabindex/next;
disabled python:test(isLocked, 'disabled', None);"/>
tal:attributes="disabled python:test(isLocked, 'disabled', None);"/>
<input class="standalone" type="submit" name="buttonCancel"
i18n:attributes="value label_cancel;" i18n:domain="plone"
tal:attributes="tabindex tabindex/next"/>
i18n:attributes="value label_cancel;" i18n:domain="plone"/>
</metal:block>
</form>
<div metal:use-macro="here/skyn/macros/macros/showPageFooter"/>

View file

@ -202,7 +202,7 @@
<metal:editRef use-macro="here/skyn/ref/macros/editReference" />
</tal:ref>
</tal:editRef>
<tal:computedField condition="python: (not isEdit) and (appyType['type'] == 'Computed')">
<tal:computedField condition="python: appyType['type'] == 'Computed'">
<metal:cf use-macro="here/skyn/macros/macros/showComputedField" />
</tal:computedField>
<tal:actionField condition="python: appyType['type'] == 'Action'">

View file

@ -2,7 +2,8 @@
developer the real classes used by the underlying web framework.'''
# ------------------------------------------------------------------------------
import time, os.path, mimetypes, unicodedata, random
import os, os.path, time, mimetypes, unicodedata, random
import appy.pod
from appy.gen import Search
from appy.gen.utils import sequenceTypes
from appy.shared.utils import getOsTempFolder
@ -236,15 +237,23 @@ class AbstractWrapper:
wfTool.doActionFor(self.o, transitionName, comment=comment)
del self.o._v_appy_do
def log(self, message, logLevel='info'):
def log(self, message, type='info'):
'''Logs a message in the log file. p_logLevel may be "info", "warning"
or "error".'''
logger = self.o.getProductConfig().logger
if logLevel == 'warning': logMethod = logger.warn
elif logLevel == 'error': logMethod = logger.error
if type == 'warning': logMethod = logger.warn
elif type == 'error': logMethod = logger.error
else: logMethod = logger.info
logMethod(message)
def say(self, message, type='info'):
'''Prints a message in the user interface. p_logLevel may be "info",
"warning" or "error".'''
mType = type
if mType == 'warning': mType = 'warn'
elif mType == 'error': mType = 'stop'
self.o.plone_utils.addPortalMessage(message, type=mType)
def normalize(self, s):
'''Returns a version of string p_s whose special chars have been
replaced with normal chars.'''
@ -338,11 +347,16 @@ class FileWrapper:
raise 'Impossible to set attribute %s. "Settable" attributes ' \
'are "name", "content" and "mimeType".' % name
def dump(self, filePath=None):
def dump(self, filePath=None, format=None, tool=None):
'''Writes the file on disk. If p_filePath is specified, it is the
path name where the file will be dumped; folders mentioned in it
must exist. If not, the file will be dumped in the OS temp folder.
The absolute path name of the dumped file is returned.'''
The absolute path name of the dumped file is returned.
If an error occurs, the method returns None. If p_format is
specified, OpenOffice will be called for converting the dumped file
to the desired format. In this case, p_tool, a Appy tool, must be
provided. Indeed, any Appy tool contains parameters for contacting
OpenOffice in server mode.'''
if not filePath:
filePath = '%s/file%f.%s' % (getOsTempFolder(), time.time(),
self.name)
@ -358,5 +372,20 @@ class FileWrapper:
# Only one chunk
f.write(self.content)
f.close()
if format:
if not tool: return
# Convert the dumped file using OpenOffice
convScript = '%s/converter.py' % os.path.dirname(appy.pod.__file__)
cmd = '%s %s "%s" %s -p%d' % (tool.unoEnabledPython, convScript,
filePath, format, tool.openOfficePort)
res = os.system(cmd)
os.remove(filePath)
if res != 0: return
# Return the name of the converted file.
baseName, ext = os.path.splitext(filePath)
if (ext == '.%s' % format):
filePath = '%s.res.%s' % (baseName, format)
else:
filePath = '%s.%s' % (baseName, format)
return filePath
# ------------------------------------------------------------------------------

View file

@ -20,15 +20,26 @@
import sys, os, os.path, time, signal
from optparse import OptionParser
ODT_FILE_TYPES = {'doc': 'MS Word 97', # Could be 'MS Word 2003 XML'
'pdf': 'writer_pdf_Export',
htmlFilters = {'odt': 'HTML (StarWriter)',
'ods': 'HTML (StarCalc)',
'odp': 'impress_html_Export'}
FILE_TYPES = {'odt': 'writer8',
'ods': 'calc8',
'odp': 'impress8',
'htm': htmlFilters, 'html': htmlFilters,
'rtf': 'Rich Text Format',
'txt': 'Text',
'html': 'HTML (StarWriter)',
'htm': 'HTML (StarWriter)',
'odt': 'ODT'}
# Conversion to ODT does not make any conversion; it simply updates indexes and
# linked documents.
'csv': 'Text - txt - csv (StarCalc)',
'pdf': {'odt': 'writer_pdf_Export', 'ods': 'calc_pdf_Export',
'odp': 'impress_pdf_Export', 'odg': 'draw_pdf_Export'},
'swf': 'impress_flash_Export',
'doc': 'MS Word 97',
'xls': 'MS Excel 97',
'ppt': 'MS PowerPoint 97',
}
# Conversion from odt to odt does not make any conversion, but updates indexes
# and linked documents.
# ------------------------------------------------------------------------------
class ConverterError(Exception): pass
@ -46,7 +57,7 @@ DEFAULT_PORT = 2002
# ------------------------------------------------------------------------------
class Converter:
'''Converts an ODT document into pdf, doc, txt or rtf.'''
'''Converts an document readable by OpenOffice into pdf, doc, txt or rtf.'''
exeVariants = ('soffice.exe', 'soffice')
pathReplacements = {'program files': 'progra~1',
'openoffice.org 1': 'openof~1',
@ -54,41 +65,64 @@ class Converter:
}
def __init__(self, docPath, resultType, port=DEFAULT_PORT):
self.port = port
self.docUrl, self.docPath = self.getDocUrls(docPath)
self.resultFilter = self.getResultFilter(resultType)
self.resultUrl = self.getResultUrl(resultType)
self.docUrl, self.docPath = self.getInputUrls(docPath)
self.inputType = os.path.splitext(docPath)[1][1:].lower()
self.resultType = resultType
self.resultFilter = self.getResultFilter()
self.resultUrl = self.getResultUrl()
self.ooContext = None
self.oo = None # OpenOffice application object
self.doc = None # OpenOffice loaded document
def getDocUrls(self, docPath):
self.oo = None # The OpenOffice application object
self.doc = None # The OpenOffice loaded document
def getInputUrls(self, docPath):
'''Returns the absolute path of the input file. In fact, it returns a
tuple with some URL version of the path for OO as the first element
and the absolute path as the second element.'''
import uno
if not os.path.exists(docPath) and not os.path.isfile(docPath):
raise ConverterError(DOC_NOT_FOUND % docPath)
docAbsPath = os.path.abspath(docPath)
# Return one path for OO, one path for me.
return uno.systemPathToFileUrl(docAbsPath), docAbsPath
def getResultFilter(self, resultType):
if ODT_FILE_TYPES.has_key(resultType):
res = ODT_FILE_TYPES[resultType]
def getResultFilter(self):
'''Based on the result type, identifies which OO filter to use for the
document conversion.'''
if FILE_TYPES.has_key(self.resultType):
res = FILE_TYPES[self.resultType]
if isinstance(res, dict):
res = res[self.inputType]
else:
raise ConverterError(BAD_RESULT_TYPE % (resultType,
ODT_FILE_TYPES.keys()))
raise ConverterError(BAD_RESULT_TYPE % (self.resultType,
FILE_TYPES.keys()))
return res
def getResultUrl(self, resultType):
def getResultUrl(self):
'''Returns the path of the result file in the format needed by OO. If
the result type and the input type are the same (ie the user wants to
refresh indexes or some other action and not perform a real
conversion), the result file is named
<inputFileName>.res.<resultType>.
Else, the result file is named like the input file but with a
different extension:
<inputFileName>.<resultType>
'''
import uno
baseName = os.path.splitext(self.docPath)[0]
if resultType != 'odt':
res = '%s.%s' % (baseName, resultType)
if self.resultType != self.inputType:
res = '%s.%s' % (baseName, self.resultType)
else:
res = '%s.res.%s' % (baseName, resultType)
res = '%s.res.%s' % (baseName, self.resultType)
try:
f = open(res, 'w')
f.write('Hello')
f.close()
os.remove(res)
return uno.systemPathToFileUrl(res)
except OSError, oe:
raise ConverterError(CANNOT_WRITE_RESULT % (res, oe))
except (OSError, IOError), ioe:
raise ConverterError(CANNOT_WRITE_RESULT % (res, ioe))
def connect(self):
'''Connects to OpenOffice'''
if os.name == 'nt':
@ -115,30 +149,18 @@ class Converter:
'com.sun.star.frame.Desktop', self.ooContext)
except NoConnectException, nce:
raise ConverterError(CONNECT_ERROR % (self.port, nce))
def disconnect(self):
self.doc.close(True)
# Do a nasty thing before exiting the python process. In case the
# last call is a oneway call (e.g. see idl-spec of insertString),
# it must be forced out of the remote-bridge caches before python
# exits the process. Otherwise, the oneway call may or may not reach
# the target object.
# I do this here by calling a cheap synchronous call (getPropertyValue).
self.ooContext.ServiceManager
def loadDocument(self):
from com.sun.star.lang import IllegalArgumentException, \
IndexOutOfBoundsException
def updateOdtDocument(self):
'''If the input file is an ODT document, we will perform 2 tasks:
1) Update all annexes;
2) Update sections (if sections refer to external content, we try to
include the content within the result file)
'''
from com.sun.star.lang import IndexOutOfBoundsException
# I need to use IndexOutOfBoundsException because sometimes, when
# using sections.getCount, UNO returns a number that is bigger than
# the real number of sections (this is because it also counts the
# sections that are present within the sub-documents to integrate)
from com.sun.star.beans import PropertyValue
try:
# Load the document to convert in a new hidden frame
prop = PropertyValue()
prop.Name = 'Hidden'
prop.Value = True
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
(prop,))
# Update all indexes
indexes = self.doc.getDocumentIndexes()
indexesCount = indexes.getCount()
@ -165,23 +187,52 @@ class Converter:
# of the section. Else, it won't appear.
except IndexOutOfBoundsException:
pass
def loadDocument(self):
from com.sun.star.lang import IllegalArgumentException, \
IndexOutOfBoundsException
from com.sun.star.beans import PropertyValue
try:
# Loads the document to convert in a new hidden frame
prop = PropertyValue()
prop.Name = 'Hidden'
prop.Value = True
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
(prop,))
if self.inputType == 'odt':
# Perform additional tasks for odt documents
self.updateOdtDocument()
try:
self.doc.refresh()
except AttributeError:
pass
except IllegalArgumentException, iae:
raise ConverterError(URL_NOT_FOUND % (self.docPath, iae))
def convertDocument(self):
if self.resultFilter != 'ODT':
# I must really perform a conversion
'''Calls OO to perform a document conversion. Note that the conversion
is not really done if the source and target documents have the same
type.'''
properties = []
from com.sun.star.beans import PropertyValue
prop = PropertyValue()
prop.Name = 'FilterName'
prop.Value = self.resultFilter
self.doc.storeToURL(self.resultUrl, (prop,))
else:
self.doc.storeToURL(self.resultUrl, ())
properties.append(prop)
if self.resultType == 'csv':
# For CSV export, add options (separator, etc)
optionsProp = PropertyValue()
optionsProp.Name = 'FilterOptions'
optionsProp.Value = '59,34,76,1'
properties.append(optionsProp)
self.doc.storeToURL(self.resultUrl, tuple(properties))
def run(self):
'''Connects to OO, does the job and disconnects.'''
self.connect()
self.loadDocument()
self.convertDocument()
self.disconnect()
self.doc.close(True)
# ConverterScript-related messages ---------------------------------------------
WRONG_NB_OF_ARGS = 'Wrong number of arguments.'
@ -191,12 +242,13 @@ ERROR_CODE = 1
class ConverterScript:
usage = 'usage: python converter.py fileToConvert outputType [options]\n' \
' where fileToConvert is the absolute or relative pathname of\n' \
' the ODT file you want to convert;\n'\
' the file you want to convert (or whose content like\n' \
' indexes need to be refreshed);\n'\
' and outputType is the output format, that must be one of\n' \
' %s.\n' \
' "python" should be a UNO-enabled Python interpreter (ie the one\n' \
' which is included in the OpenOffice.org distribution).' % \
str(ODT_FILE_TYPES.keys())
' "python" should be a UNO-enabled Python interpreter (ie the ' \
' one which is included in the OpenOffice.org distribution).' % \
str(FILE_TYPES.keys())
def run(self):
optParser = OptionParser(usage=ConverterScript.usage)
optParser.add_option("-p", "--port", dest="port",

View file

@ -25,7 +25,7 @@ import appy.pod
from appy.pod import PodError
from appy.shared.xml_parser import XmlElement
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
from appy.pod.converter import ODT_FILE_TYPES
from appy.pod.converter import FILE_TYPES
from appy.pod.buffers import FileBuffer
from appy.pod.xhtml2odt import Xhtml2OdtConverter
from appy.pod.doc_importers import OdtImporter, ImageImporter, PdfImporter
@ -423,9 +423,9 @@ class Renderer:
os.rename(resultOdtName, self.result)
else:
if resultType.startswith('.'): resultType = resultType[1:]
if not resultType in ODT_FILE_TYPES.keys():
if not resultType in FILE_TYPES.keys():
raise PodError(BAD_RESULT_TYPE % (
self.result, ODT_FILE_TYPES.keys()))
self.result, FILE_TYPES.keys()))
# Call OpenOffice to perform the conversion or document update
self.callOpenOffice(resultOdtName, resultType)
# I have the result. Move it to the correct name