diff options
Diffstat (limited to 'tablib/packages/odf/opendocument.py')
| -rw-r--r-- | tablib/packages/odf/opendocument.py | 654 |
1 files changed, 0 insertions, 654 deletions
diff --git a/tablib/packages/odf/opendocument.py b/tablib/packages/odf/opendocument.py deleted file mode 100644 index 6319638..0000000 --- a/tablib/packages/odf/opendocument.py +++ /dev/null @@ -1,654 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2006-2010 Søren Roug, European Environment Agency -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -# -# Contributor(s): -# - -__doc__="""Use OpenDocument to generate your documents.""" - -import zipfile, time, sys, mimetypes, copy -from cStringIO import StringIO -from namespaces import * -import manifest, meta -from office import * -import element -from attrconverters import make_NCName -from xml.sax.xmlreader import InputSource -from odfmanifest import manifestlist - -__version__= TOOLSVERSION - -_XMLPROLOGUE = u"<?xml version='1.0' encoding='UTF-8'?>\n" - -UNIXPERMS = 0100644 << 16L # -rw-r--r-- - -IS_FILENAME = 0 -IS_IMAGE = 1 -# We need at least Python 2.2 -assert sys.version_info[0]>=2 and sys.version_info[1] >= 2 - -#sys.setrecursionlimit(100) -#The recursion limit is set conservative so mistakes like -# s=content() s.addElement(s) won't eat up too much processor time. - -odmimetypes = { - 'application/vnd.oasis.opendocument.text': '.odt', - 'application/vnd.oasis.opendocument.text-template': '.ott', - 'application/vnd.oasis.opendocument.graphics': '.odg', - 'application/vnd.oasis.opendocument.graphics-template': '.otg', - 'application/vnd.oasis.opendocument.presentation': '.odp', - 'application/vnd.oasis.opendocument.presentation-template': '.otp', - 'application/vnd.oasis.opendocument.spreadsheet': '.ods', - 'application/vnd.oasis.opendocument.spreadsheet-template': '.ots', - 'application/vnd.oasis.opendocument.chart': '.odc', - 'application/vnd.oasis.opendocument.chart-template': '.otc', - 'application/vnd.oasis.opendocument.image': '.odi', - 'application/vnd.oasis.opendocument.image-template': '.oti', - 'application/vnd.oasis.opendocument.formula': '.odf', - 'application/vnd.oasis.opendocument.formula-template': '.otf', - 'application/vnd.oasis.opendocument.text-master': '.odm', - 'application/vnd.oasis.opendocument.text-web': '.oth', -} - -class OpaqueObject: - def __init__(self, filename, mediatype, content=None): - self.mediatype = mediatype - self.filename = filename - self.content = content - -class OpenDocument: - """ A class to hold the content of an OpenDocument document - Use the xml method to write the XML - source to the screen or to a file - d = OpenDocument(mimetype) - fd.write(d.xml()) - """ - thumbnail = None - - def __init__(self, mimetype, add_generator=True): - self.mimetype = mimetype - self.childobjects = [] - self._extra = [] - self.folder = "" # Always empty for toplevel documents - self.topnode = Document(mimetype=self.mimetype) - self.topnode.ownerDocument = self - - self.clear_caches() - - self.Pictures = {} - self.meta = Meta() - self.topnode.addElement(self.meta) - if add_generator: - self.meta.addElement(meta.Generator(text=TOOLSVERSION)) - self.scripts = Scripts() - self.topnode.addElement(self.scripts) - self.fontfacedecls = FontFaceDecls() - self.topnode.addElement(self.fontfacedecls) - self.settings = Settings() - self.topnode.addElement(self.settings) - self.styles = Styles() - self.topnode.addElement(self.styles) - self.automaticstyles = AutomaticStyles() - self.topnode.addElement(self.automaticstyles) - self.masterstyles = MasterStyles() - self.topnode.addElement(self.masterstyles) - self.body = Body() - self.topnode.addElement(self.body) - - def rebuild_caches(self, node=None): - if node is None: node = self.topnode - self.build_caches(node) - for e in node.childNodes: - if e.nodeType == element.Node.ELEMENT_NODE: - self.rebuild_caches(e) - - def clear_caches(self): - self.element_dict = {} - self._styles_dict = {} - self._styles_ooo_fix = {} - - def build_caches(self, element): - """ Called from element.py - """ - if not self.element_dict.has_key(element.qname): - self.element_dict[element.qname] = [] - self.element_dict[element.qname].append(element) - if element.qname == (STYLENS, u'style'): - self.__register_stylename(element) # Add to style dictionary - styleref = element.getAttrNS(TEXTNS,u'style-name') - if styleref is not None and self._styles_ooo_fix.has_key(styleref): - element.setAttrNS(TEXTNS,u'style-name', self._styles_ooo_fix[styleref]) - - def __register_stylename(self, element): - ''' Register a style. But there are three style dictionaries: - office:styles, office:automatic-styles and office:master-styles - Chapter 14 - ''' - name = element.getAttrNS(STYLENS, u'name') - if name is None: - return - if element.parentNode.qname in ((OFFICENS,u'styles'), (OFFICENS,u'automatic-styles')): - if self._styles_dict.has_key(name): - newname = 'M'+name # Rename style - self._styles_ooo_fix[name] = newname - # From here on all references to the old name will refer to the new one - name = newname - element.setAttrNS(STYLENS, u'name', name) - self._styles_dict[name] = element - - def toXml(self, filename=''): - xml=StringIO() - xml.write(_XMLPROLOGUE) - self.body.toXml(0, xml) - if not filename: - return xml.getvalue() - else: - f=file(filename,'w') - f.write(xml.getvalue()) - f.close() - - def xml(self): - """ Generates the full document as an XML file - Always written as a bytestream in UTF-8 encoding - """ - self.__replaceGenerator() - xml=StringIO() - xml.write(_XMLPROLOGUE) - self.topnode.toXml(0, xml) - return xml.getvalue() - - - def contentxml(self): - """ Generates the content.xml file - Always written as a bytestream in UTF-8 encoding - """ - xml=StringIO() - xml.write(_XMLPROLOGUE) - x = DocumentContent() - x.write_open_tag(0, xml) - if self.scripts.hasChildNodes(): - self.scripts.toXml(1, xml) - if self.fontfacedecls.hasChildNodes(): - self.fontfacedecls.toXml(1, xml) - a = AutomaticStyles() - stylelist = self._used_auto_styles([self.styles, self.automaticstyles, self.body]) - if len(stylelist) > 0: - a.write_open_tag(1, xml) - for s in stylelist: - s.toXml(2, xml) - a.write_close_tag(1, xml) - else: - a.toXml(1, xml) - self.body.toXml(1, xml) - x.write_close_tag(0, xml) - return xml.getvalue() - - def __manifestxml(self): - """ Generates the manifest.xml file - The self.manifest isn't avaible unless the document is being saved - """ - xml=StringIO() - xml.write(_XMLPROLOGUE) - self.manifest.toXml(0,xml) - return xml.getvalue() - - def metaxml(self): - """ Generates the meta.xml file """ - self.__replaceGenerator() - x = DocumentMeta() - x.addElement(self.meta) - xml=StringIO() - xml.write(_XMLPROLOGUE) - x.toXml(0,xml) - return xml.getvalue() - - def settingsxml(self): - """ Generates the settings.xml file """ - x = DocumentSettings() - x.addElement(self.settings) - xml=StringIO() - xml.write(_XMLPROLOGUE) - x.toXml(0,xml) - return xml.getvalue() - - def _parseoneelement(self, top, stylenamelist): - """ Finds references to style objects in master-styles - and add the style name to the style list if not already there. - Recursive - """ - for e in top.childNodes: - if e.nodeType == element.Node.ELEMENT_NODE: - for styleref in ( (DRAWNS,u'style-name'), - (DRAWNS,u'text-style-name'), - (PRESENTATIONNS,u'style-name'), - (STYLENS,u'data-style-name'), - (STYLENS,u'list-style-name'), - (STYLENS,u'page-layout-name'), - (STYLENS,u'style-name'), - (TABLENS,u'default-cell-style-name'), - (TABLENS,u'style-name'), - (TEXTNS,u'style-name') ): - if e.getAttrNS(styleref[0],styleref[1]): - stylename = e.getAttrNS(styleref[0],styleref[1]) - if stylename not in stylenamelist: - stylenamelist.append(stylename) - stylenamelist = self._parseoneelement(e, stylenamelist) - return stylenamelist - - def _used_auto_styles(self, segments): - """ Loop through the masterstyles elements, and find the automatic - styles that are used. These will be added to the automatic-styles - element in styles.xml - """ - stylenamelist = [] - for top in segments: - stylenamelist = self._parseoneelement(top, stylenamelist) - stylelist = [] - for e in self.automaticstyles.childNodes: - if e.getAttrNS(STYLENS,u'name') in stylenamelist: - stylelist.append(e) - return stylelist - - def stylesxml(self): - """ Generates the styles.xml file """ - xml=StringIO() - xml.write(_XMLPROLOGUE) - x = DocumentStyles() - x.write_open_tag(0, xml) - if self.fontfacedecls.hasChildNodes(): - self.fontfacedecls.toXml(1, xml) - self.styles.toXml(1, xml) - a = AutomaticStyles() - a.write_open_tag(1, xml) - for s in self._used_auto_styles([self.masterstyles]): - s.toXml(2, xml) - a.write_close_tag(1, xml) - if self.masterstyles.hasChildNodes(): - self.masterstyles.toXml(1, xml) - x.write_close_tag(0, xml) - return xml.getvalue() - - def addPicture(self, filename, mediatype=None, content=None): - """ Add a picture - It uses the same convention as OOo, in that it saves the picture in - the zipfile in the subdirectory 'Pictures' - If passed a file ptr, mediatype must be set - """ - if content is None: - if mediatype is None: - mediatype, encoding = mimetypes.guess_type(filename) - if mediatype is None: - mediatype = '' - try: ext = filename[filename.rindex('.'):] - except: ext='' - else: - ext = mimetypes.guess_extension(mediatype) - manifestfn = "Pictures/%0.0f%s" % ((time.time()*10000000000), ext) - self.Pictures[manifestfn] = (IS_FILENAME, filename, mediatype) - else: - manifestfn = filename - self.Pictures[manifestfn] = (IS_IMAGE, content, mediatype) - return manifestfn - - def addPictureFromFile(self, filename, mediatype=None): - """ Add a picture - It uses the same convention as OOo, in that it saves the picture in - the zipfile in the subdirectory 'Pictures'. - If mediatype is not given, it will be guessed from the filename - extension. - """ - if mediatype is None: - mediatype, encoding = mimetypes.guess_type(filename) - if mediatype is None: - mediatype = '' - try: ext = filename[filename.rindex('.'):] - except ValueError: ext='' - else: - ext = mimetypes.guess_extension(mediatype) - manifestfn = "Pictures/%0.0f%s" % ((time.time()*10000000000), ext) - self.Pictures[manifestfn] = (IS_FILENAME, filename, mediatype) - return manifestfn - - def addPictureFromString(self, content, mediatype): - """ Add a picture - It uses the same convention as OOo, in that it saves the picture in - the zipfile in the subdirectory 'Pictures'. The content variable - is a string that contains the binary image data. The mediatype - indicates the image format. - """ - ext = mimetypes.guess_extension(mediatype) - manifestfn = "Pictures/%0.0f%s" % ((time.time()*10000000000), ext) - self.Pictures[manifestfn] = (IS_IMAGE, content, mediatype) - return manifestfn - - def addThumbnail(self, filecontent=None): - """ Add a fixed thumbnail - The thumbnail in the library is big, so this is pretty useless. - """ - if filecontent is None: - import thumbnail - self.thumbnail = thumbnail.thumbnail() - else: - self.thumbnail = filecontent - - def addObject(self, document, objectname=None): - """ Adds an object (subdocument). The object must be an OpenDocument class - The return value will be the folder in the zipfile the object is stored in - """ - self.childobjects.append(document) - if objectname is None: - document.folder = "%s/Object %d" % (self.folder, len(self.childobjects)) - else: - document.folder = objectname - return ".%s" % document.folder - - def _savePictures(self, object, folder): - hasPictures = False - for arcname, picturerec in object.Pictures.items(): - what_it_is, fileobj, mediatype = picturerec - self.manifest.addElement(manifest.FileEntry(fullpath="%s%s" % ( folder ,arcname), mediatype=mediatype)) - hasPictures = True - if what_it_is == IS_FILENAME: - self._z.write(fileobj, arcname, zipfile.ZIP_STORED) - else: - zi = zipfile.ZipInfo(str(arcname), self._now) - zi.compress_type = zipfile.ZIP_STORED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, fileobj) - # According to section 17.7.3 in ODF 1.1, the pictures folder should not have a manifest entry -# if hasPictures: -# self.manifest.addElement(manifest.FileEntry(fullpath="%sPictures/" % folder, mediatype="")) - # Look in subobjects - subobjectnum = 1 - for subobject in object.childobjects: - self._savePictures(subobject,'%sObject %d/' % (folder, subobjectnum)) - subobjectnum += 1 - - def __replaceGenerator(self): - """ Section 3.1.1: The application MUST NOT export the original identifier - belonging to the application that created the document. - """ - for m in self.meta.childNodes[:]: - if m.qname == (METANS, u'generator'): - self.meta.removeChild(m) - self.meta.addElement(meta.Generator(text=TOOLSVERSION)) - - def save(self, outputfile, addsuffix=False): - """ Save the document under the filename. - If the filename is '-' then save to stdout - """ - if outputfile == '-': - outputfp = zipfile.ZipFile(sys.stdout,"w") - else: - if addsuffix: - outputfile = outputfile + odmimetypes.get(self.mimetype,'.xxx') - outputfp = zipfile.ZipFile(outputfile, "w") - self.__zipwrite(outputfp) - outputfp.close() - - def write(self, outputfp): - """ User API to write the ODF file to an open file descriptor - Writes the ZIP format - """ - zipoutputfp = zipfile.ZipFile(outputfp,"w") - self.__zipwrite(zipoutputfp) - - def __zipwrite(self, outputfp): - """ Write the document to an open file pointer - This is where the real work is done - """ - self._z = outputfp - self._now = time.localtime()[:6] - self.manifest = manifest.Manifest() - - # Write mimetype - zi = zipfile.ZipInfo('mimetype', self._now) - zi.compress_type = zipfile.ZIP_STORED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, self.mimetype) - - self._saveXmlObjects(self,"") - - # Write pictures - self._savePictures(self,"") - - # Write the thumbnail - if self.thumbnail is not None: - self.manifest.addElement(manifest.FileEntry(fullpath="Thumbnails/", mediatype='')) - self.manifest.addElement(manifest.FileEntry(fullpath="Thumbnails/thumbnail.png", mediatype='')) - zi = zipfile.ZipInfo("Thumbnails/thumbnail.png", self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, self.thumbnail) - - # Write any extra files - for op in self._extra: - if op.filename == "META-INF/documentsignatures.xml": continue # Don't save signatures - self.manifest.addElement(manifest.FileEntry(fullpath=op.filename, mediatype=op.mediatype)) - zi = zipfile.ZipInfo(op.filename.encode('utf-8'), self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - if op.content is not None: - self._z.writestr(zi, op.content) - # Write manifest - zi = zipfile.ZipInfo("META-INF/manifest.xml", self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, self.__manifestxml() ) - del self._z - del self._now - del self.manifest - - - def _saveXmlObjects(self, object, folder): - if self == object: - self.manifest.addElement(manifest.FileEntry(fullpath="/", mediatype=object.mimetype)) - else: - self.manifest.addElement(manifest.FileEntry(fullpath=folder, mediatype=object.mimetype)) - # Write styles - self.manifest.addElement(manifest.FileEntry(fullpath="%sstyles.xml" % folder, mediatype="text/xml")) - zi = zipfile.ZipInfo("%sstyles.xml" % folder, self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, object.stylesxml() ) - - # Write content - self.manifest.addElement(manifest.FileEntry(fullpath="%scontent.xml" % folder, mediatype="text/xml")) - zi = zipfile.ZipInfo("%scontent.xml" % folder, self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, object.contentxml() ) - - # Write settings - if object.settings.hasChildNodes(): - self.manifest.addElement(manifest.FileEntry(fullpath="%ssettings.xml" % folder, mediatype="text/xml")) - zi = zipfile.ZipInfo("%ssettings.xml" % folder, self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, object.settingsxml() ) - - # Write meta - if self == object: - self.manifest.addElement(manifest.FileEntry(fullpath="meta.xml", mediatype="text/xml")) - zi = zipfile.ZipInfo("meta.xml", self._now) - zi.compress_type = zipfile.ZIP_DEFLATED - zi.external_attr = UNIXPERMS - self._z.writestr(zi, object.metaxml() ) - - # Write subobjects - subobjectnum = 1 - for subobject in object.childobjects: - self._saveXmlObjects(subobject, '%sObject %d/' % (folder, subobjectnum)) - subobjectnum += 1 - -# Document's DOM methods - def createElement(self, element): - """ Inconvenient interface to create an element, but follows XML-DOM. - Does not allow attributes as argument, therefore can't check grammar. - """ - return element(check_grammar=False) - - def createTextNode(self, data): - """ Method to create a text node """ - return element.Text(data) - - def createCDATASection(self, data): - """ Method to create a CDATA section """ - return element.CDATASection(cdata) - - def getMediaType(self): - """ Returns the media type """ - return self.mimetype - - def getStyleByName(self, name): - """ Finds a style object based on the name """ - ncname = make_NCName(name) - if self._styles_dict == {}: - self.rebuild_caches() - return self._styles_dict.get(ncname, None) - - def getElementsByType(self, element): - """ Gets elements based on the type, which is function from text.py, draw.py etc. """ - obj = element(check_grammar=False) - if self.element_dict == {}: - self.rebuild_caches() - return self.element_dict.get(obj.qname, []) - -# Convenience functions -def OpenDocumentChart(): - """ Creates a chart document """ - doc = OpenDocument('application/vnd.oasis.opendocument.chart') - doc.chart = Chart() - doc.body.addElement(doc.chart) - return doc - -def OpenDocumentDrawing(): - """ Creates a drawing document """ - doc = OpenDocument('application/vnd.oasis.opendocument.graphics') - doc.drawing = Drawing() - doc.body.addElement(doc.drawing) - return doc - -def OpenDocumentImage(): - """ Creates an image document """ - doc = OpenDocument('application/vnd.oasis.opendocument.image') - doc.image = Image() - doc.body.addElement(doc.image) - return doc - -def OpenDocumentPresentation(): - """ Creates a presentation document """ - doc = OpenDocument('application/vnd.oasis.opendocument.presentation') - doc.presentation = Presentation() - doc.body.addElement(doc.presentation) - return doc - -def OpenDocumentSpreadsheet(): - """ Creates a spreadsheet document """ - doc = OpenDocument('application/vnd.oasis.opendocument.spreadsheet') - doc.spreadsheet = Spreadsheet() - doc.body.addElement(doc.spreadsheet) - return doc - -def OpenDocumentText(): - """ Creates a text document """ - doc = OpenDocument('application/vnd.oasis.opendocument.text') - doc.text = Text() - doc.body.addElement(doc.text) - return doc - -def OpenDocumentTextMaster(): - """ Creates a text master document """ - doc = OpenDocument('application/vnd.oasis.opendocument.text-master') - doc.text = Text() - doc.body.addElement(doc.text) - return doc - -def __loadxmlparts(z, manifest, doc, objectpath): - from load import LoadParser - from xml.sax import make_parser, handler - - for xmlfile in (objectpath+'settings.xml', objectpath+'meta.xml', objectpath+'content.xml', objectpath+'styles.xml'): - if not manifest.has_key(xmlfile): - continue - try: - xmlpart = z.read(xmlfile) - doc._parsing = xmlfile - - parser = make_parser() - parser.setFeature(handler.feature_namespaces, 1) - parser.setContentHandler(LoadParser(doc)) - parser.setErrorHandler(handler.ErrorHandler()) - - inpsrc = InputSource() - inpsrc.setByteStream(StringIO(xmlpart)) - parser.parse(inpsrc) - del doc._parsing - except KeyError, v: pass - -def load(odffile): - """ Load an ODF file into memory - Returns a reference to the structure - """ - z = zipfile.ZipFile(odffile) - mimetype = z.read('mimetype') - doc = OpenDocument(mimetype, add_generator=False) - - # Look in the manifest file to see if which of the four files there are - manifestpart = z.read('META-INF/manifest.xml') - manifest = manifestlist(manifestpart) - __loadxmlparts(z, manifest, doc, '') - for mentry,mvalue in manifest.items(): - if mentry[:9] == "Pictures/" and len(mentry) > 9: - doc.addPicture(mvalue['full-path'], mvalue['media-type'], z.read(mentry)) - elif mentry == "Thumbnails/thumbnail.png": - doc.addThumbnail(z.read(mentry)) - elif mentry in ('settings.xml', 'meta.xml', 'content.xml', 'styles.xml'): - pass - # Load subobjects into structure - elif mentry[:7] == "Object " and len(mentry) < 11 and mentry[-1] == "/": - subdoc = OpenDocument(mvalue['media-type'], add_generator=False) - doc.addObject(subdoc, "/" + mentry[:-1]) - __loadxmlparts(z, manifest, subdoc, mentry) - elif mentry[:7] == "Object ": - pass # Don't load subobjects as opaque objects - else: - if mvalue['full-path'][-1] == '/': - doc._extra.append(OpaqueObject(mvalue['full-path'], mvalue['media-type'], None)) - else: - doc._extra.append(OpaqueObject(mvalue['full-path'], mvalue['media-type'], z.read(mentry))) - # Add the SUN junk here to the struct somewhere - # It is cached data, so it can be out-of-date - z.close() - b = doc.getElementsByType(Body) - if mimetype[:39] == 'application/vnd.oasis.opendocument.text': - doc.text = b[0].firstChild - elif mimetype[:43] == 'application/vnd.oasis.opendocument.graphics': - doc.graphics = b[0].firstChild - elif mimetype[:47] == 'application/vnd.oasis.opendocument.presentation': - doc.presentation = b[0].firstChild - elif mimetype[:46] == 'application/vnd.oasis.opendocument.spreadsheet': - doc.spreadsheet = b[0].firstChild - elif mimetype[:40] == 'application/vnd.oasis.opendocument.chart': - doc.chart = b[0].firstChild - elif mimetype[:40] == 'application/vnd.oasis.opendocument.image': - doc.image = b[0].firstChild - elif mimetype[:42] == 'application/vnd.oasis.opendocument.formula': - doc.formula = b[0].firstChild - return doc - -# vim: set expandtab sw=4 : |
