class Cell(PodElement): OD = XmlElement('table-cell', nsUri=ns.NS_TABLE) subTags = [Text.OD] def __init__(self): self.tableInfo = None # ~OdTable~ self.colIndex = None # The column index for this cell, within its table.
class Table(PodElement): OD = XmlElement('table', nsUri=ns.NS_TABLE) subTags = [Row.OD, Cell.OD, Text.OD] # When we must remove the Table element from a buffer, the deepest element # to remove is the Cell (it can only be done for one-row, one-cell tables). DEEPEST_TO_REMOVE = Cell.OD def __init__(self): self.tableInfo = None # ~OdTable~
def __init__(self, template, context, result, pythonWithUnoPath=None, ooPort=2002, stylesMapping={}, forceOoCall=False, finalizeFunction=None, overwriteExisting=False, raiseOnError=False, imageResolver=None): '''This Python Open Document Renderer (PodRenderer) loads a document template (p_template) which is an ODT or ODS file with some elements written in Python. Based on this template and some Python objects defined in p_context, the renderer generates an ODT file (p_result) that instantiates the p_template and fills it with objects from the p_context. - If p_result does not end with .odt or .ods, the Renderer will call LibreOffice to perform a conversion. If p_forceOoCall is True, even if p_result ends with .odt, LibreOffice will be called, not for performing a conversion, but for updating some elements like indexes (table of contents, etc) and sections containing links to external files (which is the case, for example, if you use the default function "document"). - If the Python interpreter which runs the current script is not UNO-enabled, this script will run, in another process, a UNO-enabled Python interpreter (whose path is p_pythonWithUnoPath) which will call LibreOffice. In both cases, we will try to connect to LibreOffice in server mode on port p_ooPort. - If you plan to make "XHTML to OpenDocument" conversions, you may specify a styles mapping in p_stylesMapping. - If you specify a function in p_finalizeFunction, this function will be called by the renderer before re-zipping the ODT/S result. This way, you can still perform some actions on the content of the ODT/S file before it is zipped and potentially converted. This function must accept one arg: the absolute path to the temporary folder containing the un-zipped content of the ODT/S result. - If you set p_overwriteExisting to True, the renderer will overwrite the result file. Else, an exception will be thrown if the result file already exists. - If p_raiseOnError is False (the default value), any error encountered during the generation of the result file will be dumped into it, as a Python traceback within a note. Else, the error will be raised. - p_imageResolver allows POD to retrieve images, from "img" tags within XHTML content. Indeed, POD may not be able (ie, may not have the permission to) perform a HTTP GET on those images. Currently, the resolver can only be a Zope application object. ''' self.template = template self.templateZip = zipfile.ZipFile(template) self.result = result self.contentXml = None # Content (string) of content.xml self.stylesXml = None # Content (string) of styles.xml self.stylesManager = None # Manages the styles defined into the ODT # template self.tempFolder = None self.env = None self.pyPath = pythonWithUnoPath self.ooPort = ooPort self.forceOoCall = forceOoCall self.finalizeFunction = finalizeFunction self.overwriteExisting = overwriteExisting self.raiseOnError = raiseOnError self.imageResolver = imageResolver # Remember potential files or images that will be included through # "do ... from document" statements: we will need to declare them in # META-INF/manifest.xml. Keys are file names as they appear within the # ODT file (to dump in manifest.xml); values are original paths of # included images (used for avoiding to create multiple copies of a file # which is imported several times). self.fileNames = {} self.prepareFolders() # Unzip template self.unzipFolder = os.path.join(self.tempFolder, 'unzip') os.mkdir(self.unzipFolder) for zippedFile in self.templateZip.namelist(): # Before writing the zippedFile into self.unzipFolder, 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 self.unzipFolder. 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. self.raiseOnError = True f.write(fileContent) f.close() self.templateZip.close() # Create the content.xml parser pe = PodEnvironment contentInserts = ( OdInsert(CONTENT_POD_FONTS, XmlElement('font-face-decls', nsUri=pe.NS_OFFICE), nsUris={'style': pe.NS_STYLE, 'svg': pe.NS_SVG}), OdInsert(CONTENT_POD_STYLES, XmlElement('automatic-styles', nsUri=pe.NS_OFFICE), nsUris={'style': pe.NS_STYLE, 'fo': pe.NS_FO, 'text': pe.NS_TEXT, 'table': pe.NS_TABLE})) self.contentParser = self.createPodParser('content.xml', context, contentInserts) # Create the styles.xml parser stylesInserts = ( OdInsert(STYLES_POD_FONTS, XmlElement('font-face-decls', nsUri=pe.NS_OFFICE), nsUris={'style': pe.NS_STYLE, 'svg': pe.NS_SVG}), OdInsert(STYLES_POD_STYLES, XmlElement('styles', nsUri=pe.NS_OFFICE), nsUris={'style': pe.NS_STYLE, 'fo': pe.NS_FO, 'text': pe.NS_TEXT})) self.stylesParser = self.createPodParser('styles.xml', context, stylesInserts) # Store the styles mapping self.setStylesMapping(stylesMapping) # While working, POD may identify "dynamic styles" to insert into # the "automatic styles" section of content.xml, like the column styles # of tables generated from XHTML tables via xhtml2odt.py. self.dynamicStyles = []
class Row(PodElement): OD = XmlElement('table-row', nsUri=ns.NS_TABLE) subTags = [Cell.OD, Text.OD]
class Section(PodElement): OD = XmlElement('section', nsUri=ns.NS_TEXT) subTags = [Text.OD] DEEPEST_TO_REMOVE = OD # When we must remove the Section element from a
class Title(PodElement): OD = XmlElement('h', nsUri=ns.NS_TEXT) subTags = []
class Text(PodElement): OD = XmlElement('p', nsUri=ns.NS_TEXT) subTags = [] # When generating an error we may need to surround the error
def __init__(self, template, context, result, pythonWithUnoPath=None, ooPort=2002, stylesMapping={}, forceOoCall=False, finalizeFunction=None, overwriteExisting=False): '''This Python Open Document Renderer (PodRenderer) loads a document template (p_template) which is an ODT file with some elements written in Python. Based on this template and some Python objects defined in p_context, the renderer generates an ODT file (p_result) that instantiates the p_template and fills it with objects from the p_context. - If p_result does not end with .odt, the Renderer will call OpenOffice to perform a conversion. If p_forceOoCall is True, even if p_result ends with .odt, OpenOffice will be called, not for performing a conversion, but for updating some elements like indexes (table of contents, etc) and sections containing links to external files (which is the case, for example, if you use the default function "document"). - If the Python interpreter which runs the current script is not UNO-enabled, this script will run, in another process, a UNO-enabled Python interpreter (whose path is p_pythonWithUnoPath) which will call OpenOffice. In both cases, we will try to connect to OpenOffice in server mode on port p_ooPort. - If you plan to make "XHTML to OpenDocument" conversions, you may specify a styles mapping in p_stylesMapping. - If you specify a function in p_finalizeFunction, this function will be called by the renderer before re-zipping the ODT result. This way, you can still perform some actions on the content of the ODT file before it is zipped and potentially converted. This function must accept one arg: the absolute path to the temporary folder containing the un-zipped content of the ODT result. - If you set p_overwriteExisting to True, the renderer will overwrite the result file. Else, an exception will be thrown if the result file already exists.''' self.template = template self.templateZip = zipfile.ZipFile(template) self.result = result self.contentXml = None # Content (string) of content.xml self.stylesXml = None # Content (string) of styles.xml self.stylesManager = None # Manages the styles defined into the ODT # template self.tempFolder = None self.env = None self.pyPath = pythonWithUnoPath self.ooPort = ooPort self.forceOoCall = forceOoCall self.finalizeFunction = finalizeFunction self.overwriteExisting = overwriteExisting # Remember potential files or images that will be included through # "do ... from document" statements: we will need to declare them in # META-INF/manifest.xml. Keys are file names as they appear within the # ODT file (to dump in manifest.xml); values are original paths of # included images (used for avoiding to create multiple copies of a file # which is imported several times). # imported file). self.fileNames = {} self.prepareFolders() # Unzip template self.unzipFolder = os.path.join(self.tempFolder, 'unzip') os.mkdir(self.unzipFolder) for zippedFile in self.templateZip.namelist(): # Before writing the zippedFile into self.unzipFolder, 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. os.makedirs(os.path.join(self.unzipFolder, zippedFile)) 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 f.write(fileContent) f.close() self.templateZip.close() # Create the content.xml parser pe = PodEnvironment contentInserts = (OdInsert(CONTENT_POD_FONTS, XmlElement('font-face-decls', nsUri=pe.NS_OFFICE), nsUris={ 'style': pe.NS_STYLE, 'svg': pe.NS_SVG }), OdInsert(CONTENT_POD_STYLES, XmlElement('automatic-styles', nsUri=pe.NS_OFFICE), nsUris={ 'style': pe.NS_STYLE, 'fo': pe.NS_FO, 'text': pe.NS_TEXT, 'table': pe.NS_TABLE })) self.contentParser = self.createPodParser('content.xml', context, contentInserts) # Create the styles.xml parser stylesInserts = (OdInsert(STYLES_POD_FONTS, XmlElement('font-face-decls', nsUri=pe.NS_OFFICE), nsUris={ 'style': pe.NS_STYLE, 'svg': pe.NS_SVG }), OdInsert(STYLES_POD_STYLES, XmlElement('styles', nsUri=pe.NS_OFFICE), nsUris={ 'style': pe.NS_STYLE, 'fo': pe.NS_FO })) self.stylesParser = self.createPodParser('styles.xml', context, stylesInserts) # Stores the styles mapping self.setStylesMapping(stylesMapping)
class Section(PodElement): OD = XmlElement('section', nsUri=ns.NS_TEXT) subTags = [Text.OD] # When we must remove the Section element from a buffer, the deepest element # to remove is the Section element itself. DEEPEST_TO_REMOVE = OD
class Text(PodElement): OD = XmlElement('p', nsUri=ns.NS_TEXT) # When generating an error we may need to surround it with a given tag and # sub-tags. subTags = []