def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None, sizeUnit='cm', style=None): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara and p_size are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in p_sizeUnit (see below). If not specified, size will be computed from image info. * p_sizeUnit is the unit for p_size elements, it can be "cm" (centimeters) or "px" (pixels). * If p_style is given, it is the content of a "style" attribute, containing CSS attributes. If "width" and "heigth" attributes are found there, they will override p_size and p_sizeUnit. ''' importer = None # Is there someting to import? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Guess document format if isinstance(content, FileWrapper): format = content.mimeType elif hasattr(content, 'filename') and content.filename: format = os.path.splitext(content.filename)[1][1:] content = content.data if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if mimeTypesExts.has_key(format): format = mimeTypesExts[format] isImage = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True elif (format in self.imageFormats) or not format: # If the format can't be guessed, we suppose it is an image. importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self) # Initialise image-specific parameters if isImage: imp.setImageInfo(anchor, wrapInPara, size, sizeUnit, style) res = imp.run() return res
def prepareFolders(self): # Check if I can write the result if not self.overwriteExisting and os.path.exists(self.result): raise PodError(RESULT_FILE_EXISTS % self.result) try: f = open(self.result, 'w') f.write('Hello') f.close() except OSError, oe: raise PodError(CANT_WRITE_RESULT % (self.result, oe))
def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara and p_size are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in centimeters. If not specified, size will be computed from image info.''' ns = self.currentParser.env.namespaces importer = None # Is there someting to import? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Guess document format if isinstance(content, FileWrapper): format = content.mimeType if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if mimeTypesExts.has_key(format): format = mimeTypesExts[format] isImage = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True elif format in self.imageFormats: importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self.tempFolder, ns, self.fileNames) # Initialise image-specific parameters if isImage: imp.setImageInfo(anchor, wrapInPara, size) res = imp.run() return res
def finalize(self): '''Re-zip the result and potentially call LibreOffice if target format is not among self.templateTypes or if forceOoCall is True.''' for innerFile in ('content.xml', 'styles.xml'): shutil.copy(os.path.join(self.tempFolder, innerFile), os.path.join(self.unzipFolder, innerFile)) # Insert dynamic styles contentXml = os.path.join(self.unzipFolder, 'content.xml') f = open(contentXml, 'r+', encoding='utf-8') dynamicStyles = ''.join(self.dynamicStyles) content = f.read().replace('<!DYNAMIC_STYLES!>', dynamicStyles) f.seek(0) f.truncate(0) f.write(content) f.close() # Call the user-defined "finalize" function when present if self.finalizeFunction: try: self.finalizeFunction(self.unzipFolder) except Exception as e: print((WARNING_FINALIZE_ERROR % str(e))) # Re-zip the result, first as an OpenDocument file of the same type as # the POD template (odt, ods...) resultExt = self.getTemplateType() resultName = os.path.join(self.tempFolder, 'result.%s' % resultExt) zip(resultName, self.unzipFolder, odf=True) resultType = os.path.splitext(self.result)[1].strip('.') if (resultType in self.templateTypes) and not self.forceOoCall: # Simply move the ODT result to the result os.rename(resultName, self.result) else: if resultType not in FILE_TYPES: raise PodError(BAD_RESULT_TYPE % (self.result, FILE_TYPES.keys())) # Call LibreOffice to perform the conversion or document update. output = self.callLibreOffice(resultName, resultType) # I (should) have the result. Move it to the correct name. resPrefix = os.path.splitext(resultName)[0] if resultType in self.templateTypes: # converter.py has (normally!) created a second file # suffixed .res.[resultType] finalResultName = '%s.res.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): finalResultName = resultName # In this case OO in server mode could not be called to # update indexes, sections, etc. else: finalResultName = '%s.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): raise PodError(CONVERT_ERROR % output) os.rename(finalResultName, self.result)
def importPod(self, content=None, at=None, format='odt', context=None, pageBreakBefore=False, pageBreakAfter=False): '''Similar to m_importDocument, but allows to import the result of executing the POD template specified in p_content or p_at, and include it in the POD result.''' # Is there a pod template defined? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # If the POD template is specified as a Zope file, convert it into a # Appy FileWrapper. if content.__class__.__name__ == 'File': content = FileWrapper(content) imp = PodImporter(content, at, format, self) self.forceOoCall = True # Define the context to use: either the current context of the current # POD renderer, or p_context if given. if context: ctx = context else: ctx = self.contentParser.env.context imp.init(ctx, pageBreakBefore, pageBreakAfter) return imp.run()
def run(self): # Split the PDF into images with Ghostscript imagesFolder = os.path.dirname(self.importPath) cmd = 'gs -dNOPAUSE -dBATCH -sDEVICE=jpeg -r125x125 ' \ '-sOutputFile=%s/%s%%d.jpg %s' % \ (imagesFolder, self.imagePrefix, self.importPath) os.system(cmd) # Check that at least one image was generated succeeded = False firstImage = '%s1.jpg' % self.imagePrefix for fileName in os.listdir(imagesFolder): if fileName == firstImage: succeeded = True break if not succeeded: raise PodError(PDF_TO_IMG_ERROR) # Insert images into the result. noMoreImages = False i = 0 while not noMoreImages: i += 1 nextImage = '%s/%s%d.jpg' % (imagesFolder, self.imagePrefix, i) if os.path.exists(nextImage): # Use internally an Image importer for doing this job. imgImporter =ImageImporter(None, nextImage, 'jpg',self.renderer) imgImporter.setImageInfo('paragraph', True, None, None, None) self.res += imgImporter.run() os.remove(nextImage) else: noMoreImages = True return self.res
def createPodParser(self, odtFile, context, inserts=None): '''Creates the parser with its environment for parsing the given p_odtFile (content.xml or styles.xml). p_context is given by the pod user, while p_inserts depends on the ODT file we must parse.''' # The default evaluation context evalContext = {} if hasattr(context, '__dict__'): evalContext.update(context.__dict__) elif isinstance(context, dict) or isinstance(context, UserDict): evalContext.update(context) else: raise PodError(BAD_CONTEXT) # Incorporate the default, unalterable, context evalContext.update({'xhtml': self.renderXhtml, 'test': self.evalIfExpression, 'document': self.importDocument, 'pod': self.importPod, 'TableProperties': TableProperties, 'BulletedProperties': BulletedProperties, 'NumberedProperties': NumberedProperties, 'pageBreak': self.insertPageBreak, 'columnBreak': self.insertColumnBreak, # Variables to use for representing pod-reserved chars 'PIPE': '|', 'SEMICOLON': ';'}) # Developer, forget the following line if '_ctx_' not in evalContext: evalContext['_ctx_'] = evalContext env = PodEnvironment(evalContext, inserts) fileBuffer = FileBuffer(env, os.path.join(self.tempFolder,odtFile)) env.currentBuffer = fileBuffer return PodParser(env, self)
def setImageInfo(self, anchor, wrapInPara, size): # Initialise anchor if anchor not in self.anchorTypes: raise PodError(self.WRONG_ANCHOR % str(self.anchorTypes)) self.anchor = anchor self.wrapInPara = wrapInPara self.size = size
def callLibreOffice(self, resultName, resultType): '''Call LibreOffice in server mode to convert or update the result.''' loOutput = '' try: if (not isinstance(self.ooPort, int)) and \ (not isinstance(self.ooPort, int)): raise PodError(BAD_OO_PORT % str(self.ooPort)) try: from appy.pod.converter import Converter, ConverterError try: Converter(resultName, resultType, self.ooPort, self.stylesTemplate).run() except ConverterError as ce: raise PodError(CONVERT_ERROR % str(ce)) except ImportError: # I do not have UNO. So try to launch a UNO-enabled Python # interpreter which should be in self.pyPath. if not self.pyPath: raise PodError(NO_PY_PATH % resultType) if self.pyPath.find(' ') != -1: raise PodError(BLANKS_IN_PATH % self.pyPath) if not os.path.isfile(self.pyPath): raise PodError(PY_PATH_NOT_FILE % self.pyPath) if resultName.find(' ') != -1: qResultName = '"%s"' % resultName else: qResultName = resultName convScript = '%s/converter.py' % \ os.path.dirname(appy.pod.__file__) if convScript.find(' ') != -1: convScript = '"%s"' % convScript cmd = '%s %s %s %s -p%d' % \ (self.pyPath, convScript, qResultName, resultType, self.ooPort) if self.stylesTemplate: cmd += ' -t%s' % self.stylesTemplate loOutput = executeCommand(cmd) except PodError as pe: # When trying to call LO in server mode for producing ODT or ODS # (=forceOoCall=True), if an error occurs we have nevertheless # an ODT or ODS to return to the user. So we produce a warning # instead of raising an error. if (resultType in self.templateTypes) and self.forceOoCall: print((WARNING_INCOMPLETE_OD % str(pe))) else: raise pe return loOutput
def run(self): # Convert the document into PDF with LibreOffice output = self.renderer.callLibreOffice(self.importPath, 'pdf') if output: raise PodError(TO_PDF_ERROR % output) pdfFile = '%s.pdf' % os.path.splitext(self.importPath)[0] # Launch a PdfImporter to import this PDF into the POD result. pdfImporter = PdfImporter(None, pdfFile, 'pdf', self.renderer) return pdfImporter.run()
def checkAt(self, at, raiseOnError=True): '''Check and apply some transform to p_at''' # Resolve relative path if at.startswith('./'): at = os.path.join(os.getcwd(), at[2:]) # Checks that p_at corresponds to an existing file if given if raiseOnError and not os.path.isfile(at): raise PodError(FILE_NOT_FOUND % at) return at
def callLibreOffice(self, resultName, resultType): '''Call LibreOffice in server mode to convert or update the result''' loOutput = '' try: if (not isinstance(self.ooPort, int)) and \ (not isinstance(self.ooPort, long)): raise PodError(BAD_OO_PORT % str(self.ooPort)) try: from appy.pod.converter import Converter, ConverterError try: Converter(resultName, resultType, self.ooPort, self.stylesTemplate, self.optimalColumnWidths, self.script).run() except ConverterError as ce: raise PodError(CONVERT_ERROR % str(ce)) except ImportError: # I do not have UNO. So try to launch a UNO-enabled Python # interpreter which should be in self.pyPath. if not self.pyPath: raise PodError(NO_PY_PATH % resultType) if not os.path.isfile(self.pyPath): raise PodError(PY_PATH_NOT_FILE % self.pyPath) convScript = '%s/converter.py' % \ os.path.dirname(appy.pod.__file__) cmd = [self.pyPath, convScript, resultName, resultType, '-p%d' % self.ooPort] if self.stylesTemplate: cmd.append('-t%s' % self.stylesTemplate) if self.optimalColumnWidths: cmd.append('-o') cmd.append('%s' % str(self.optimalColumnWidths)) if self.script: cmd.append('-s') cmd.append('%s' % self.script) out, loOutput = utils.executeCommand(cmd) except PodError as pe: # When trying to call LO in server mode for producing ODT or ODS # (=forceOoCall=True), if an error occurs we have nevertheless # an ODT or ODS to return to the user. So we produce a warning # instead of raising an error. if (resultType in self.templateTypes) and self.forceOoCall: print(WARNING_INCOMPLETE_OD % str(pe)) else: raise pe return loOutput
def callOpenOffice(self, resultOdtName, resultType): '''Call Open Office in server mode to convert or update the ODT result.''' ooOutput = '' try: if (not isinstance(self.ooPort, int)) and \ (not isinstance(self.ooPort, long)): raise PodError(BAD_OO_PORT % str(self.ooPort)) try: from appy.pod.converter import Converter, ConverterError try: Converter(resultOdtName, resultType, self.ooPort).run() except ConverterError, ce: raise PodError(CONVERT_ERROR % str(ce)) except ImportError: # I do not have UNO. So try to launch a UNO-enabled Python # interpreter which should be in self.pyPath. if not self.pyPath: raise PodError(NO_PY_PATH % resultType) if self.pyPath.find(' ') != -1: raise PodError(BLANKS_IN_PATH % self.pyPath) if not os.path.isfile(self.pyPath): raise PodError(PY_PATH_NOT_FILE % self.pyPath) if resultOdtName.find(' ') != -1: qResultOdtName = '"%s"' % resultOdtName else: qResultOdtName = resultOdtName convScript = '%s/converter.py' % \ os.path.dirname(appy.pod.__file__) if convScript.find(' ') != -1: convScript = '"%s"' % convScript cmd = '%s %s %s %s -p%d' % \ (self.pyPath, convScript, qResultOdtName, resultType, self.ooPort) ooOutput = executeCommand(cmd) except PodError, pe: # When trying to call OO in server mode for producing # ODT (=forceOoCall=True), if an error occurs we still # have an ODT to return to the user. So we produce a # warning instead of raising an error. if (resultType == 'odt') and self.forceOoCall: print WARNING_INCOMPLETE_ODT % str(pe) else: raise pe
def run(self): # This feature is only available in the open source version if utils.commercial: raise utils.CommercialError() # Convert the document into PDF with LibreOffice output = self.renderer.callLibreOffice(self.importPath, 'pdf') if output: raise PodError(TO_PDF_ERROR % output) pdfFile = '%s.pdf' % os.path.splitext(self.importPath)[0] # Launch a PdfImporter to import this PDF into the POD result pdfImporter = PdfImporter(None, pdfFile, 'pdf', self.renderer) return pdfImporter.run()
def prepareFolders(self): # Check if I can write the result if not self.overwriteExisting and os.path.exists(self.result): raise PodError(RESULT_FILE_EXISTS % self.result) try: f = open(self.result, 'w') f.write('Hello') f.close() except OSError as oe: raise PodError(CANT_WRITE_RESULT % (self.result, oe)) except IOError as ie: raise PodError(CANT_WRITE_RESULT % (self.result, ie)) self.result = os.path.abspath(self.result) os.remove(self.result) # Create a temp folder for storing temporary files absResult = os.path.abspath(self.result) self.tempFolder = '%s.%f' % (absResult, time.time()) try: os.mkdir(self.tempFolder) except OSError as oe: raise PodError(CANT_WRITE_TEMP_FOLDER % (self.result, oe))
def createPodParser(self, odtFile, context, inserts): '''Creates the parser with its environment for parsing the given p_odtFile (content.xml or styles.xml). p_context is given by the pod user, while p_inserts depends on the ODT file we must parse.''' evalContext = {'xhtml': self.renderXhtml, 'text': self.renderText, 'test': self.evalIfExpression, 'document': self.importDocument} # Default context if hasattr(context, '__dict__'): evalContext.update(context.__dict__) elif isinstance(context, dict) or isinstance(context, UserDict): evalContext.update(context) else: raise PodError(BAD_CONTEXT) env = PodEnvironment(evalContext, inserts) fileBuffer = FileBuffer(env, os.path.join(self.tempFolder,odtFile)) env.currentBuffer = fileBuffer return PodParser(env, self)
def setImageInfo(self, anchor, wrapInPara, size, sizeUnit, style): # Initialise anchor if anchor not in self.anchorTypes: raise PodError(self.WRONG_ANCHOR % str(self.anchorTypes)) self.anchor = anchor self.wrapInPara = wrapInPara self.size = size self.sizeUnit = sizeUnit # Put CSS attributes from p_style in a dict. self.cssAttrs = {} if style: for attr in style.split(';'): if not attr.strip(): continue name, value = attr.strip().split(':') value = value.strip() if value.endswith('px'): value = value[:-2] if value.isdigit(): value=int(value) self.cssAttrs[name.strip()] = value
def run(self): # This feature is only available in the open source version if utils.commercial: raise utils.CommercialError() imagePrefix = os.path.splitext(os.path.basename(self.importPath))[0] # Split the PDF into images with Ghostscript. Create a sub-folder in the # OS temp folder to store those images. imagesFolder = getOsTempFolder(sub=True) device = 'png16m' ext = PdfImporter.gsDevices[device] dpi = '125' cmd = [ 'gs', '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=%s' % device, '-r%s' % dpi, '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=%s/%s%%d.%s' % (imagesFolder, imagePrefix, ext), self.importPath ] utils.executeCommand(cmd) # Check that at least one image was generated succeeded = False firstImage = '%s1.%s' % (imagePrefix, ext) for fileName in os.listdir(imagesFolder): if fileName == firstImage: succeeded = True break if not succeeded: raise PodError(PDF_TO_IMG_ERROR) # Insert images into the result noMoreImages = False i = 0 while not noMoreImages: i += 1 nextImage = '%s/%s%d.%s' % (imagesFolder, imagePrefix, i, ext) if os.path.exists(nextImage): # Use internally an Image importer for doing this job imgImporter = ImageImporter(None, nextImage, ext, self.renderer) imgImporter.init('paragraph', True, None, None, None, True, None) self.res += imgImporter.run() os.remove(nextImage) else: noMoreImages = True os.rmdir(imagesFolder) return self.res
def init(self, anchor, wrapInPara, size, sizeUnit, cssAttrs, keepRatio, convertOptions): '''ImageImporter-specific constructor''' # Initialise anchor if anchor not in self.anchorTypes: raise PodError(self.WRONG_ANCHOR % str(self.anchorTypes)) self.anchor = anchor self.wrapInPara = wrapInPara self.size = size self.sizeUnit = sizeUnit self.keepRatio = keepRatio self.convertOptions = convertOptions # CSS attributes self.cssAttrs = cssAttrs if cssAttrs: w = getattr(self.cssAttrs, 'width', None) h = getattr(self.cssAttrs, 'height', None) if w and h: self.sizeUnit = w.unit self.size = (w.value, h.value) # Call imagemagick to perform a custom conversion if required options = self.convertOptions image = None transformed = False if options: # This feature is only available in the open source version if utils.commercial: raise utils.CommercialError() if callable(options): # It is a function image = Image(self.importPath, self.format) options = self.convertOptions(image) if options: # Ensure we have the right to modify the [email protected] cmd = ['convert', self.importPath] + options.split() + \ [self.importPath] out, err = utils.executeCommand(cmd) if err: raise Exception(CONVERT_ERROR) transformed = True # Avoid creating an Image instance twice if no transformation occurred if image and not transformed: self.image = image else: self.image = Image(self.importPath, self.format)
def __init__(self, content, at, format, renderer): self.content = content # If content is None, p_at tells us where to find it (file system path, # url, etc) self.at = at # Ensure this path exists, if it is a local path. if at and not at.startswith('http') and not os.path.isfile(at): raise PodError(FILE_NOT_FOUND % at) self.format = format self.res = u'' self.renderer = renderer self.ns = renderer.currentParser.env.namespaces # Unpack some useful namespaces self.textNs = self.ns[OdfEnvironment.NS_TEXT] self.linkNs = self.ns[OdfEnvironment.NS_XLINK] self.drawNs = self.ns[OdfEnvironment.NS_DRAW] self.svgNs = self.ns[OdfEnvironment.NS_SVG] self.tempFolder = renderer.tempFolder self.importFolder = self.getImportFolder() # Create the import folder if it does not exist. if not os.path.exists(self.importFolder): os.mkdir(self.importFolder) self.importPath = self.getImportPath(at, format) # A link to the global fileNames dict (explained in renderer.py) self.fileNames = renderer.fileNames if at: # Move the file within the ODT, if it is an image and if this image # has not already been imported. self.importPath = self.moveFile(at, self.importPath) else: # We need to dump the file content (in self.content) in a temp file # first. self.content may be binary, a file handler or a # FileWrapper. if isinstance(self.content, FileWrapper): self.content.dump(self.importPath) else: if isinstance(self.content, file): fileContent = self.content.read() else: fileContent = self.content f = file(self.importPath, 'wb') f.write(fileContent) f.close()
def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None, sizeUnit='cm', style=None, pageBreakBefore=False, pageBreakAfter=False): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara and p_size, p_sizeUnit and p_style are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in p_sizeUnit (see below). If not specified, size will be computed from image info; * p_sizeUnit is the unit for p_size elements, it can be "cm" (centimeters), "px" (pixels) or "pc" (percentage). Percentages, in p_size, must be expressed as integers from 1 to 100. * if p_style is given, it is the content of a "style" attribute, containing CSS attributes. If "width" and "heigth" attributes are found there, they will override p_size and p_sizeUnit. p_pageBreakBefore and p_pageBreakAfter are only relevant for import of external odt documents, and allows to insert a page break before/after the inserted document. ''' importer = None # Is there someting to import? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Convert Zope files into Appy wrappers. if content.__class__.__name__ in ('File', 'Image'): content = FileWrapper(content) # Guess document format if isinstance(content, FileWrapper): format = content.mimeType if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if format in mimeTypesExts: format = mimeTypesExts[format] isImage = False isOdt = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True isOdt = True elif (format in self.imageFormats) or not format: # If the format can't be guessed, we suppose it is an image. importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter elif format in self.convertibleFormats: importer = ConvertImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self) # Initialise image-specific parameters if isImage: imp.init(anchor, wrapInPara, size, sizeUnit, style) elif isOdt: imp.init(pageBreakBefore, pageBreakAfter) return imp.run()
def finalize(self): '''Re-zip the result and potentially call LibreOffice if target format is not among self.templateTypes or if forceOoCall is True.''' j = os.path.join # If page styles must be renamed, create a dict mapping old > new names pageStyles = None if self.renamePageStyles: pageStyles = {} for name in self.stylesManager.stylesParser.env.pageStyleNames: pageStyles[name] = 'S%s' % getUuid(removeDots=True) for name in ('content', 'styles'): # Copy the [content|styles].xml file from the temp to the zip folder fn = '%s.xml' % name shutil.copy(j(self.tempFolder, fn), j(self.unzipFolder, fn)) # For styles.xml, complete dynamic styles with default styles for # bulleted and numbered lists. ds = self.dynamicStyles[name] if name == 'styles': env = self.stylesParser.env n = {'text': env.ns(env.NS_TEXT), 'style': env.ns(env.NS_STYLE)} ds.insert(0,NumberedProperties().dumpStyle('podNumberedList',n)) ds.insert(0,BulletedProperties().dumpStyle('podBulletedList',n)) # Get the file content and inject dynamic styles into it fn = os.path.join(self.unzipFolder, fn) f = open(fn) content = f.read().replace('<!DYNAMIC_STYLES!>', b''.join(ds).decode()) # Rename the page styles if pageStyles: part = PAGE_STYLES_PARTS[name] for old, new in pageStyles.iteritems(): content = content.replace(part % old, part % new) f.close() # Write the updated content to the file f = open(fn, 'w') f.write(content) f.close() # Call the user-defined "finalize" function when present if self.finalizeFunction: try: self.finalizeFunction(self.unzipFolder, self) except Exception as e: print(WARNING_FINALIZE_ERROR % str(e)) # Re-zip the result, first as an OpenDocument file of the same type as # the POD template (odt, ods...) resultExt = self.getTemplateType() resultName = os.path.join(self.tempFolder, 'result.%s' % resultExt) zip(resultName, self.unzipFolder, odf=True) resultType = os.path.splitext(self.result)[1].strip('.') if (resultType in self.templateTypes) and not self.forceOoCall: # Simply move the ODT result to the result os.rename(resultName, self.result) else: if resultType not in FILE_TYPES: raise PodError(BAD_RESULT_TYPE % ( self.result, FILE_TYPES.keys())) # Call LibreOffice to perform the conversion or document update output = self.callLibreOffice(resultName, resultType) # I (should) have the result. Move it to the correct name. resPrefix = os.path.splitext(resultName)[0] if resultType in self.templateTypes: # converter.py has (normally!) created a second file # suffixed .res.[resultType] finalResultName = '%s.res.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): finalResultName = resultName # In this case OO in server mode could not be called to # update indexes, sections, etc. else: finalResultName = '%s.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): raise PodError(CONVERT_ERROR % output) os.rename(finalResultName, self.result)
def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None, sizeUnit='cm', style=None, keepRatio=True, pageBreakBefore=False, pageBreakAfter=False, convertOptions=None): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara, p_size, p_sizeUnit, p_style and p_keepRatio are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in p_sizeUnit (see below). If not specified, size will be computed from image info; * p_sizeUnit is the unit for p_size elements, it can be "cm" (centimeters), "px" (pixels) or "pc" (percentage). Percentages, in p_size, must be expressed as integers from 1 to 100. * if p_style is given, it a appy.shared.css.CssStyles instance, containing CSS attributes. If "width" and "heigth" attributes are found there, they will override p_size and p_sizeUnit. * If p_keepRatio is True, the image width/height ratio will be kept when p_size is specified. p_pageBreakBefore and p_pageBreakAfter are only relevant for importing external odt documents, and allows to insert a page break before/after the inserted document. More precisely, each of these parameters can have values: * True insert a page break; * False do no insert a page break; moreover, p_pageBreakAfter can have this additional parameter: * 'duplex' insert 2 page breaks if the sub-document has an odd number of pages, 1 else (useful for duplex printing). If p_convertOptions are given (for images only), imagemagick will be called with these options to perform some transformation on the image. For example, if you specify convertOptions="-rotate 90" pod will perform this command before importing the file into the result: convert your.image -rotate 90 your.image You can also specify a function in convertOptions. This function will receive a single arg, "image", an instance of appy.pod.doc_importers.Image giving some characteristics of the image to convert, like image.width and image.height in pixels (integers). If your function does not return a string containing the convert options, no conversion will occur. ''' importer = None # Is there someting to import ? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Convert Zope files into Appy wrappers if content.__class__.__name__ in ('File', 'Image'): content = utils.FileWrapper(content) # Guess document format if isinstance(content, utils.FileWrapper): format = content.mimeType if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if format in utils.mimeTypesExts: format = utils.mimeTypesExts[format] isImage = False isOdt = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True isOdt = True elif (format in self.imageFormats) or not format: # If the format can't be guessed, we suppose it is an image importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter elif format in self.convertibleFormats: importer = ConvertImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self) # Initialise image-specific parameters if isImage: imp.init(anchor, wrapInPara, size, sizeUnit, style, keepRatio, convertOptions) elif isOdt: imp.init(pageBreakBefore, pageBreakAfter) return imp.run()
f = open(self.result, 'w') f.write('Hello') f.close() except OSError, oe: raise PodError(CANT_WRITE_RESULT % (self.result, oe)) except IOError, ie: raise PodError(CANT_WRITE_RESULT % (self.result, ie)) self.result = os.path.abspath(self.result) os.remove(self.result) # Create a temp folder for storing temporary files absResult = os.path.abspath(self.result) self.tempFolder = '%s.%f' % (absResult, time.time()) try: os.mkdir(self.tempFolder) except OSError, oe: raise PodError(CANT_WRITE_TEMP_FOLDER % (self.result, oe)) def patchManifest(self): '''Declares, in META-INF/manifest.xml, images or files included via the "do... from document" statements if any.''' if self.fileNames: j = os.path.join toInsert = '' for fileName in self.fileNames.iterkeys(): if fileName.endswith('.svg'): fileName = os.path.splitext(fileName)[0] + '.png' mimeType = mimetypes.guess_type(fileName)[0] toInsert += ' <manifest:file-entry manifest:media-type="%s" ' \ 'manifest:full-path="%s"/>\n' % (mimeType, fileName) manifestName = j(self.unzipFolder, j('META-INF', 'manifest.xml')) f = file(manifestName)
class Renderer: 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) def createPodParser(self, odtFile, context, inserts): '''Creates the parser with its environment for parsing the given p_odtFile (content.xml or styles.xml). p_context is given by the pod user, while p_inserts depends on the ODT file we must parse.''' evalContext = { 'xhtml': self.renderXhtml, 'text': self.renderText, 'test': self.evalIfExpression, 'document': self.importDocument } # Default context if hasattr(context, '__dict__'): evalContext.update(context.__dict__) elif isinstance(context, dict) or isinstance(context, UserDict): evalContext.update(context) else: raise PodError(BAD_CONTEXT) env = PodEnvironment(evalContext, inserts) fileBuffer = FileBuffer(env, os.path.join(self.tempFolder, odtFile)) env.currentBuffer = fileBuffer return PodParser(env, self) def renderXhtml(self, xhtmlString, encoding='utf-8', stylesMapping={}): '''Method that can be used (under the name 'xhtml') into a pod template for converting a chunk of XHTML content (p_xhtmlString) into a chunk of ODT content.''' stylesMapping = self.stylesManager.checkStylesMapping(stylesMapping) ns = self.currentParser.env.namespaces # xhtmlString can only be a chunk of XHTML. So we must surround it a # tag in order to get a XML-compliant file (we need a root tag). if xhtmlString == None: xhtmlString = '' xhtmlContent = '<p>%s</p>' % xhtmlString return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager, stylesMapping, ns).run() def renderText(self, text, encoding='utf-8', stylesMapping={}): '''Method that can be used (under the name 'text') into a pod template for inserting a text containing carriage returns.''' if text == None: text = '' text = cgi.escape(text).replace('\r\n', '<br/>').replace('\n', '<br/>') return self.renderXhtml(text, encoding, stylesMapping) def evalIfExpression(self, condition, ifTrue, ifFalse): '''This method implements the method 'test' which is proposed in the default pod context. It represents an 'if' expression (as opposed to the 'if' statement): depending on p_condition, expression result is p_ifTrue or p_ifFalse.''' if condition: return ifTrue return ifFalse imageFormats = ('png', 'jpeg', 'jpg', 'gif') ooFormats = ('odt', ) def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara and p_size are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in centimeters. If not specified, size will be computed from image info.''' ns = self.currentParser.env.namespaces importer = None # Is there someting to import? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Guess document format if isinstance(content, FileWrapper): format = content.mimeType if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if mimeTypesExts.has_key(format): format = mimeTypesExts[format] isImage = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True elif format in self.imageFormats: importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self.tempFolder, ns, self.fileNames) # Initialise image-specific parameters if isImage: imp.setImageInfo(anchor, wrapInPara, size) res = imp.run() return res def prepareFolders(self): # Check if I can write the result if not self.overwriteExisting and os.path.exists(self.result): raise PodError(RESULT_FILE_EXISTS % self.result) try: f = open(self.result, 'w') f.write('Hello') f.close() except OSError, oe: raise PodError(CANT_WRITE_RESULT % (self.result, oe)) except IOError, ie: raise PodError(CANT_WRITE_RESULT % (self.result, ie))
class Renderer: templateTypes = ('odt', 'ods') # Types of POD templates 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 = [] def createPodParser(self, odtFile, context, inserts): '''Creates the parser with its environment for parsing the given p_odtFile (content.xml or styles.xml). p_context is given by the pod user, while p_inserts depends on the ODT file we must parse.''' evalContext = {'xhtml': self.renderXhtml, 'text': self.renderText, 'test': self.evalIfExpression, 'document': self.importDocument, 'pod': self.importPod, 'pageBreak': self.insertPageBreak} # Default context if hasattr(context, '__dict__'): evalContext.update(context.__dict__) elif isinstance(context, dict) or isinstance(context, UserDict): evalContext.update(context) else: raise PodError(BAD_CONTEXT) env = PodEnvironment(evalContext, inserts) fileBuffer = FileBuffer(env, os.path.join(self.tempFolder,odtFile)) env.currentBuffer = fileBuffer return PodParser(env, self) def renderXhtml(self, xhtmlString, encoding='utf-8', stylesMapping={}): '''Method that can be used (under the name 'xhtml') into a pod template for converting a chunk of XHTML content (p_xhtmlString) into a chunk of ODT content.''' stylesMapping = self.stylesManager.checkStylesMapping(stylesMapping) # xhtmlString can only be a chunk of XHTML. So we must surround it with # a tag in order to get a XML-compliant file (we need a root tag). if xhtmlString == None: xhtmlString = '' xhtmlContent = '<p>%s</p>' % xhtmlString return Xhtml2OdtConverter(xhtmlContent, encoding, self.stylesManager, stylesMapping, self).run() def renderText(self, text, encoding='utf-8', stylesMapping={}): '''Obsolete method.''' raise Exception(OBSOLETE_RENDER_TEXT) def evalIfExpression(self, condition, ifTrue, ifFalse): '''This method implements the method 'test' which is proposed in the default pod context. It represents an 'if' expression (as opposed to the 'if' statement): depending on p_condition, expression result is p_ifTrue or p_ifFalse.''' if condition: return ifTrue return ifFalse imageFormats = ('png', 'jpeg', 'jpg', 'gif', 'svg') ooFormats = ('odt',) convertibleFormats = FILE_TYPES.keys() def importDocument(self, content=None, at=None, format=None, anchor='as-char', wrapInPara=True, size=None, sizeUnit='cm', style=None, pageBreakBefore=False, pageBreakAfter=False): '''If p_at is not None, it represents a path or url allowing to find the document. If p_at is None, the content of the document is supposed to be in binary format in p_content. The document p_format may be: odt or any format in imageFormats. p_anchor, p_wrapInPara and p_size, p_sizeUnit and p_style are only relevant for images: * p_anchor defines the way the image is anchored into the document; Valid values are 'page','paragraph', 'char' and 'as-char'; * p_wrapInPara, if true, wraps the resulting 'image' tag into a 'p' tag; * p_size, if specified, is a tuple of float or integers (width, height) expressing size in p_sizeUnit (see below). If not specified, size will be computed from image info; * p_sizeUnit is the unit for p_size elements, it can be "cm" (centimeters) or "px" (pixels); * if p_style is given, it is the content of a "style" attribute, containing CSS attributes. If "width" and "heigth" attributes are found there, they will override p_size and p_sizeUnit. p_pageBreakBefore and p_pageBreakAfter are only relevant for import of external odt documents, and allows to insert a page break before/after the inserted document. ''' importer = None # Is there someting to import? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # Convert Zope files into Appy wrappers. if content.__class__.__name__ in ('File', 'Image'): content = FileWrapper(content) # Guess document format if isinstance(content, FileWrapper): format = content.mimeType if not format: # It should be deduced from p_at if not at: raise PodError(DOC_FORMAT_ERROR) format = os.path.splitext(at)[1][1:] else: # If format is a mimeType, convert it to an extension if mimeTypesExts.has_key(format): format = mimeTypesExts[format] isImage = False isOdt = False if format in self.ooFormats: importer = OdtImporter self.forceOoCall = True isOdt = True elif (format in self.imageFormats) or not format: # If the format can't be guessed, we suppose it is an image. importer = ImageImporter isImage = True elif format == 'pdf': importer = PdfImporter elif format in self.convertibleFormats: importer = ConvertImporter else: raise PodError(DOC_WRONG_FORMAT % format) imp = importer(content, at, format, self) # Initialise image-specific parameters if isImage: imp.init(anchor, wrapInPara, size, sizeUnit, style) elif isOdt: imp.init(pageBreakBefore, pageBreakAfter) return imp.run() def importPod(self, content=None, at=None, format='odt', context=None, pageBreakBefore=False, pageBreakAfter=False): '''Similar to m_importDocument, but allows to import the result of executing the POD template specified in p_content or p_at, and include it in the POD result.''' # Is there a pod template defined? if not content and not at: raise PodError(DOC_NOT_SPECIFIED) # If the POD template is specified as a Zope file, convert it into a # Appy FileWrapper. if content.__class__.__name__ == 'File': content = FileWrapper(content) imp = PodImporter(content, at, format, self) self.forceOoCall = True # Define the context to use: either the current context of the current # POD renderer, or p_context if given. if context: ctx = context else: ctx = self.contentParser.env.context imp.init(ctx, pageBreakBefore, pageBreakAfter) return imp.run() def insertPageBreak(self): '''Inserts a page break into the result.''' textNs = self.currentParser.env.namespaces[PodEnvironment.NS_TEXT] return '<%s:p %s:style-name="podPageBreak"></%s:p>' % \ (textNs, textNs, textNs) def prepareFolders(self): # Check if I can write the result if not self.overwriteExisting and os.path.exists(self.result): raise PodError(RESULT_FILE_EXISTS % self.result) try: f = open(self.result, 'w') f.write('Hello') f.close() except OSError, oe: raise PodError(CANT_WRITE_RESULT % (self.result, oe)) except IOError, ie: raise PodError(CANT_WRITE_RESULT % (self.result, ie))
def finalize(self): '''Re-zip the result and potentially call LibreOffice if target format is not among self.templateTypes or if forceOoCall is True.''' for innerFile in ('content.xml', 'styles.xml'): shutil.copy(os.path.join(self.tempFolder, innerFile), os.path.join(self.unzipFolder, innerFile)) # Insert dynamic styles contentXml = os.path.join(self.unzipFolder, 'content.xml') f = open(contentXml) dynamicStyles = ''.join(self.dynamicStyles) content = f.read().replace('<!DYNAMIC_STYLES!>', dynamicStyles) f.close() f = open(contentXml, 'w') f.write(content) f.close() # Call the user-defined "finalize" function when present. if self.finalizeFunction: try: self.finalizeFunction(self.unzipFolder) except (Exception, e): print(WARNING_FINALIZE_ERROR % str(e)) # Re-zip the result, first as an OpenDocument file of the same type as # the POD template (odt, ods...) resultExt = self.getTemplateType() resultName = os.path.join(self.tempFolder, 'result.%s' % resultExt) try: 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('.') if (resultType in self.templateTypes) and not self.forceOoCall: # Simply move the ODT result to the result os.rename(resultName, self.result) else: if resultType not in FILE_TYPES: raise PodError(BAD_RESULT_TYPE % (self.result, FILE_TYPES.keys())) # Call LibreOffice to perform the conversion or document update. output = self.callLibreOffice(resultName, resultType) # I (should) have the result. Move it to the correct name. resPrefix = os.path.splitext(resultName)[0] if resultType in self.templateTypes: # converter.py has (normally!) created a second file # suffixed .res.[resultType] finalResultName = '%s.res.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): finalResultName = resultName # In this case OO in server mode could not be called to # update indexes, sections, etc. else: finalResultName = '%s.%s' % (resPrefix, resultType) if not os.path.exists(finalResultName): raise PodError(CONVERT_ERROR % output) os.rename(finalResultName, self.result)