manifest.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. """
  4. File manifest
  5. """
  6. from mimetypes import MimeTypes
  7. import os.path
  8. from openpyxl.descriptors.serialisable import Serialisable
  9. from openpyxl.descriptors import String, Sequence
  10. from openpyxl.xml.functions import fromstring
  11. from openpyxl.xml.constants import (
  12. ARC_CORE,
  13. ARC_CONTENT_TYPES,
  14. ARC_WORKBOOK,
  15. ARC_APP,
  16. ARC_THEME,
  17. ARC_STYLE,
  18. ARC_SHARED_STRINGS,
  19. EXTERNAL_LINK,
  20. THEME_TYPE,
  21. STYLES_TYPE,
  22. XLSX,
  23. XLSM,
  24. XLTM,
  25. XLTX,
  26. WORKSHEET_TYPE,
  27. COMMENTS_TYPE,
  28. SHARED_STRINGS,
  29. DRAWING_TYPE,
  30. CHART_TYPE,
  31. CHARTSHAPE_TYPE,
  32. CHARTSHEET_TYPE,
  33. CONTYPES_NS,
  34. ACTIVEX,
  35. CTRL,
  36. VBA,
  37. )
  38. from openpyxl.xml.functions import tostring
  39. # initialise mime-types
  40. mimetypes = MimeTypes()
  41. mimetypes.add_type('application/xml', ".xml")
  42. mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels")
  43. mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin")
  44. mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml")
  45. mimetypes.add_type("image/x-emf", ".emf")
  46. class FileExtension(Serialisable):
  47. tagname = "Default"
  48. Extension = String()
  49. ContentType = String()
  50. def __init__(self, Extension, ContentType):
  51. self.Extension = Extension
  52. self.ContentType = ContentType
  53. class Override(Serialisable):
  54. tagname = "Override"
  55. PartName = String()
  56. ContentType = String()
  57. def __init__(self, PartName, ContentType):
  58. self.PartName = PartName
  59. self.ContentType = ContentType
  60. DEFAULT_TYPES = [
  61. FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"),
  62. FileExtension("xml", "application/xml"),
  63. ]
  64. DEFAULT_OVERRIDE = [
  65. Override("/" + ARC_STYLE, STYLES_TYPE), # Styles
  66. Override("/" + ARC_THEME, THEME_TYPE), # Theme
  67. Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"),
  68. Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml")
  69. ]
  70. class Manifest(Serialisable):
  71. tagname = "Types"
  72. Default = Sequence(expected_type=FileExtension, unique=True)
  73. Override = Sequence(expected_type=Override, unique=True)
  74. path = "[Content_Types].xml"
  75. __elements__ = ("Default", "Override")
  76. def __init__(self,
  77. Default=(),
  78. Override=(),
  79. ):
  80. if not Default:
  81. Default = DEFAULT_TYPES
  82. self.Default = Default
  83. if not Override:
  84. Override = DEFAULT_OVERRIDE
  85. self.Override = Override
  86. @property
  87. def filenames(self):
  88. return [part.PartName for part in self.Override]
  89. @property
  90. def extensions(self):
  91. """
  92. Map content types to file extensions
  93. Skip parts without extensions
  94. """
  95. exts = set([os.path.splitext(part.PartName)[-1] for part in self.Override])
  96. return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext]
  97. def to_tree(self):
  98. """
  99. Custom serialisation method to allow setting a default namespace
  100. """
  101. defaults = [t.Extension for t in self.Default]
  102. for ext, mime in self.extensions:
  103. if ext not in defaults:
  104. mime = FileExtension(ext, mime)
  105. self.Default.append(mime)
  106. tree = super(Manifest, self).to_tree()
  107. tree.set("xmlns", CONTYPES_NS)
  108. return tree
  109. def __contains__(self, content_type):
  110. """
  111. Check whether a particular content type is contained
  112. """
  113. for t in self.Override:
  114. if t.ContentType == content_type:
  115. return True
  116. def find(self, content_type):
  117. """
  118. Find specific content-type
  119. """
  120. try:
  121. return next(self.findall(content_type))
  122. except StopIteration:
  123. return
  124. def findall(self, content_type):
  125. """
  126. Find all elements of a specific content-type
  127. """
  128. for t in self.Override:
  129. if t.ContentType == content_type:
  130. yield t
  131. def append(self, obj):
  132. """
  133. Add content object to the package manifest
  134. # needs a contract...
  135. """
  136. ct = Override(PartName=obj.path, ContentType=obj.mime_type)
  137. self.Override.append(ct)
  138. def _write(self, archive, workbook):
  139. """
  140. Write manifest to the archive
  141. """
  142. self.append(workbook)
  143. self._write_vba(workbook)
  144. self._register_mimetypes(filenames=archive.namelist())
  145. archive.writestr(self.path, tostring(self.to_tree()))
  146. def _register_mimetypes(self, filenames):
  147. """
  148. Make sure that the mime type for all file extensions is registered
  149. """
  150. for fn in filenames:
  151. ext = os.path.splitext(fn)[-1]
  152. if not ext:
  153. continue
  154. mime = mimetypes.types_map[True][ext]
  155. fe = FileExtension(ext[1:], mime)
  156. self.Default.append(fe)
  157. def _write_vba(self, workbook):
  158. """
  159. Add content types from cached workbook when keeping VBA
  160. """
  161. if workbook.vba_archive:
  162. node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES))
  163. mf = Manifest.from_tree(node)
  164. filenames = self.filenames
  165. for override in mf.Override:
  166. if override.PartName not in (ACTIVEX, CTRL, VBA):
  167. continue
  168. if override.PartName not in filenames:
  169. self.Override.append(override)