1###############################################################################
2#
3# Metadata - A class for writing the Excel XLSX Metadata file.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6#
7# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
8#
9
10from . import xmlwriter
11
12
13class Metadata(xmlwriter.XMLwriter):
14 """
15 A class for writing the Excel XLSX Metadata file.
16
17
18 """
19
20 ###########################################################################
21 #
22 # Public API.
23 #
24 ###########################################################################
25
26 def __init__(self):
27 """
28 Constructor.
29
30 """
31
32 super().__init__()
33 self.has_dynamic_functions = False
34 self.has_embedded_images = False
35 self.num_embedded_images = 0
36
37 ###########################################################################
38 #
39 # Private API.
40 #
41 ###########################################################################
42
43 def _assemble_xml_file(self):
44 # Assemble and write the XML file.
45
46 if self.num_embedded_images > 0:
47 self.has_embedded_images = True
48
49 # Write the XML declaration.
50 self._xml_declaration()
51
52 # Write the metadata element.
53 self._write_metadata()
54
55 # Write the metadataTypes element.
56 self._write_metadata_types()
57
58 # Write the futureMetadata elements.
59 if self.has_dynamic_functions:
60 self._write_cell_future_metadata()
61 if self.has_embedded_images:
62 self._write_value_future_metadata()
63
64 # Write the cellMetadata element.
65 if self.has_dynamic_functions:
66 self._write_cell_metadata()
67 if self.has_embedded_images:
68 self._write_value_metadata()
69
70 self._xml_end_tag("metadata")
71
72 # Close the file.
73 self._xml_close()
74
75 ###########################################################################
76 #
77 # XML methods.
78 #
79 ###########################################################################
80
81 def _write_metadata(self):
82 # Write the <metadata> element.
83 xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
84 schema = "http://schemas.microsoft.com/office/spreadsheetml"
85
86 attributes = [("xmlns", xmlns)]
87
88 if self.has_embedded_images:
89 attributes.append(("xmlns:xlrd", schema + "/2017/richdata"))
90
91 if self.has_dynamic_functions:
92 attributes.append(("xmlns:xda", schema + "/2017/dynamicarray"))
93
94 self._xml_start_tag("metadata", attributes)
95
96 def _write_metadata_types(self):
97 # Write the <metadataTypes> element.
98 count = 0
99
100 if self.has_dynamic_functions:
101 count += 1
102 if self.has_embedded_images:
103 count += 1
104
105 attributes = [("count", count)]
106
107 self._xml_start_tag("metadataTypes", attributes)
108
109 # Write the metadataType element.
110 if self.has_dynamic_functions:
111 self._write_cell_metadata_type()
112 if self.has_embedded_images:
113 self._write_value_metadata_type()
114
115 self._xml_end_tag("metadataTypes")
116
117 def _write_cell_metadata_type(self):
118 # Write the <metadataType> element.
119 attributes = [
120 ("name", "XLDAPR"),
121 ("minSupportedVersion", 120000),
122 ("copy", 1),
123 ("pasteAll", 1),
124 ("pasteValues", 1),
125 ("merge", 1),
126 ("splitFirst", 1),
127 ("rowColShift", 1),
128 ("clearFormats", 1),
129 ("clearComments", 1),
130 ("assign", 1),
131 ("coerce", 1),
132 ("cellMeta", 1),
133 ]
134
135 self._xml_empty_tag("metadataType", attributes)
136
137 def _write_value_metadata_type(self):
138 # Write the <metadataType> element.
139 attributes = [
140 ("name", "XLRICHVALUE"),
141 ("minSupportedVersion", 120000),
142 ("copy", 1),
143 ("pasteAll", 1),
144 ("pasteValues", 1),
145 ("merge", 1),
146 ("splitFirst", 1),
147 ("rowColShift", 1),
148 ("clearFormats", 1),
149 ("clearComments", 1),
150 ("assign", 1),
151 ("coerce", 1),
152 ]
153
154 self._xml_empty_tag("metadataType", attributes)
155
156 def _write_cell_future_metadata(self):
157 # Write the <futureMetadata> element.
158 attributes = [
159 ("name", "XLDAPR"),
160 ("count", 1),
161 ]
162
163 self._xml_start_tag("futureMetadata", attributes)
164 self._xml_start_tag("bk")
165 self._xml_start_tag("extLst")
166 self._write_cell_ext()
167 self._xml_end_tag("extLst")
168 self._xml_end_tag("bk")
169 self._xml_end_tag("futureMetadata")
170
171 def _write_value_future_metadata(self):
172 # Write the <futureMetadata> element.
173 attributes = [
174 ("name", "XLRICHVALUE"),
175 ("count", self.num_embedded_images),
176 ]
177
178 self._xml_start_tag("futureMetadata", attributes)
179
180 for index in range(self.num_embedded_images):
181 self._xml_start_tag("bk")
182 self._xml_start_tag("extLst")
183 self._write_value_ext(index)
184 self._xml_end_tag("extLst")
185 self._xml_end_tag("bk")
186
187 self._xml_end_tag("futureMetadata")
188
189 def _write_cell_ext(self):
190 # Write the <ext> element.
191 attributes = [("uri", "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}")]
192
193 self._xml_start_tag("ext", attributes)
194
195 # Write the xda:dynamicArrayProperties element.
196 self._write_xda_dynamic_array_properties()
197
198 self._xml_end_tag("ext")
199
200 def _write_xda_dynamic_array_properties(self):
201 # Write the <xda:dynamicArrayProperties> element.
202 attributes = [
203 ("fDynamic", 1),
204 ("fCollapsed", 0),
205 ]
206
207 self._xml_empty_tag("xda:dynamicArrayProperties", attributes)
208
209 def _write_value_ext(self, index):
210 # Write the <ext> element.
211 attributes = [("uri", "{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}")]
212
213 self._xml_start_tag("ext", attributes)
214
215 # Write the xlrd:rvb element.
216 self._write_xlrd_rvb(index)
217
218 self._xml_end_tag("ext")
219
220 def _write_xlrd_rvb(self, index):
221 # Write the <xlrd:rvb> element.
222 attributes = [("i", index)]
223
224 self._xml_empty_tag("xlrd:rvb", attributes)
225
226 def _write_cell_metadata(self):
227 # Write the <cellMetadata> element.
228 attributes = [("count", 1)]
229
230 self._xml_start_tag("cellMetadata", attributes)
231 self._xml_start_tag("bk")
232
233 # Write the rc element.
234 self._write_rc(1, 0)
235
236 self._xml_end_tag("bk")
237 self._xml_end_tag("cellMetadata")
238
239 def _write_value_metadata(self):
240 # Write the <valueMetadata> element.
241 count = self.num_embedded_images
242 rc_type = 1
243
244 if self.has_dynamic_functions:
245 rc_type = 2
246
247 attributes = [("count", count)]
248
249 self._xml_start_tag("valueMetadata", attributes)
250
251 # Write the rc elements.
252 for index in range(self.num_embedded_images):
253 self._xml_start_tag("bk")
254 self._write_rc(rc_type, index)
255 self._xml_end_tag("bk")
256
257 self._xml_end_tag("valueMetadata")
258
259 def _write_rc(self, rc_type, index):
260 # Write the <rc> element.
261 attributes = [
262 ("t", rc_type),
263 ("v", index),
264 ]
265
266 self._xml_empty_tag("rc", attributes)