[gen] [pod] converter.py: added param '-t' allowing to specify a LibreOffice file whose styles will be imported in the pod result (works only for odt files). Thanks to IMIO. [pod] Pod expressions can now be defined in fields of type 'text-input' (in addition to existing fields 'conditional-text' and track-changed text). Thanks to IMIO. [gen] Added parameter 'stylesTemplate' to the Renderer, allowing to specify a LibreOffice file whose styles will be imported in the pod result. Thanks to IMIO. [bin] Added script odfwalk.py allowing to modify or consult the content of odf files in a folder hierarchy (the script manages the unzip and re-zip of odf files and let a caller script access the unzipped content). [pod] Take into account tag 's'. Thanks to IMIO.
This commit is contained in:
parent
727eec8a91
commit
8168306b57
75
bin/odfwalk.py
Normal file
75
bin/odfwalk.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
'''This script allows to walk (and potentially patch) files (content.xml,
|
||||||
|
styles.xml...) contained within a given ODF file or within all ODF files
|
||||||
|
found in some folder.'''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import sys, os.path, time
|
||||||
|
from appy.shared.zip import unzip, zip
|
||||||
|
from appy.shared.utils import getOsTempFolder, FolderDeleter, executeCommand
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
usage = '''Usage: python odfWalk.py [file|folder] yourScript.
|
||||||
|
|
||||||
|
If *file* is given, it is the path to an ODF file (odt or ods). This single
|
||||||
|
file will be walked.
|
||||||
|
If *folder* is given, we will walk all ODF files found in this folder and
|
||||||
|
sub-folders.
|
||||||
|
|
||||||
|
*yourScript* is the path to a Python script that will be run on every walked
|
||||||
|
file. It will be called with a single arg containing the absolute path to the
|
||||||
|
folder containing the unzipped file content (content.xml, styles.xml...).'''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
class OdfWalk:
|
||||||
|
toUnzip = ('.ods', '.odt')
|
||||||
|
def __init__(self, fileOrFolder, script):
|
||||||
|
self.fileOrFolder = fileOrFolder
|
||||||
|
self.script = script
|
||||||
|
self.tempFolder = getOsTempFolder()
|
||||||
|
|
||||||
|
def walkFile(self, fileName):
|
||||||
|
'''Unzip p_fileName in a temp folder, call self.script, and then re-zip
|
||||||
|
the result.'''
|
||||||
|
print 'Walking %s...' % fileName
|
||||||
|
# Create a temp folder
|
||||||
|
name = 'f%f' % time.time()
|
||||||
|
tempFolder = os.path.join(self.tempFolder, name)
|
||||||
|
os.mkdir(tempFolder)
|
||||||
|
# Unzip the file in it
|
||||||
|
unzip(fileName, tempFolder)
|
||||||
|
# Call self.script
|
||||||
|
py = sys.executable or 'python'
|
||||||
|
cmd = '%s %s %s' % (py, self.script, tempFolder)
|
||||||
|
print ' Running %s...' % cmd,
|
||||||
|
os.system(cmd)
|
||||||
|
# Re-zip the result
|
||||||
|
zip(fileName, tempFolder, odf=True)
|
||||||
|
FolderDeleter.delete(tempFolder)
|
||||||
|
print 'done.'
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if os.path.isfile(self.fileOrFolder):
|
||||||
|
self.walkFile(self.fileOrFolder)
|
||||||
|
elif os.path.isdir(self.fileOrFolder):
|
||||||
|
# Walk all files found in this folder
|
||||||
|
for dir, dirnames, filenames in os.walk(self.fileOrFolder):
|
||||||
|
for name in filenames:
|
||||||
|
if os.path.splitext(name)[1] in self.toUnzip:
|
||||||
|
self.walkFile(os.path.join(dir, name))
|
||||||
|
else:
|
||||||
|
print('%s does not exist.' % self.fileOrFolder)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print(usage)
|
||||||
|
sys.exit()
|
||||||
|
# Warn the user.
|
||||||
|
print 'All the files in %s will be modified. ' \
|
||||||
|
'Are you sure? [y/N] ' % sys.argv[1],
|
||||||
|
response = sys.stdin.readline().strip().lower()
|
||||||
|
if response == 'y':
|
||||||
|
OdfWalk(sys.argv[1], sys.argv[2]).run()
|
||||||
|
else:
|
||||||
|
print 'Canceled.'
|
||||||
|
# ------------------------------------------------------------------------------
|
|
@ -53,7 +53,7 @@ FILE_TYPES = {'odt': 'writer8',
|
||||||
class ConverterError(Exception): pass
|
class ConverterError(Exception): pass
|
||||||
|
|
||||||
# ConverterError-related messages ----------------------------------------------
|
# ConverterError-related messages ----------------------------------------------
|
||||||
DOC_NOT_FOUND = 'Document "%s" was not found.'
|
DOC_NOT_FOUND = '"%s" not found.'
|
||||||
URL_NOT_FOUND = 'Doc URL "%s" is wrong. %s'
|
URL_NOT_FOUND = 'Doc URL "%s" is wrong. %s'
|
||||||
BAD_RESULT_TYPE = 'Bad result type "%s". Available types are %s.'
|
BAD_RESULT_TYPE = 'Bad result type "%s". Available types are %s.'
|
||||||
CANNOT_WRITE_RESULT = 'I cannot write result "%s". %s'
|
CANNOT_WRITE_RESULT = 'I cannot write result "%s". %s'
|
||||||
|
@ -71,9 +71,11 @@ class Converter:
|
||||||
'openoffice.org 1': 'openof~1',
|
'openoffice.org 1': 'openof~1',
|
||||||
'openoffice.org 2': 'openof~1',
|
'openoffice.org 2': 'openof~1',
|
||||||
}
|
}
|
||||||
def __init__(self, docPath, resultType, port=DEFAULT_PORT):
|
def __init__(self, docPath, resultType, port=DEFAULT_PORT,
|
||||||
|
templatePath=None):
|
||||||
self.port = port
|
self.port = port
|
||||||
self.docUrl, self.docPath = self.getInputUrls(docPath)
|
# The path to the document to convert
|
||||||
|
self.docUrl, self.docPath = self.getFilePath(docPath)
|
||||||
self.inputType = os.path.splitext(docPath)[1][1:].lower()
|
self.inputType = os.path.splitext(docPath)[1][1:].lower()
|
||||||
self.resultType = resultType
|
self.resultType = resultType
|
||||||
self.resultFilter = self.getResultFilter()
|
self.resultFilter = self.getResultFilter()
|
||||||
|
@ -81,16 +83,21 @@ class Converter:
|
||||||
self.loContext = None
|
self.loContext = None
|
||||||
self.oo = None # The LibreOffice application object
|
self.oo = None # The LibreOffice application object
|
||||||
self.doc = None # The LibreOffice loaded document
|
self.doc = None # The LibreOffice loaded document
|
||||||
|
# The path to a LibreOffice template (ie, a ".ott" file) from which
|
||||||
|
# styles can be imported
|
||||||
|
self.templateUrl = self.templatePath = None
|
||||||
|
if templatePath:
|
||||||
|
self.templateUrl, self.templatePath = self.getFilePath(templatePath)
|
||||||
|
|
||||||
def getInputUrls(self, docPath):
|
def getFilePath(self, filePath):
|
||||||
'''Returns the absolute path of the input file. In fact, it returns a
|
'''Returns the absolute path of p_filePath. In fact, it returns a
|
||||||
tuple with some URL version of the path for OO as the first element
|
tuple with some URL version of the path for LO as the first element
|
||||||
and the absolute path as the second element.'''
|
and the absolute path as the second element.'''
|
||||||
import unohelper
|
import unohelper
|
||||||
if not os.path.exists(docPath) and not os.path.isfile(docPath):
|
if not os.path.exists(filePath) and not os.path.isfile(filePath):
|
||||||
raise ConverterError(DOC_NOT_FOUND % docPath)
|
raise ConverterError(DOC_NOT_FOUND % filePath)
|
||||||
docAbsPath = os.path.abspath(docPath)
|
docAbsPath = os.path.abspath(filePath)
|
||||||
# Return one path for OO, one path for me.
|
# Return one path for OO, one path for me
|
||||||
return unohelper.systemPathToFileUrl(docAbsPath), docAbsPath
|
return unohelper.systemPathToFileUrl(docAbsPath), docAbsPath
|
||||||
|
|
||||||
def getResultFilter(self):
|
def getResultFilter(self):
|
||||||
|
@ -132,6 +139,18 @@ class Converter:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
raise ConverterError(CANNOT_WRITE_RESULT % (res, e))
|
raise ConverterError(CANNOT_WRITE_RESULT % (res, e))
|
||||||
|
|
||||||
|
def props(self, properties):
|
||||||
|
'''Create a UNO-compliant tuple of properties, from tuple p_properties
|
||||||
|
containing sub-tuples (s_propertyName, value).'''
|
||||||
|
from com.sun.star.beans import PropertyValue
|
||||||
|
res = []
|
||||||
|
for name, value in properties:
|
||||||
|
prop = PropertyValue()
|
||||||
|
prop.Name = name
|
||||||
|
prop.Value = value
|
||||||
|
res.append(prop)
|
||||||
|
return tuple(res)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
'''Connects to LibreOffice'''
|
'''Connects to LibreOffice'''
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
|
@ -161,10 +180,11 @@ class Converter:
|
||||||
raise ConverterError(CONNECT_ERROR % (self.port, e))
|
raise ConverterError(CONNECT_ERROR % (self.port, e))
|
||||||
|
|
||||||
def updateOdtDocument(self):
|
def updateOdtDocument(self):
|
||||||
'''If the input file is an ODT document, we will perform 2 tasks:
|
'''If the input file is an ODT document, we will perform those tasks:
|
||||||
1) Update all annexes;
|
1) update all annexes;
|
||||||
2) Update sections (if sections refer to external content, we try to
|
2) update sections (if sections refer to external content, we try to
|
||||||
include the content within the result file)
|
include the content within the result file);
|
||||||
|
3) load styles from an external template if given.
|
||||||
'''
|
'''
|
||||||
from com.sun.star.lang import IndexOutOfBoundsException
|
from com.sun.star.lang import IndexOutOfBoundsException
|
||||||
# I need to use IndexOutOfBoundsException because sometimes, when
|
# I need to use IndexOutOfBoundsException because sometimes, when
|
||||||
|
@ -197,29 +217,26 @@ class Converter:
|
||||||
# of the section. Else, it won't appear.
|
# of the section. Else, it won't appear.
|
||||||
except IndexOutOfBoundsException:
|
except IndexOutOfBoundsException:
|
||||||
pass
|
pass
|
||||||
|
# Import styles from an external file when required
|
||||||
|
if self.templateUrl:
|
||||||
|
params = self.props(('OverwriteStyles', True),
|
||||||
|
('LoadPageStyles', False))
|
||||||
|
self.doc.StyleFamilies.loadStylesFromURL(self.templateUrl, params)
|
||||||
|
|
||||||
def loadDocument(self):
|
def loadDocument(self):
|
||||||
from com.sun.star.lang import IllegalArgumentException, \
|
from com.sun.star.lang import IllegalArgumentException, \
|
||||||
IndexOutOfBoundsException
|
IndexOutOfBoundsException
|
||||||
from com.sun.star.beans import PropertyValue
|
|
||||||
try:
|
try:
|
||||||
# Loads the document to convert in a new hidden frame
|
# Loads the document to convert in a new hidden frame
|
||||||
prop = PropertyValue(); prop.Name = 'Hidden'; prop.Value = True
|
props = [('Hidden', True)]
|
||||||
if self.inputType == 'csv':
|
if self.inputType == 'csv':
|
||||||
# Give some additional params if we need to open a CSV file
|
# Give some additional params if we need to open a CSV file
|
||||||
prop2 = PropertyValue()
|
props.append(('FilterFlags', '59,34,76,1'))
|
||||||
prop2.Name = 'FilterFlags'
|
#props.append(('FilterData', 'Any'))
|
||||||
prop2.Value = '59,34,76,1'
|
|
||||||
#prop2.Name = 'FilterData'
|
|
||||||
#prop2.Value = 'Any'
|
|
||||||
props = (prop, prop2)
|
|
||||||
else:
|
|
||||||
props = (prop,)
|
|
||||||
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
|
self.doc = self.oo.loadComponentFromURL(self.docUrl, "_blank", 0,
|
||||||
props)
|
self.props(props))
|
||||||
if self.inputType == 'odt':
|
|
||||||
# Perform additional tasks for odt documents
|
# Perform additional tasks for odt documents
|
||||||
self.updateOdtDocument()
|
if self.inputType == 'odt': self.updateOdtDocument()
|
||||||
try:
|
try:
|
||||||
self.doc.refresh()
|
self.doc.refresh()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -232,22 +249,13 @@ class Converter:
|
||||||
'''Calls LO to perform a document conversion. Note that the conversion
|
'''Calls LO to perform a document conversion. Note that the conversion
|
||||||
is not really done if the source and target documents have the same
|
is not really done if the source and target documents have the same
|
||||||
type.'''
|
type.'''
|
||||||
properties = []
|
props = [('FilterName', self.resultFilter)]
|
||||||
from com.sun.star.beans import PropertyValue
|
if self.resultType == 'csv': # Add options for CSV export (separator...)
|
||||||
prop = PropertyValue()
|
props.append(('FilterOptions', '59,34,76,1'))
|
||||||
prop.Name = 'FilterName'
|
self.doc.storeToURL(self.resultUrl, self.props(props))
|
||||||
prop.Value = self.resultFilter
|
|
||||||
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):
|
def run(self):
|
||||||
'''Connects to LO, does the job and disconnects.'''
|
'''Connects to LO, does the job and disconnects'''
|
||||||
self.connect()
|
self.connect()
|
||||||
self.loadDocument()
|
self.loadDocument()
|
||||||
self.convertDocument()
|
self.convertDocument()
|
||||||
|
@ -274,13 +282,17 @@ class ConverterScript:
|
||||||
help="The port on which LibreOffice runs " \
|
help="The port on which LibreOffice runs " \
|
||||||
"Default is %d." % DEFAULT_PORT,
|
"Default is %d." % DEFAULT_PORT,
|
||||||
default=DEFAULT_PORT, metavar="PORT", type='int')
|
default=DEFAULT_PORT, metavar="PORT", type='int')
|
||||||
|
optParser.add_option("-t", "--template", dest="template",
|
||||||
|
default=None, metavar="TEMPLATE", type='string',
|
||||||
|
help="The path to a LibreOffice template from " \
|
||||||
|
"which you may import styles.")
|
||||||
(options, args) = optParser.parse_args()
|
(options, args) = optParser.parse_args()
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
sys.stderr.write(WRONG_NB_OF_ARGS)
|
sys.stderr.write(WRONG_NB_OF_ARGS)
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write('\n')
|
||||||
optParser.print_help()
|
optParser.print_help()
|
||||||
sys.exit(ERROR_CODE)
|
sys.exit(ERROR_CODE)
|
||||||
converter = Converter(args[0], args[1], options.port)
|
converter = Converter(args[0], args[1], options.port, options.template)
|
||||||
try:
|
try:
|
||||||
converter.run()
|
converter.run()
|
||||||
except ConverterError:
|
except ConverterError:
|
||||||
|
|
|
@ -83,9 +83,11 @@ class PodEnvironment(OdfEnvironment):
|
||||||
# Current state
|
# Current state
|
||||||
self.state = self.READING_CONTENT
|
self.state = self.READING_CONTENT
|
||||||
# Elements we must ignore (they will not be included in the result)
|
# Elements we must ignore (they will not be included in the result)
|
||||||
self.ignorableElements = None # Will be set after namespace propagation
|
self.ignorableElems = None # Will be set after namespace propagation
|
||||||
# Elements that may be impacted by POD statements
|
# Elements that may be impacted by POD statements
|
||||||
self.impactableElements = None # Idem
|
self.impactableElems = None # Idem
|
||||||
|
# Elements representing start and end tags surrounding expressions
|
||||||
|
self.exprStartElems = self.exprEndElems = None # Idem
|
||||||
# Stack of currently visited tables
|
# Stack of currently visited tables
|
||||||
self.tableStack = []
|
self.tableStack = []
|
||||||
self.tableIndex = -1
|
self.tableIndex = -1
|
||||||
|
@ -193,30 +195,36 @@ class PodEnvironment(OdfEnvironment):
|
||||||
# Create a table of names of used tags and attributes (precomputed,
|
# Create a table of names of used tags and attributes (precomputed,
|
||||||
# including namespace, for performance).
|
# including namespace, for performance).
|
||||||
table = ns[self.NS_TABLE]
|
table = ns[self.NS_TABLE]
|
||||||
self.tags = {
|
text = ns[self.NS_TEXT]
|
||||||
'tracked-changes': '%s:tracked-changes' % ns[self.NS_TEXT],
|
office = ns[self.NS_OFFICE]
|
||||||
'change': '%s:change' % ns[self.NS_TEXT],
|
tags = {
|
||||||
'annotation': '%s:annotation' % ns[self.NS_OFFICE],
|
'tracked-changes': '%s:tracked-changes' % text,
|
||||||
'change-start': '%s:change-start' % ns[self.NS_TEXT],
|
'change': '%s:change' % text,
|
||||||
'change-end': '%s:change-end' % ns[self.NS_TEXT],
|
'annotation': '%s:annotation' % office,
|
||||||
'conditional-text': '%s:conditional-text' % ns[self.NS_TEXT],
|
'change-start': '%s:change-start' % text,
|
||||||
|
'change-end': '%s:change-end' % text,
|
||||||
|
'conditional-text': '%s:conditional-text' % text,
|
||||||
|
'text-input': '%s:text-input' % text,
|
||||||
'table': '%s:table' % table,
|
'table': '%s:table' % table,
|
||||||
'table-name': '%s:name' % table,
|
'table-name': '%s:name' % table,
|
||||||
'table-cell': '%s:table-cell' % table,
|
'table-cell': '%s:table-cell' % table,
|
||||||
'table-column': '%s:table-column' % table,
|
'table-column': '%s:table-column' % table,
|
||||||
'formula': '%s:formula' % table,
|
'formula': '%s:formula' % table,
|
||||||
'value-type': '%s:value-type' % ns[self.NS_OFFICE],
|
'value-type': '%s:value-type' % office,
|
||||||
'value': '%s:value' % ns[self.NS_OFFICE],
|
'value': '%s:value' % office,
|
||||||
'string-value': '%s:string-value' % ns[self.NS_OFFICE],
|
'string-value': '%s:string-value' % office,
|
||||||
'span': '%s:span' % ns[self.NS_TEXT],
|
'span': '%s:span' % text,
|
||||||
'number-columns-spanned': '%s:number-columns-spanned' % table,
|
'number-columns-spanned': '%s:number-columns-spanned' % table,
|
||||||
'number-columns-repeated': '%s:number-columns-repeated' % table,
|
'number-columns-repeated': '%s:number-columns-repeated' % table,
|
||||||
}
|
}
|
||||||
self.ignorableElements = (self.tags['tracked-changes'],
|
self.tags = tags
|
||||||
self.tags['change'])
|
self.ignorableElems = (tags['tracked-changes'], tags['change'])
|
||||||
self.impactableElements = (
|
self.exprStartElems = (tags['change-start'], tags['conditional-text'], \
|
||||||
Text.OD.elem, Title.OD.elem, Table.OD.elem, Row.OD.elem,
|
tags['text-input'])
|
||||||
Cell.OD.elem, Section.OD.elem)
|
self.exprEndElems = (tags['change-end'], tags['conditional-text'], \
|
||||||
|
tags['text-input'])
|
||||||
|
self.impactableElems = (Text.OD.elem, Title.OD.elem, Table.OD.elem,
|
||||||
|
Row.OD.elem, Cell.OD.elem, Section.OD.elem)
|
||||||
self.inserts = self.transformInserts()
|
self.inserts = self.transformInserts()
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -234,15 +242,15 @@ class PodParser(OdfParser):
|
||||||
officeNs = ns[e.NS_OFFICE]
|
officeNs = ns[e.NS_OFFICE]
|
||||||
textNs = ns[e.NS_TEXT]
|
textNs = ns[e.NS_TEXT]
|
||||||
tableNs = ns[e.NS_TABLE]
|
tableNs = ns[e.NS_TABLE]
|
||||||
if elem in e.ignorableElements:
|
if elem in e.ignorableElems:
|
||||||
e.state = e.IGNORING
|
e.state = e.IGNORING
|
||||||
elif elem == e.tags['annotation']:
|
elif elem == e.tags['annotation']:
|
||||||
# Be it in an ODT or ODS template, an annotation is considered to
|
# Be it in an ODT or ODS template, an annotation is considered to
|
||||||
# contain a POD statement.
|
# contain a POD statement.
|
||||||
e.state = e.READING_STATEMENT
|
e.state = e.READING_STATEMENT
|
||||||
elif elem in (e.tags['change-start'], e.tags['conditional-text']):
|
elif elem in e.exprStartElems:
|
||||||
# In an ODT template, any text in track-changes or any conditional
|
# Any track-changed text or being in a conditional or input field is
|
||||||
# field is considered to contain a POD expression.
|
# considered to be a POD expression.
|
||||||
e.state = e.READING_EXPRESSION
|
e.state = e.READING_EXPRESSION
|
||||||
e.exprHasStyle = False
|
e.exprHasStyle = False
|
||||||
elif (elem == e.tags['table-cell']) and \
|
elif (elem == e.tags['table-cell']) and \
|
||||||
|
@ -272,7 +280,7 @@ class PodParser(OdfParser):
|
||||||
if e.state == e.IGNORING:
|
if e.state == e.IGNORING:
|
||||||
pass
|
pass
|
||||||
elif e.state == e.READING_CONTENT:
|
elif e.state == e.READING_CONTENT:
|
||||||
if elem in e.impactableElements:
|
if elem in e.impactableElems:
|
||||||
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)
|
||||||
|
@ -290,7 +298,7 @@ class PodParser(OdfParser):
|
||||||
ns = e.onEndElement()
|
ns = e.onEndElement()
|
||||||
officeNs = ns[e.NS_OFFICE]
|
officeNs = ns[e.NS_OFFICE]
|
||||||
textNs = ns[e.NS_TEXT]
|
textNs = ns[e.NS_TEXT]
|
||||||
if elem in e.ignorableElements:
|
if elem in e.ignorableElems:
|
||||||
e.state = e.READING_CONTENT
|
e.state = e.READING_CONTENT
|
||||||
elif elem == e.tags['annotation']:
|
elif elem == e.tags['annotation']:
|
||||||
# Manage statement
|
# Manage statement
|
||||||
|
@ -317,7 +325,7 @@ class PodParser(OdfParser):
|
||||||
e.currentOdsHook = 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.impactableElems:
|
||||||
if isinstance(e.currentBuffer, MemoryBuffer):
|
if isinstance(e.currentBuffer, MemoryBuffer):
|
||||||
isMainElement = e.currentBuffer.isMainElement(elem)
|
isMainElement = e.currentBuffer.isMainElement(elem)
|
||||||
# Unreference the element among buffer.elements
|
# Unreference the element among buffer.elements
|
||||||
|
@ -346,8 +354,7 @@ class PodParser(OdfParser):
|
||||||
e.currentStatement.append(statementLine)
|
e.currentStatement.append(statementLine)
|
||||||
e.currentContent = ''
|
e.currentContent = ''
|
||||||
elif e.state == e.READING_EXPRESSION:
|
elif e.state == e.READING_EXPRESSION:
|
||||||
if (elem == e.tags['change-end']) or \
|
if elem in e.exprEndElems:
|
||||||
(elem == e.tags['conditional-text']):
|
|
||||||
expression = e.currentContent.strip()
|
expression = e.currentContent.strip()
|
||||||
e.currentContent = ''
|
e.currentContent = ''
|
||||||
# Manage expression
|
# Manage expression
|
||||||
|
|
104
pod/renderer.py
104
pod/renderer.py
|
@ -18,13 +18,12 @@
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import zipfile, shutil, xml.sax, os, os.path, re, mimetypes, time
|
import zipfile, shutil, xml.sax, os, os.path, re, mimetypes, time
|
||||||
|
|
||||||
from UserDict import UserDict
|
from UserDict import UserDict
|
||||||
|
import appy.pod
|
||||||
import appy.pod, time, cgi
|
|
||||||
from appy.pod import PodError
|
from appy.pod import PodError
|
||||||
from appy.shared import mimeTypes, mimeTypesExts
|
from appy.shared import mimeTypes, mimeTypesExts
|
||||||
from appy.shared.xml_parser import XmlElement
|
from appy.shared.xml_parser import XmlElement
|
||||||
|
from appy.shared.zip import unzip, zip
|
||||||
from appy.shared.utils import FolderDeleter, executeCommand, FileWrapper
|
from appy.shared.utils import FolderDeleter, executeCommand, FileWrapper
|
||||||
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
|
from appy.pod.pod_parser import PodParser, PodEnvironment, OdInsert
|
||||||
from appy.pod.converter import FILE_TYPES
|
from appy.pod.converter import FILE_TYPES
|
||||||
|
@ -101,7 +100,7 @@ class Renderer:
|
||||||
def __init__(self, template, context, result, pythonWithUnoPath=None,
|
def __init__(self, template, context, result, pythonWithUnoPath=None,
|
||||||
ooPort=2002, stylesMapping={}, forceOoCall=False,
|
ooPort=2002, stylesMapping={}, forceOoCall=False,
|
||||||
finalizeFunction=None, overwriteExisting=False,
|
finalizeFunction=None, overwriteExisting=False,
|
||||||
raiseOnError=False, imageResolver=None):
|
raiseOnError=False, imageResolver=None, stylesTemplate=None):
|
||||||
'''This Python Open Document Renderer (PodRenderer) loads a document
|
'''This Python Open Document Renderer (PodRenderer) loads a document
|
||||||
template (p_template) which is an ODT or ODS file with some elements
|
template (p_template) which is an ODT or ODS file with some elements
|
||||||
written in Python. Based on this template and some Python objects
|
written in Python. Based on this template and some Python objects
|
||||||
|
@ -145,9 +144,11 @@ class Renderer:
|
||||||
XHTML content. Indeed, POD may not be able (ie, may not have the
|
XHTML content. Indeed, POD may not be able (ie, may not have the
|
||||||
permission to) perform a HTTP GET on those images. Currently, the
|
permission to) perform a HTTP GET on those images. Currently, the
|
||||||
resolver can only be a Zope application object.
|
resolver can only be a Zope application object.
|
||||||
|
|
||||||
|
- p_stylesTemplate can be the path to a LibreOffice file (ie, a .ott
|
||||||
|
file) whose styles will be imported within the result.
|
||||||
'''
|
'''
|
||||||
self.template = template
|
self.template = template
|
||||||
self.templateZip = zipfile.ZipFile(template)
|
|
||||||
self.result = result
|
self.result = result
|
||||||
self.contentXml = None # Content (string) of content.xml
|
self.contentXml = None # Content (string) of content.xml
|
||||||
self.stylesXml = None # Content (string) of styles.xml
|
self.stylesXml = None # Content (string) of styles.xml
|
||||||
|
@ -162,6 +163,7 @@ class Renderer:
|
||||||
self.overwriteExisting = overwriteExisting
|
self.overwriteExisting = overwriteExisting
|
||||||
self.raiseOnError = raiseOnError
|
self.raiseOnError = raiseOnError
|
||||||
self.imageResolver = imageResolver
|
self.imageResolver = imageResolver
|
||||||
|
self.stylesTemplate = stylesTemplate
|
||||||
# Remember potential files or images that will be included through
|
# Remember potential files or images that will be included through
|
||||||
# "do ... from document" statements: we will need to declare them in
|
# "do ... from document" statements: we will need to declare them in
|
||||||
# META-INF/manifest.xml. Keys are file names as they appear within the
|
# META-INF/manifest.xml. Keys are file names as they appear within the
|
||||||
|
@ -173,49 +175,16 @@ class Renderer:
|
||||||
# Unzip template
|
# Unzip template
|
||||||
self.unzipFolder = os.path.join(self.tempFolder, 'unzip')
|
self.unzipFolder = os.path.join(self.tempFolder, 'unzip')
|
||||||
os.mkdir(self.unzipFolder)
|
os.mkdir(self.unzipFolder)
|
||||||
for zippedFile in self.templateZip.namelist():
|
info = unzip(template, self.unzipFolder, odf=True)
|
||||||
# Before writing the zippedFile into self.unzipFolder, create the
|
self.contentXml = info['content.xml']
|
||||||
# intermediary subfolder(s) if needed.
|
self.stylesXml = info['styles.xml']
|
||||||
fileName = None
|
self.stylesManager = StylesManager(self.stylesXml)
|
||||||
if zippedFile.endswith('/') or zippedFile.endswith(os.sep):
|
# From LibreOffice 3.5, it is not possible anymore to dump errors into
|
||||||
# This is an empty folder. Create it nevertheless. If zippedFile
|
# the resulting ods as annotations. Indeed, annotations can't reside
|
||||||
# starts with a '/', os.path.join will consider it an absolute
|
# anymore within paragraphs. ODS files generated with pod and containing
|
||||||
# path and will throw away self.unzipFolder.
|
# error messages in annotations cause LibreOffice 3.5 and 4.0 to crash.
|
||||||
os.makedirs(os.path.join(self.unzipFolder,
|
|
||||||
zippedFile.lstrip('/')))
|
|
||||||
else:
|
|
||||||
fileName = os.path.basename(zippedFile)
|
|
||||||
folderName = os.path.dirname(zippedFile)
|
|
||||||
fullFolderName = self.unzipFolder
|
|
||||||
if folderName:
|
|
||||||
fullFolderName = os.path.join(fullFolderName, folderName)
|
|
||||||
if not os.path.exists(fullFolderName):
|
|
||||||
os.makedirs(fullFolderName)
|
|
||||||
# Unzip the file in self.unzipFolder
|
|
||||||
if fileName:
|
|
||||||
fullFileName = os.path.join(fullFolderName, fileName)
|
|
||||||
f = open(fullFileName, 'wb')
|
|
||||||
fileContent = self.templateZip.read(zippedFile)
|
|
||||||
if (fileName == 'content.xml') and not folderName:
|
|
||||||
# content.xml files may reside in subfolders.
|
|
||||||
# We modify only the one in the root folder.
|
|
||||||
self.contentXml = fileContent
|
|
||||||
elif (fileName == 'styles.xml') and not folderName:
|
|
||||||
# Same remark as above.
|
|
||||||
self.stylesManager = StylesManager(fileContent)
|
|
||||||
self.stylesXml = fileContent
|
|
||||||
elif (fileName == 'mimetype') and \
|
|
||||||
(fileContent == mimeTypes['ods']):
|
|
||||||
# From LibreOffice 3.5, it is not possible anymore to dump
|
|
||||||
# errors into the resulting ods as annotations. Indeed,
|
|
||||||
# annotations can't reside anymore within paragraphs. ODS
|
|
||||||
# files generated with pod and containing error messages in
|
|
||||||
# annotations cause LibreOffice 3.5 and 4.0 to crash.
|
|
||||||
# LibreOffice >= 4.1 simply does not show the annotation.
|
# LibreOffice >= 4.1 simply does not show the annotation.
|
||||||
self.raiseOnError = True
|
if info['mimetype'] == mimeTypes['ods']: self.raiseOnError = True
|
||||||
f.write(fileContent)
|
|
||||||
f.close()
|
|
||||||
self.templateZip.close()
|
|
||||||
# Create the content.xml parser
|
# Create the content.xml parser
|
||||||
pe = PodEnvironment
|
pe = PodEnvironment
|
||||||
contentInserts = (
|
contentInserts = (
|
||||||
|
@ -440,7 +409,7 @@ class Renderer:
|
||||||
|
|
||||||
# Public interface
|
# Public interface
|
||||||
def run(self):
|
def run(self):
|
||||||
'''Renders the result.'''
|
'''Renders the result'''
|
||||||
try:
|
try:
|
||||||
# Remember which parser is running
|
# Remember which parser is running
|
||||||
self.currentParser = self.contentParser
|
self.currentParser = self.contentParser
|
||||||
|
@ -490,7 +459,8 @@ class Renderer:
|
||||||
try:
|
try:
|
||||||
from appy.pod.converter import Converter, ConverterError
|
from appy.pod.converter import Converter, ConverterError
|
||||||
try:
|
try:
|
||||||
Converter(resultName, resultType, self.ooPort).run()
|
Converter(resultName, resultType, self.ooPort,
|
||||||
|
self.stylesTemplate).run()
|
||||||
except ConverterError, ce:
|
except ConverterError, ce:
|
||||||
raise PodError(CONVERT_ERROR % str(ce))
|
raise PodError(CONVERT_ERROR % str(ce))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -513,6 +483,7 @@ class Renderer:
|
||||||
cmd = '%s %s %s %s -p%d' % \
|
cmd = '%s %s %s %s -p%d' % \
|
||||||
(self.pyPath, convScript, qResultName, resultType,
|
(self.pyPath, convScript, qResultName, resultType,
|
||||||
self.ooPort)
|
self.ooPort)
|
||||||
|
if self.stylesTemplate: cmd += ' -t%s' % self.stylesTemplate
|
||||||
loOutput = executeCommand(cmd)
|
loOutput = executeCommand(cmd)
|
||||||
except PodError, pe:
|
except PodError, pe:
|
||||||
# When trying to call LO in server mode for producing ODT or ODS
|
# When trying to call LO in server mode for producing ODT or ODS
|
||||||
|
@ -559,7 +530,7 @@ class Renderer:
|
||||||
f = file(contentXml, 'w')
|
f = file(contentXml, 'w')
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
f.close()
|
||||||
# Call the user-defined "finalize" function when present.
|
# Call the user-defined "finalize" function when present
|
||||||
if self.finalizeFunction:
|
if self.finalizeFunction:
|
||||||
try:
|
try:
|
||||||
self.finalizeFunction(self.unzipFolder)
|
self.finalizeFunction(self.unzipFolder)
|
||||||
|
@ -569,38 +540,7 @@ class Renderer:
|
||||||
# the POD template (odt, ods...)
|
# the POD template (odt, ods...)
|
||||||
resultExt = self.getTemplateType()
|
resultExt = self.getTemplateType()
|
||||||
resultName = os.path.join(self.tempFolder, 'result.%s' % resultExt)
|
resultName = os.path.join(self.tempFolder, 'result.%s' % resultExt)
|
||||||
try:
|
zip(resultName, self.unzipFolder, odf=True)
|
||||||
resultZip = zipfile.ZipFile(resultName, 'w', zipfile.ZIP_DEFLATED)
|
|
||||||
except RuntimeError:
|
|
||||||
resultZip = zipfile.ZipFile(resultName,'w')
|
|
||||||
# Insert first the file "mimetype" (uncompressed), in order to be
|
|
||||||
# compliant with the OpenDocument Format specification, section 17.4,
|
|
||||||
# that expresses this restriction. Else, libraries like "magic", under
|
|
||||||
# Linux/Unix, are unable to detect the correct mimetype for a pod result
|
|
||||||
# (it simply recognizes it as a "application/zip" and not a
|
|
||||||
# "application/vnd.oasis.opendocument.text)".
|
|
||||||
mimetypeFile = os.path.join(self.unzipFolder, 'mimetype')
|
|
||||||
# This file may not exist (presumably, ods files from Google Drive)
|
|
||||||
if not os.path.exists(mimetypeFile):
|
|
||||||
f = open(mimetypeFile, 'w')
|
|
||||||
f.write(mimeTypes[resultExt])
|
|
||||||
f.close()
|
|
||||||
resultZip.write(mimetypeFile, 'mimetype', zipfile.ZIP_STORED)
|
|
||||||
for dir, dirnames, filenames in os.walk(self.unzipFolder):
|
|
||||||
for f in filenames:
|
|
||||||
folderName = dir[len(self.unzipFolder)+1:]
|
|
||||||
# Ignore file "mimetype" that was already inserted.
|
|
||||||
if (folderName == '') and (f == 'mimetype'): continue
|
|
||||||
resultZip.write(os.path.join(dir, f),
|
|
||||||
os.path.join(folderName, f))
|
|
||||||
if not dirnames and not filenames:
|
|
||||||
# This is an empty leaf folder. We must create an entry in the
|
|
||||||
# zip for him.
|
|
||||||
folderName = dir[len(self.unzipFolder):]
|
|
||||||
zInfo = zipfile.ZipInfo("%s/" % folderName,time.localtime()[:6])
|
|
||||||
zInfo.external_attr = 48
|
|
||||||
resultZip.writestr(zInfo, '')
|
|
||||||
resultZip.close()
|
|
||||||
resultType = os.path.splitext(self.result)[1].strip('.')
|
resultType = os.path.splitext(self.result)[1].strip('.')
|
||||||
if (resultType in self.templateTypes) and not self.forceOoCall:
|
if (resultType in self.templateTypes) and not self.forceOoCall:
|
||||||
# Simply move the ODT result to the result
|
# Simply move the ODT result to the result
|
||||||
|
|
|
@ -4,18 +4,15 @@
|
||||||
@style@:font-name-asian="PodStarSymbol" @style@:font-size-asian="9pt"
|
@style@:font-name-asian="PodStarSymbol" @style@:font-size-asian="9pt"
|
||||||
@style@:font-name-complex="PodStarSymbol" @style@:font-size-complex="9pt"/>
|
@style@:font-name-complex="PodStarSymbol" @style@:font-size-complex="9pt"/>
|
||||||
</@style@:style>
|
</@style@:style>
|
||||||
<@style@:style style:name="AppyStandard" style:family="paragraph" style:class="text" style:master-page-name="">
|
<@style@:style style:name="AppyStandard" style:family="paragraph" style:class="text" style:master-page-name="" @style@:parent-style-name="Standard">
|
||||||
<@style@:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.101cm" fo:margin-bottom="0.169cm" fo:text-indent="0cm" style:auto-text-indent="false" style:page-number="auto"/>
|
<@style@:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.101cm" fo:margin-bottom="0.169cm" fo:text-indent="0cm" style:auto-text-indent="false" style:page-number="auto"/>
|
||||||
<@style@:text-properties style:font-name="DejaVu Sans" fo:font-size="10pt"/>
|
|
||||||
</@style@:style>
|
</@style@:style>
|
||||||
<@style@:style @style@:name="Appy_Table_Content" @style@:display-name="Appy Table Contents" @style@:family="paragraph"
|
<@style@:style @style@:name="Appy_Table_Content" @style@:display-name="Appy Table Contents" @style@:family="paragraph"
|
||||||
@style@:parent-style-name="AppyStandard" @style@:class="extra">
|
@style@:parent-style-name="AppyStandard" @style@:class="extra">
|
||||||
<@style@:paragraph-properties @fo@:margin-top="0cm" @fo@:margin-bottom="0cm" @text@:number-lines="false" @text@:line-number="0"/>
|
<@style@:paragraph-properties @fo@:margin-top="0cm" @fo@:margin-bottom="0cm" @text@:number-lines="false" @text@:line-number="0"/>
|
||||||
<@style@:text-properties @fo@:font-size="8pt"/>
|
|
||||||
</@style@:style>
|
</@style@:style>
|
||||||
<@style@:style @style@:name="Appy_Table_Heading" @style@:display-name="Appy Table Heading" @style@:family="paragraph"
|
<@style@:style @style@:name="Appy_Table_Heading" @style@:display-name="Appy Table Heading" @style@:family="paragraph"
|
||||||
@style@:parent-style-name="Appy_Table_Contents" @style@:class="extra">
|
@style@:parent-style-name="Appy_Table_Contents" @style@:class="extra">
|
||||||
<@style@:paragraph-properties @fo@:text-align="center" @style@:justify-single-word="false" @text@:number-lines="false"
|
<@style@:paragraph-properties @fo@:text-align="center" @style@:justify-single-word="false" @text@:number-lines="false" @text@:line-number="0"/>
|
||||||
@text@:line-number="0"/>
|
|
||||||
<@style@:text-properties @fo@:font-weight="bold" @style@:font-weight-asian="bold" @style@:font-weight-complex="bold"/>
|
<@style@:text-properties @fo@:font-weight="bold" @style@:font-weight-asian="bold" @style@:font-weight-complex="bold"/>
|
||||||
</@style@:style>
|
</@style@:style>
|
||||||
|
|
|
@ -17,13 +17,13 @@ from appy.pod import *
|
||||||
|
|
||||||
# To which ODT tags do HTML tags correspond ?
|
# To which ODT tags do HTML tags correspond ?
|
||||||
HTML_2_ODT = {'h1':'h', 'h2':'h', 'h3':'h', 'h4':'h', 'h5':'h', 'h6':'h',
|
HTML_2_ODT = {'h1':'h', 'h2':'h', 'h3':'h', 'h4':'h', 'h5':'h', 'h6':'h',
|
||||||
'p':'p', 'div': 'p', 'b':'span', 'i':'span', 'strong':'span',
|
'p':'p', 'div': 'p', 'b':'span', 'i':'span', 'strong':'span', 'strike':'span',
|
||||||
'strike':'span', 'u':'span', 'em': 'span', 'sub': 'span',
|
's':'span', 'u':'span', 'em': 'span', 'sub': 'span', 'sup': 'span',
|
||||||
'sup': 'span', 'br': 'line-break'}
|
'br': 'line-break'}
|
||||||
DEFAULT_ODT_STYLES = {'b': 'podBold', 'strong':'podBold', 'i': 'podItalic',
|
DEFAULT_ODT_STYLES = {'b': 'podBold', 'strong':'podBold', 'i': 'podItalic',
|
||||||
'u': 'podUnderline', 'strike': 'podStrike',
|
'u': 'podUnderline', 'strike': 'podStrike', 's': 'podStrike',
|
||||||
'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub',
|
'em': 'podItalic', 'sup': 'podSup', 'sub':'podSub', 'td': 'podCell',
|
||||||
'td': 'podCell', 'th': 'podHeaderCell'}
|
'th': 'podHeaderCell'}
|
||||||
INNER_TAGS = ('b', 'strong', 'i', 'u', 'em', 'sup', 'sub', 'span')
|
INNER_TAGS = ('b', 'strong', 'i', 'u', 'em', 'sup', 'sub', 'span')
|
||||||
TABLE_CELL_TAGS = ('td', 'th')
|
TABLE_CELL_TAGS = ('td', 'th')
|
||||||
OUTER_TAGS = TABLE_CELL_TAGS + ('li',)
|
OUTER_TAGS = TABLE_CELL_TAGS + ('li',)
|
||||||
|
|
|
@ -245,7 +245,7 @@ def getTempFileName(prefix='', extension=''):
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
def executeCommand(cmd):
|
def executeCommand(cmd):
|
||||||
'''Executes command p_cmd and returns the content of its stderr.'''
|
'''Executes command p_cmd and returns the content of its stderr'''
|
||||||
childStdIn, childStdOut, childStdErr = os.popen3(cmd)
|
childStdIn, childStdOut, childStdErr = os.popen3(cmd)
|
||||||
res = childStdErr.read()
|
res = childStdErr.read()
|
||||||
childStdIn.close(); childStdOut.close(); childStdErr.close()
|
childStdIn.close(); childStdOut.close(); childStdErr.close()
|
||||||
|
|
94
shared/zip.py
Normal file
94
shared/zip.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
'''Functions for (un)zipping files'''
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import os, os.path, zipfile, time
|
||||||
|
from appy.shared import mimeTypes
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
def unzip(f, folder, odf=False):
|
||||||
|
'''Unzips file p_f into p_folder. p_f can be any anything accepted by the
|
||||||
|
zipfile.ZipFile constructor. p_folder must exist.
|
||||||
|
|
||||||
|
If p_odf is True, p_f is considered to be an odt or ods file and this
|
||||||
|
function will return a dict containing the content of content.xml and
|
||||||
|
styles.xml from the zipped file.'''
|
||||||
|
zipFile = zipfile.ZipFile(f)
|
||||||
|
if odf: res = {}
|
||||||
|
else: res = None
|
||||||
|
for zippedFile in zipFile.namelist():
|
||||||
|
# Before writing the zippedFile into p_folder, create the intermediary
|
||||||
|
# subfolder(s) if needed.
|
||||||
|
fileName = None
|
||||||
|
if zippedFile.endswith('/') or zippedFile.endswith(os.sep):
|
||||||
|
# This is an empty folder. Create it nevertheless. If zippedFile
|
||||||
|
# starts with a '/', os.path.join will consider it an absolute
|
||||||
|
# path and will throw away folder.
|
||||||
|
os.makedirs(os.path.join(folder, zippedFile.lstrip('/')))
|
||||||
|
else:
|
||||||
|
fileName = os.path.basename(zippedFile)
|
||||||
|
folderName = os.path.dirname(zippedFile)
|
||||||
|
fullFolderName = folder
|
||||||
|
if folderName:
|
||||||
|
fullFolderName = os.path.join(fullFolderName, folderName)
|
||||||
|
if not os.path.exists(fullFolderName):
|
||||||
|
os.makedirs(fullFolderName)
|
||||||
|
# Unzip the file in folder
|
||||||
|
if fileName:
|
||||||
|
fullFileName = os.path.join(fullFolderName, fileName)
|
||||||
|
f = open(fullFileName, 'wb')
|
||||||
|
fileContent = zipFile.read(zippedFile)
|
||||||
|
if odf and not folderName:
|
||||||
|
# content.xml and others may reside in subfolders. Get only the
|
||||||
|
# one in the root folder.
|
||||||
|
if fileName == 'content.xml':
|
||||||
|
res['content.xml'] = fileContent
|
||||||
|
elif fileName == 'styles.xml':
|
||||||
|
res['styles.xml'] = fileContent
|
||||||
|
elif fileName == 'mimetype':
|
||||||
|
res['mimetype'] = fileContent
|
||||||
|
f.write(fileContent)
|
||||||
|
f.close()
|
||||||
|
zipFile.close()
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
def zip(f, folder, odf=False):
|
||||||
|
'''Zips the content of p_folder into the zip file whose (preferably)
|
||||||
|
absolute filename is p_f. If p_odf is True, p_folder is considered to
|
||||||
|
contain the standard content of an ODF file (content.xml,...). In this
|
||||||
|
case, some rules must be respected while building the zip (see below).'''
|
||||||
|
# Remove p_f if it exists
|
||||||
|
if os.path.exists(f): os.remove(f)
|
||||||
|
try:
|
||||||
|
zipFile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
except RuntimeError:
|
||||||
|
zipFile = zipfile.ZipFile(f, 'w')
|
||||||
|
# If p_odf is True, insert first the file "mimetype" (uncompressed), in
|
||||||
|
# order to be compliant with the OpenDocument Format specification,
|
||||||
|
# section 17.4, that expresses this restriction. Else, libraries like
|
||||||
|
# "magic", under Linux/Unix, are unable to detect the correct mimetype for
|
||||||
|
# a pod result (it simply recognizes it as a "application/zip" and not a
|
||||||
|
# "application/vnd.oasis.opendocument.text)".
|
||||||
|
if odf:
|
||||||
|
mimetypeFile = os.path.join(folder, 'mimetype')
|
||||||
|
# This file may not exist (presumably, ods files from Google Drive)
|
||||||
|
if not os.path.exists(mimetypeFile):
|
||||||
|
f = file(mimetypeFile, 'w')
|
||||||
|
f.write(mimeTypes[os.path.splitext(f)[-1][1:]])
|
||||||
|
f.close()
|
||||||
|
zipFile.write(mimetypeFile, 'mimetype', zipfile.ZIP_STORED)
|
||||||
|
for dir, dirnames, filenames in os.walk(folder):
|
||||||
|
for name in filenames:
|
||||||
|
folderName = dir[len(folder)+1:]
|
||||||
|
# For p_odf files, ignore file "mimetype" that was already inserted
|
||||||
|
if odf and (folderName == '') and (name == 'mimetype'): continue
|
||||||
|
zipFile.write(os.path.join(dir,name), os.path.join(folderName,name))
|
||||||
|
if not dirnames and not filenames:
|
||||||
|
# This is an empty leaf folder. We must create an entry in the
|
||||||
|
# zip for him.
|
||||||
|
folderName = dir[len(folder):]
|
||||||
|
zInfo = zipfile.ZipInfo("%s/" % folderName, time.localtime()[:6])
|
||||||
|
zInfo.external_attr = 48
|
||||||
|
zipFile.writestr(zInfo, '')
|
||||||
|
zipFile.close()
|
||||||
|
# ------------------------------------------------------------------------------
|
Loading…
Reference in a new issue