xml2odf 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2006 Søren Roug, European Environment Agency
  4. #
  5. # This is free software. You may redistribute it under the terms
  6. # of the Apache license and the GNU General Public License Version
  7. # 2 or at your option any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public
  15. # License along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. #
  18. # Contributor(s):
  19. #
  20. #
  21. # OpenDocument can be a complete office document in a single
  22. # XML document. This script will take such a document and create
  23. # a package
  24. import io
  25. import zipfile,time, sys, getopt
  26. import xml.sax, xml.sax.saxutils
  27. from odf import manifest
  28. class SplitWriter:
  29. def __init__(self):
  30. self.activefiles = []
  31. self._content = []
  32. self._meta = []
  33. self._styles = []
  34. self._settings = []
  35. self.files = {'content': self._content, 'meta': self._meta,
  36. 'styles':self._styles, 'settings': self._settings }
  37. def write(self, str):
  38. for f in self.activefiles:
  39. f.append(str)
  40. def activate(self, filename):
  41. file = self.files[filename]
  42. if file not in self.activefiles:
  43. self.activefiles.append(file)
  44. def deactivate(self, filename):
  45. file = self.files[filename]
  46. if file in self.activefiles:
  47. self.activefiles.remove(file)
  48. odmimetypes = {
  49. 'application/vnd.oasis.opendocument.text': '.odt',
  50. 'application/vnd.oasis.opendocument.text-template': '.ott',
  51. 'application/vnd.oasis.opendocument.graphics': '.odg',
  52. 'application/vnd.oasis.opendocument.graphics-template': '.otg',
  53. 'application/vnd.oasis.opendocument.presentation': '.odp',
  54. 'application/vnd.oasis.opendocument.presentation-template': '.otp',
  55. 'application/vnd.oasis.opendocument.spreadsheet': '.ods',
  56. 'application/vnd.oasis.opendocument.spreadsheet-template': '.ots',
  57. 'application/vnd.oasis.opendocument.chart': '.odc',
  58. 'application/vnd.oasis.opendocument.chart-template': '.otc',
  59. 'application/vnd.oasis.opendocument.image': '.odi',
  60. 'application/vnd.oasis.opendocument.image-template': '.oti',
  61. 'application/vnd.oasis.opendocument.formula': '.odf',
  62. 'application/vnd.oasis.opendocument.formula-template': '.otf',
  63. 'application/vnd.oasis.opendocument.text-master': '.odm',
  64. 'application/vnd.oasis.opendocument.text-web': '.oth',
  65. }
  66. OFFICENS = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"
  67. base = xml.sax.saxutils.XMLGenerator
  68. class odfsplitter(base):
  69. def __init__(self):
  70. self._mimetype = ''
  71. self.output = SplitWriter()
  72. self._prefixes = []
  73. base.__init__(self, self.output, 'utf-8')
  74. def startPrefixMapping(self, prefix, uri):
  75. base.startPrefixMapping(self, prefix, uri)
  76. self._prefixes.append('xmlns:%s="%s"' % (prefix, uri))
  77. def startElementNS(self, name, qname, attrs):
  78. if name == (OFFICENS, u"document"):
  79. self._mimetype = attrs.get((OFFICENS, "mimetype"))
  80. elif name == (OFFICENS, u"meta"):
  81. self.output.activate('meta')
  82. elif name == (OFFICENS, u"settings"):
  83. self.output.activate('settings')
  84. elif name == (OFFICENS, u"scripts"):
  85. self.output.activate('content')
  86. elif name == (OFFICENS, u"font-face-decls"):
  87. self.output.activate('content')
  88. self.output.activate('styles')
  89. elif name == (OFFICENS, u"styles"):
  90. self.output.activate('styles')
  91. elif name == (OFFICENS, u"automatic-styles"):
  92. self.output.activate('content')
  93. self.output.activate('styles')
  94. elif name == (OFFICENS, u"master-styles"):
  95. self.output.activate('styles')
  96. elif name == (OFFICENS, u"body"):
  97. self.output.activate('content')
  98. base.startElementNS(self, name, qname, attrs)
  99. def endElementNS(self, name, qname):
  100. base.endElementNS(self, name, qname)
  101. if name == (OFFICENS, u"meta"):
  102. self.output.deactivate('meta')
  103. elif name == (OFFICENS, u"settings"):
  104. self.output.deactivate('settings')
  105. elif name == (OFFICENS, u"scripts"):
  106. self.output.deactivate('content')
  107. elif name == (OFFICENS, u"font-face-decls"):
  108. self.output.deactivate('content')
  109. self.output.deactivate('styles')
  110. elif name == (OFFICENS, u"styles"):
  111. self.output.deactivate('styles')
  112. elif name == (OFFICENS, u"automatic-styles"):
  113. self.output.deactivate('content')
  114. self.output.deactivate('styles')
  115. elif name == (OFFICENS, u"master-styles"):
  116. self.output.deactivate('styles')
  117. elif name == (OFFICENS, u"body"):
  118. self.output.deactivate('content')
  119. def content(self):
  120. """ Return the content inside a wrapper called <office:document-content>
  121. """
  122. prefixes = ' '.join(self._prefixes)
  123. return ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-content %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._content)) + ['</office:document-content>'])
  124. def settings(self):
  125. prefixes = ' '.join(self._prefixes).encode('utf-8')
  126. return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-settings %s office:version="1.0">' % prefixes] + self.output._settings + ['''</office:document-settings>'''])
  127. def styles(self):
  128. prefixes = ' '.join(self._prefixes)
  129. return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-styles %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._styles)) + ['''</office:document-styles>'''])
  130. def meta(self):
  131. prefixes = ' '.join(self._prefixes)
  132. return ''.join( ['<?xml version="1.0" encoding="UTF-8"?>\n<office:document-meta %s office:version="1.0">' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._meta)) + ['''</office:document-meta>'''])
  133. def usage():
  134. sys.stderr.write("Usage: %s [-o outputfile] [-s] inputfile\n" % sys.argv[0])
  135. def manifestxml(m):
  136. """ Generates the content of the manifest.xml file """
  137. xml=io.StringIO()
  138. xml.write(u"<?xml version='1.0' encoding='UTF-8'?>\n")
  139. m.toXml(0,xml)
  140. return xml.getvalue()
  141. try:
  142. opts, args = getopt.getopt(sys.argv[1:], "o:s", ["output=","suffix"])
  143. except getopt.GetoptError:
  144. usage()
  145. sys.exit(2)
  146. outputfile = '-'
  147. addsuffix = False
  148. for o, a in opts:
  149. if o in ("-o", "--output"):
  150. outputfile = a
  151. if o in ("-s", "--suffix"):
  152. addsuffix = True
  153. if len(args) > 1:
  154. usage()
  155. sys.exit(2)
  156. odfs = odfsplitter()
  157. parser = xml.sax.make_parser()
  158. parser.setFeature(xml.sax.handler.feature_namespaces, 1)
  159. parser.setContentHandler(odfs)
  160. if len(args) == 0:
  161. parser.parse(sys.stdin)
  162. else:
  163. parser.parse(open(args[0],"r"))
  164. mimetype = odfs._mimetype
  165. suffix = odmimetypes.get(mimetype,'.xxx')
  166. if outputfile == '-':
  167. if sys.stdout.isatty():
  168. sys.stderr.write("Won't write ODF file to terminal\n")
  169. sys.exit(1)
  170. z = zipfile.ZipFile(sys.stdout,"w")
  171. else:
  172. if addsuffix:
  173. outputfile = outputfile + suffix
  174. z = zipfile.ZipFile(outputfile,"w")
  175. now = time.localtime()[:6]
  176. # Write mimetype
  177. zi = zipfile.ZipInfo('mimetype', now)
  178. zi.compress_type = zipfile.ZIP_STORED
  179. z.writestr(zi,mimetype)
  180. # Write content
  181. zi = zipfile.ZipInfo("content.xml", now)
  182. zi.compress_type = zipfile.ZIP_DEFLATED
  183. z.writestr(zi,odfs.content() )
  184. # Write styles
  185. zi = zipfile.ZipInfo("styles.xml", now)
  186. zi.compress_type = zipfile.ZIP_DEFLATED
  187. z.writestr(zi,odfs.styles() )
  188. # Write meta
  189. zi = zipfile.ZipInfo("meta.xml", now)
  190. zi.compress_type = zipfile.ZIP_DEFLATED
  191. z.writestr(zi,odfs.meta() )
  192. m = manifest.Manifest()
  193. m.addElement(manifest.FileEntry(fullpath="/", mediatype=mimetype))
  194. m.addElement(manifest.FileEntry(fullpath="content.xml",mediatype="text/xml"))
  195. m.addElement(manifest.FileEntry(fullpath="styles.xml", mediatype="text/xml"))
  196. m.addElement(manifest.FileEntry(fullpath="meta.xml", mediatype="text/xml"))
  197. # Write manifest
  198. zi = zipfile.ZipInfo("META-INF/manifest.xml", now)
  199. zi.compress_type = zipfile.ZIP_DEFLATED
  200. z.writestr(zi, manifestxml(m).encode("utf-8") )
  201. z.close()
  202. # Local Variables: ***
  203. # mode: python ***
  204. # End: ***