Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/packaging/manifest.py: 79%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

98 statements  

1# Copyright (c) 2010-2024 openpyxl 

2 

3""" 

4File manifest 

5""" 

6from mimetypes import MimeTypes 

7import os.path 

8 

9from openpyxl.descriptors.serialisable import Serialisable 

10from openpyxl.descriptors import String, Sequence 

11from openpyxl.xml.functions import fromstring 

12from openpyxl.xml.constants import ( 

13 ARC_CONTENT_TYPES, 

14 ARC_THEME, 

15 ARC_STYLE, 

16 THEME_TYPE, 

17 STYLES_TYPE, 

18 CONTYPES_NS, 

19 ACTIVEX, 

20 CTRL, 

21 VBA, 

22) 

23from openpyxl.xml.functions import tostring 

24 

25# initialise mime-types 

26mimetypes = MimeTypes() 

27mimetypes.add_type('application/xml', ".xml") 

28mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels") 

29mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin") 

30mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml") 

31mimetypes.add_type("image/x-emf", ".emf") 

32 

33 

34class FileExtension(Serialisable): 

35 

36 tagname = "Default" 

37 

38 Extension = String() 

39 ContentType = String() 

40 

41 def __init__(self, Extension, ContentType): 

42 self.Extension = Extension 

43 self.ContentType = ContentType 

44 

45 

46class Override(Serialisable): 

47 

48 tagname = "Override" 

49 

50 PartName = String() 

51 ContentType = String() 

52 

53 def __init__(self, PartName, ContentType): 

54 self.PartName = PartName 

55 self.ContentType = ContentType 

56 

57 

58DEFAULT_TYPES = [ 

59 FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"), 

60 FileExtension("xml", "application/xml"), 

61] 

62 

63DEFAULT_OVERRIDE = [ 

64 Override("/" + ARC_STYLE, STYLES_TYPE), # Styles 

65 Override("/" + ARC_THEME, THEME_TYPE), # Theme 

66 Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"), 

67 Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml") 

68] 

69 

70 

71class Manifest(Serialisable): 

72 

73 tagname = "Types" 

74 

75 Default = Sequence(expected_type=FileExtension, unique=True) 

76 Override = Sequence(expected_type=Override, unique=True) 

77 path = "[Content_Types].xml" 

78 

79 __elements__ = ("Default", "Override") 

80 

81 def __init__(self, 

82 Default=(), 

83 Override=(), 

84 ): 

85 if not Default: 

86 Default = DEFAULT_TYPES 

87 self.Default = Default 

88 if not Override: 

89 Override = DEFAULT_OVERRIDE 

90 self.Override = Override 

91 

92 

93 @property 

94 def filenames(self): 

95 return [part.PartName for part in self.Override] 

96 

97 

98 @property 

99 def extensions(self): 

100 """ 

101 Map content types to file extensions 

102 Skip parts without extensions 

103 """ 

104 exts = {os.path.splitext(part.PartName)[-1] for part in self.Override} 

105 return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext] 

106 

107 

108 def to_tree(self): 

109 """ 

110 Custom serialisation method to allow setting a default namespace 

111 """ 

112 defaults = [t.Extension for t in self.Default] 

113 for ext, mime in self.extensions: 

114 if ext not in defaults: 

115 mime = FileExtension(ext, mime) 

116 self.Default.append(mime) 

117 tree = super().to_tree() 

118 tree.set("xmlns", CONTYPES_NS) 

119 return tree 

120 

121 

122 def __contains__(self, content_type): 

123 """ 

124 Check whether a particular content type is contained 

125 """ 

126 for t in self.Override: 

127 if t.ContentType == content_type: 

128 return True 

129 

130 

131 def find(self, content_type): 

132 """ 

133 Find specific content-type 

134 """ 

135 try: 

136 return next(self.findall(content_type)) 

137 except StopIteration: 

138 return 

139 

140 

141 def findall(self, content_type): 

142 """ 

143 Find all elements of a specific content-type 

144 """ 

145 for t in self.Override: 

146 if t.ContentType == content_type: 

147 yield t 

148 

149 

150 def append(self, obj): 

151 """ 

152 Add content object to the package manifest 

153 # needs a contract... 

154 """ 

155 ct = Override(PartName=obj.path, ContentType=obj.mime_type) 

156 self.Override.append(ct) 

157 

158 

159 def _write(self, archive, workbook): 

160 """ 

161 Write manifest to the archive 

162 """ 

163 self.append(workbook) 

164 self._write_vba(workbook) 

165 self._register_mimetypes(filenames=archive.namelist()) 

166 archive.writestr(self.path, tostring(self.to_tree())) 

167 

168 

169 def _register_mimetypes(self, filenames): 

170 """ 

171 Make sure that the mime type for all file extensions is registered 

172 """ 

173 for fn in filenames: 

174 ext = os.path.splitext(fn)[-1] 

175 if not ext: 

176 continue 

177 mime = mimetypes.types_map[True][ext] 

178 fe = FileExtension(ext[1:], mime) 

179 self.Default.append(fe) 

180 

181 

182 def _write_vba(self, workbook): 

183 """ 

184 Add content types from cached workbook when keeping VBA 

185 """ 

186 if workbook.vba_archive: 

187 node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES)) 

188 mf = Manifest.from_tree(node) 

189 filenames = self.filenames 

190 for override in mf.Override: 

191 if override.PartName not in (ACTIVEX, CTRL, VBA): 

192 continue 

193 if override.PartName not in filenames: 

194 self.Override.append(override)