odfmeta 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2006-2009 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. import zipfile, time, sys, getopt, re
  21. import xml.sax, xml.sax.saxutils
  22. from odf.namespaces import TOOLSVERSION, OFFICENS, XLINKNS, DCNS, METANS
  23. from io import BytesIO
  24. OUTENCODING="utf-8"
  25. whitespace = re.compile(r'\s+')
  26. fields = {
  27. 'title': (DCNS,u'title'),
  28. 'description': (DCNS,u'description'),
  29. 'subject': (DCNS,u'subject'),
  30. 'creator': (DCNS,u'creator'),
  31. 'date': (DCNS,u'date'),
  32. 'language': (DCNS,u'language'),
  33. 'generator': (METANS,u'generator'),
  34. 'initial-creator': (METANS,u'initial-creator'),
  35. 'keyword': (METANS,u'keyword'),
  36. 'editing-duration': (METANS,u'editing-duration'),
  37. 'editing-cycles': (METANS,u'editing-cycles'),
  38. 'printed-by': (METANS,u'printed-by'),
  39. 'print-date': (METANS,u'print-date'),
  40. 'creation-date': (METANS,u'creation-date'),
  41. 'user-defined': (METANS,u'user-defined'),
  42. #'template': (METANS,u'template'),
  43. }
  44. xfields = []
  45. Xfields = []
  46. addfields = {}
  47. deletefields = {}
  48. yieldfields = {}
  49. showversion = None
  50. def exitwithusage(exitcode=2):
  51. """ print out usage information """
  52. sys.stderr.write("Usage: %s [-cdlvV] [-xXaAI metafield]... [-o output] [inputfile]\n" % sys.argv[0])
  53. sys.stderr.write("\tInputfile must be OpenDocument format\n")
  54. sys.exit(exitcode)
  55. def normalize(str):
  56. """
  57. The normalize-space function returns the argument string with whitespace
  58. normalized by stripping leading and trailing whitespace and replacing
  59. sequences of whitespace characters by a single space.
  60. """
  61. return whitespace.sub(' ', str).strip()
  62. class MetaCollector:
  63. """
  64. The MetaCollector is a pseudo file object, that can temporarily ignore write-calls
  65. It could probably be replaced with a StringIO object.
  66. """
  67. def __init__(self):
  68. self._content = []
  69. self.dowrite = True
  70. def write(self, str):
  71. if self.dowrite:
  72. self._content.append(str)
  73. def content(self):
  74. return ''.join(self._content)
  75. base = xml.sax.saxutils.XMLGenerator
  76. class odfmetaparser(base):
  77. """ Parse a meta.xml file with an event-driven parser and replace elements.
  78. It would probably be a cleaner approach to use a DOM based parser and
  79. then manipulate in memory.
  80. Small issue: Reorders elements
  81. """
  82. version = 'Unknown'
  83. def __init__(self):
  84. self._mimetype = ''
  85. self.output = MetaCollector()
  86. self._data = []
  87. self.seenfields = {}
  88. base.__init__(self, self.output, OUTENCODING)
  89. def startElementNS(self, name, qname, attrs):
  90. self._data = []
  91. field = name
  92. # I can't modify the template until the tool replaces elements at the same
  93. # location and not at the end
  94. # if name == (METANS,u'template'):
  95. # self._data = [attrs.get((XLINKNS,u'title'),'')]
  96. if showversion and name == (OFFICENS,u'document-meta'):
  97. if showversion == '-V':
  98. print ("version:%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8'))
  99. else:
  100. print ("%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8'))
  101. if name == (METANS,u'user-defined'):
  102. field = attrs.get((METANS,u'name'))
  103. if field in deletefields:
  104. self.output.dowrite = False
  105. elif field in yieldfields:
  106. del addfields[field]
  107. base.startElementNS(self, name, qname, attrs)
  108. else:
  109. base.startElementNS(self, name, qname, attrs)
  110. self._tag = field
  111. def endElementNS(self, name, qname):
  112. field = name
  113. if name == (METANS,u'user-defined'):
  114. field = self._tag
  115. if name == (OFFICENS,u'meta'):
  116. for k,v in addfields.items():
  117. if len(v) > 0:
  118. if type(k) == type(''):
  119. base.startElementNS(self,(METANS,u'user-defined'),None,{(METANS,u'name'):k})
  120. base.characters(self, v)
  121. base.endElementNS(self, (METANS,u'user-defined'),None)
  122. else:
  123. base.startElementNS(self, k, None, {})
  124. base.characters(self, v)
  125. base.endElementNS(self, k, None)
  126. if name in xfields:
  127. print ("%s" % self.data())
  128. if name in Xfields:
  129. if isinstance(self._tag, tuple):
  130. texttag = self._tag[1]
  131. else:
  132. texttag = self._tag
  133. print ("%s:%s" % (texttag, self.data()))
  134. if field in deletefields:
  135. self.output.dowrite = True
  136. else:
  137. base.endElementNS(self, name, qname)
  138. def characters(self, content):
  139. base.characters(self, content)
  140. self._data.append(content)
  141. def meta(self):
  142. return self.output.content()
  143. def data(self):
  144. if usenormalize:
  145. return normalize(''.join(self._data))
  146. else:
  147. return ''.join(self._data)
  148. now = time.localtime()[:6]
  149. outputfile = "-"
  150. writemeta = False # Do we change any meta data?
  151. usenormalize = False
  152. try:
  153. opts, args = getopt.getopt(sys.argv[1:], "cdlvVI:A:a:o:x:X:")
  154. except getopt.GetoptError:
  155. exitwithusage()
  156. if len(opts) == 0:
  157. opts = [ ('-l','') ]
  158. for o, a in opts:
  159. if o in ('-a','-A','-I'):
  160. writemeta = True
  161. if a.find(":") >= 0:
  162. k,v = a.split(":",1)
  163. else:
  164. k,v = (a, "")
  165. if len(k) == 0:
  166. exitwithusage()
  167. k = fields.get(k,k)
  168. addfields[k] = unicode(v,'utf-8')
  169. if o == '-a':
  170. yieldfields[k] = True
  171. if o == '-I':
  172. deletefields[k] = True
  173. if o == '-d':
  174. writemeta = True
  175. addfields[(DCNS,u'date')] = "%04d-%02d-%02dT%02d:%02d:%02d" % now
  176. deletefields[(DCNS,u'date')] = True
  177. if o == '-c':
  178. usenormalize = True
  179. if o in ('-v', '-V'):
  180. showversion = o
  181. if o == '-l':
  182. Xfields = fields.values()
  183. if o == "-x":
  184. xfields.append(fields.get(a,a))
  185. if o == "-X":
  186. Xfields.append(fields.get(a,a))
  187. if o == "-o":
  188. outputfile = a
  189. # The specification says we should change the element to our own,
  190. # and must not export the original identifier.
  191. if writemeta:
  192. addfields[(METANS,u'generator')] = TOOLSVERSION
  193. deletefields[(METANS,u'generator')] = True
  194. odfs = odfmetaparser()
  195. parser = xml.sax.make_parser()
  196. parser.setFeature(xml.sax.handler.feature_namespaces, 1)
  197. parser.setContentHandler(odfs)
  198. if len(args) == 0:
  199. zin = zipfile.ZipFile(sys.stdin,'r')
  200. else:
  201. if not zipfile.is_zipfile(args[0]):
  202. exitwithusage()
  203. zin = zipfile.ZipFile(args[0], 'r')
  204. try:
  205. content = zin.read('meta.xml').decode('utf-8')
  206. except:
  207. sys.stderr.write("File has no meta data\n")
  208. sys.exit(1)
  209. parser.parse(BytesIO(content.encode('utf-8')))
  210. if writemeta:
  211. if outputfile == '-':
  212. if sys.stdout.isatty():
  213. sys.stderr.write("Won't write ODF file to terminal\n")
  214. sys.exit(1)
  215. zout = zipfile.ZipFile(sys.stdout,"w")
  216. else:
  217. zout = zipfile.ZipFile(outputfile,"w")
  218. # Loop through the input zipfile and copy the content to the output until we
  219. # get to the meta.xml. Then substitute.
  220. for zinfo in zin.infolist():
  221. if zinfo.filename == "meta.xml":
  222. # Write meta
  223. zi = zipfile.ZipInfo("meta.xml", now)
  224. zi.compress_type = zipfile.ZIP_DEFLATED
  225. zout.writestr(zi,odfs.meta() )
  226. else:
  227. payload = zin.read(zinfo.filename)
  228. zout.writestr(zinfo, payload)
  229. zout.close()
  230. zin.close()
  231. # Local Variables: ***
  232. # mode: python ***
  233. # End: ***