1###############################################################################
2#
3# Packager - A class for writing the Excel XLSX Worksheet file.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6#
7# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
8#
9
10# Standard packages.
11import os
12import stat
13import tempfile
14from io import BytesIO, StringIO
15from shutil import copy
16
17# Package imports.
18from .app import App
19from .comments import Comments
20from .contenttypes import ContentTypes
21from .core import Core
22from .custom import Custom
23from .exceptions import EmptyChartSeries
24from .feature_property_bag import FeaturePropertyBag
25from .metadata import Metadata
26from .relationships import Relationships
27from .rich_value import RichValue
28from .rich_value_rel import RichValueRel
29from .rich_value_structure import RichValueStructure
30from .rich_value_types import RichValueTypes
31from .sharedstrings import SharedStrings
32from .styles import Styles
33from .table import Table
34from .theme import Theme
35from .vml import Vml
36
37
38class Packager:
39 """
40 A class for writing the Excel XLSX Packager file.
41
42 This module is used in conjunction with XlsxWriter to create an
43 Excel XLSX container file.
44
45 From Wikipedia: The Open Packaging Conventions (OPC) is a
46 container-file technology initially created by Microsoft to store
47 a combination of XML and non-XML files that together form a single
48 entity such as an Open XML Paper Specification (OpenXPS)
49 document. http://en.wikipedia.org/wiki/Open_Packaging_Conventions.
50
51 At its simplest an Excel XLSX file contains the following elements::
52
53 ____ [Content_Types].xml
54 |
55 |____ docProps
56 | |____ app.xml
57 | |____ core.xml
58 |
59 |____ xl
60 | |____ workbook.xml
61 | |____ worksheets
62 | | |____ sheet1.xml
63 | |
64 | |____ styles.xml
65 | |
66 | |____ theme
67 | | |____ theme1.xml
68 | |
69 | |_____rels
70 | |____ workbook.xml.rels
71 |
72 |_____rels
73 |____ .rels
74
75 The Packager class coordinates the classes that represent the
76 elements of the package and writes them into the XLSX file.
77
78 """
79
80 ###########################################################################
81 #
82 # Public API.
83 #
84 ###########################################################################
85
86 def __init__(self):
87 """
88 Constructor.
89
90 """
91
92 super().__init__()
93
94 self.tmpdir = ""
95 self.in_memory = False
96 self.workbook = None
97 self.worksheet_count = 0
98 self.chartsheet_count = 0
99 self.chart_count = 0
100 self.drawing_count = 0
101 self.table_count = 0
102 self.num_vml_files = 0
103 self.num_comment_files = 0
104 self.named_ranges = []
105 self.filenames = []
106
107 ###########################################################################
108 #
109 # Private API.
110 #
111 ###########################################################################
112
113 def _set_tmpdir(self, tmpdir):
114 # Set an optional user defined temp directory.
115 self.tmpdir = tmpdir
116
117 def _set_in_memory(self, in_memory):
118 # Set the optional 'in_memory' mode.
119 self.in_memory = in_memory
120
121 def _add_workbook(self, workbook):
122 # Add the Excel::Writer::XLSX::Workbook object to the package.
123 self.workbook = workbook
124 self.chart_count = len(workbook.charts)
125 self.drawing_count = len(workbook.drawings)
126 self.num_vml_files = workbook.num_vml_files
127 self.num_comment_files = workbook.num_comment_files
128 self.named_ranges = workbook.named_ranges
129
130 for worksheet in self.workbook.worksheets():
131 if worksheet.is_chartsheet:
132 self.chartsheet_count += 1
133 else:
134 self.worksheet_count += 1
135
136 def _create_package(self):
137 # Write the xml files that make up the XLSX OPC package.
138 self._write_content_types_file()
139 self._write_root_rels_file()
140 self._write_workbook_rels_file()
141 self._write_worksheet_files()
142 self._write_chartsheet_files()
143 self._write_workbook_file()
144 self._write_chart_files()
145 self._write_drawing_files()
146 self._write_vml_files()
147 self._write_comment_files()
148 self._write_table_files()
149 self._write_shared_strings_file()
150 self._write_styles_file()
151 self._write_custom_file()
152 self._write_theme_file()
153 self._write_worksheet_rels_files()
154 self._write_chartsheet_rels_files()
155 self._write_drawing_rels_files()
156 self._write_rich_value_rels_files()
157 self._add_image_files()
158 self._add_vba_project()
159 self._add_vba_project_signature()
160 self._write_vba_project_rels_file()
161 self._write_core_file()
162 self._write_app_file()
163 self._write_metadata_file()
164 self._write_feature_bag_property()
165 self._write_rich_value_files()
166
167 return self.filenames
168
169 def _filename(self, xml_filename):
170 # Create a temp filename to write the XML data to and store the Excel
171 # filename to use as the name in the Zip container.
172 if self.in_memory:
173 os_filename = StringIO()
174 else:
175 (fd, os_filename) = tempfile.mkstemp(dir=self.tmpdir)
176 os.close(fd)
177
178 self.filenames.append((os_filename, xml_filename, False))
179
180 return os_filename
181
182 def _write_workbook_file(self):
183 # Write the workbook.xml file.
184 workbook = self.workbook
185
186 workbook._set_xml_writer(self._filename("xl/workbook.xml"))
187 workbook._assemble_xml_file()
188
189 def _write_worksheet_files(self):
190 # Write the worksheet files.
191 index = 1
192 for worksheet in self.workbook.worksheets():
193 if worksheet.is_chartsheet:
194 continue
195
196 if worksheet.constant_memory:
197 worksheet._opt_reopen()
198 worksheet._write_single_row()
199
200 worksheet._set_xml_writer(
201 self._filename("xl/worksheets/sheet" + str(index) + ".xml")
202 )
203 worksheet._assemble_xml_file()
204 index += 1
205
206 def _write_chartsheet_files(self):
207 # Write the chartsheet files.
208 index = 1
209 for worksheet in self.workbook.worksheets():
210 if not worksheet.is_chartsheet:
211 continue
212
213 worksheet._set_xml_writer(
214 self._filename("xl/chartsheets/sheet" + str(index) + ".xml")
215 )
216 worksheet._assemble_xml_file()
217 index += 1
218
219 def _write_chart_files(self):
220 # Write the chart files.
221 if not self.workbook.charts:
222 return
223
224 index = 1
225 for chart in self.workbook.charts:
226 # Check that the chart has at least one data series.
227 if not chart.series:
228 raise EmptyChartSeries(
229 f"Chart{index} must contain at least one "
230 f"data series. See chart.add_series()."
231 )
232
233 chart._set_xml_writer(
234 self._filename("xl/charts/chart" + str(index) + ".xml")
235 )
236 chart._assemble_xml_file()
237 index += 1
238
239 def _write_drawing_files(self):
240 # Write the drawing files.
241 if not self.drawing_count:
242 return
243
244 index = 1
245 for drawing in self.workbook.drawings:
246 drawing._set_xml_writer(
247 self._filename("xl/drawings/drawing" + str(index) + ".xml")
248 )
249 drawing._assemble_xml_file()
250 index += 1
251
252 def _write_vml_files(self):
253 # Write the comment VML files.
254 index = 1
255 for worksheet in self.workbook.worksheets():
256 if not worksheet.has_vml and not worksheet.has_header_vml:
257 continue
258 if worksheet.has_vml:
259 vml = Vml()
260 vml._set_xml_writer(
261 self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
262 )
263 vml._assemble_xml_file(
264 worksheet.vml_data_id,
265 worksheet.vml_shape_id,
266 worksheet.comments_list,
267 worksheet.buttons_list,
268 )
269 index += 1
270
271 if worksheet.has_header_vml:
272 vml = Vml()
273
274 vml._set_xml_writer(
275 self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
276 )
277 vml._assemble_xml_file(
278 worksheet.vml_header_id,
279 worksheet.vml_header_id * 1024,
280 None,
281 None,
282 worksheet.header_images_list,
283 )
284
285 self._write_vml_drawing_rels_file(worksheet, index)
286 index += 1
287
288 def _write_comment_files(self):
289 # Write the comment files.
290 index = 1
291 for worksheet in self.workbook.worksheets():
292 if not worksheet.has_comments:
293 continue
294
295 comment = Comments()
296 comment._set_xml_writer(self._filename("xl/comments" + str(index) + ".xml"))
297 comment._assemble_xml_file(worksheet.comments_list)
298 index += 1
299
300 def _write_shared_strings_file(self):
301 # Write the sharedStrings.xml file.
302 sst = SharedStrings()
303 sst.string_table = self.workbook.str_table
304
305 if not self.workbook.str_table.count:
306 return
307
308 sst._set_xml_writer(self._filename("xl/sharedStrings.xml"))
309 sst._assemble_xml_file()
310
311 def _write_app_file(self):
312 # Write the app.xml file.
313 properties = self.workbook.doc_properties
314 app = App()
315
316 # Add the Worksheet parts.
317 worksheet_count = 0
318 for worksheet in self.workbook.worksheets():
319 if worksheet.is_chartsheet:
320 continue
321
322 # Don't write/count veryHidden sheets.
323 if worksheet.hidden != 2:
324 app._add_part_name(worksheet.name)
325 worksheet_count += 1
326
327 # Add the Worksheet heading pairs.
328 app._add_heading_pair(["Worksheets", worksheet_count])
329
330 # Add the Chartsheet parts.
331 for worksheet in self.workbook.worksheets():
332 if not worksheet.is_chartsheet:
333 continue
334 app._add_part_name(worksheet.name)
335
336 # Add the Chartsheet heading pairs.
337 app._add_heading_pair(["Charts", self.chartsheet_count])
338
339 # Add the Named Range heading pairs.
340 if self.named_ranges:
341 app._add_heading_pair(["Named Ranges", len(self.named_ranges)])
342
343 # Add the Named Ranges parts.
344 for named_range in self.named_ranges:
345 app._add_part_name(named_range)
346
347 app._set_properties(properties)
348 app.doc_security = self.workbook.read_only
349
350 app._set_xml_writer(self._filename("docProps/app.xml"))
351 app._assemble_xml_file()
352
353 def _write_core_file(self):
354 # Write the core.xml file.
355 properties = self.workbook.doc_properties
356 core = Core()
357
358 core._set_properties(properties)
359 core._set_xml_writer(self._filename("docProps/core.xml"))
360 core._assemble_xml_file()
361
362 def _write_metadata_file(self):
363 # Write the metadata.xml file.
364 if not self.workbook.has_metadata:
365 return
366
367 metadata = Metadata()
368 metadata.has_dynamic_functions = self.workbook.has_dynamic_functions
369 metadata.num_embedded_images = len(self.workbook.embedded_images.images)
370
371 metadata._set_xml_writer(self._filename("xl/metadata.xml"))
372 metadata._assemble_xml_file()
373
374 def _write_feature_bag_property(self):
375 # Write the featurePropertyBag.xml file.
376 feature_property_bags = self.workbook._has_feature_property_bags()
377 if not feature_property_bags:
378 return
379
380 property_bag = FeaturePropertyBag()
381 property_bag.feature_property_bags = feature_property_bags
382
383 property_bag._set_xml_writer(
384 self._filename("xl/featurePropertyBag/featurePropertyBag.xml")
385 )
386 property_bag._assemble_xml_file()
387
388 def _write_rich_value_files(self):
389
390 if not self.workbook.embedded_images.has_images():
391 return
392
393 self._write_rich_value()
394 self._write_rich_value_types()
395 self._write_rich_value_structure()
396 self._write_rich_value_rel()
397
398 def _write_rich_value(self):
399 # Write the rdrichvalue.xml file.
400 filename = self._filename("xl/richData/rdrichvalue.xml")
401 xml_file = RichValue()
402 xml_file.embedded_images = self.workbook.embedded_images.images
403 xml_file._set_xml_writer(filename)
404 xml_file._assemble_xml_file()
405
406 def _write_rich_value_types(self):
407 # Write the rdRichValueTypes.xml file.
408 filename = self._filename("xl/richData/rdRichValueTypes.xml")
409 xml_file = RichValueTypes()
410 xml_file._set_xml_writer(filename)
411 xml_file._assemble_xml_file()
412
413 def _write_rich_value_structure(self):
414 # Write the rdrichvaluestructure.xml file.
415 filename = self._filename("xl/richData/rdrichvaluestructure.xml")
416 xml_file = RichValueStructure()
417 xml_file.has_embedded_descriptions = self.workbook.has_embedded_descriptions
418 xml_file._set_xml_writer(filename)
419 xml_file._assemble_xml_file()
420
421 def _write_rich_value_rel(self):
422 # Write the richValueRel.xml file.
423 filename = self._filename("xl/richData/richValueRel.xml")
424 xml_file = RichValueRel()
425 xml_file.num_embedded_images = len(self.workbook.embedded_images.images)
426 xml_file._set_xml_writer(filename)
427 xml_file._assemble_xml_file()
428
429 def _write_custom_file(self):
430 # Write the custom.xml file.
431 properties = self.workbook.custom_properties
432 custom = Custom()
433
434 if not properties:
435 return
436
437 custom._set_properties(properties)
438 custom._set_xml_writer(self._filename("docProps/custom.xml"))
439 custom._assemble_xml_file()
440
441 def _write_content_types_file(self):
442 # Write the ContentTypes.xml file.
443 content = ContentTypes()
444 content._add_image_types(self.workbook.image_types)
445
446 self._get_table_count()
447
448 worksheet_index = 1
449 chartsheet_index = 1
450 for worksheet in self.workbook.worksheets():
451 if worksheet.is_chartsheet:
452 content._add_chartsheet_name("sheet" + str(chartsheet_index))
453 chartsheet_index += 1
454 else:
455 content._add_worksheet_name("sheet" + str(worksheet_index))
456 worksheet_index += 1
457
458 for i in range(1, self.chart_count + 1):
459 content._add_chart_name("chart" + str(i))
460
461 for i in range(1, self.drawing_count + 1):
462 content._add_drawing_name("drawing" + str(i))
463
464 if self.num_vml_files:
465 content._add_vml_name()
466
467 for i in range(1, self.table_count + 1):
468 content._add_table_name("table" + str(i))
469
470 for i in range(1, self.num_comment_files + 1):
471 content._add_comment_name("comments" + str(i))
472
473 # Add the sharedString rel if there is string data in the workbook.
474 if self.workbook.str_table.count:
475 content._add_shared_strings()
476
477 # Add vbaProject (and optionally vbaProjectSignature) if present.
478 if self.workbook.vba_project:
479 content._add_vba_project()
480 if self.workbook.vba_project_signature:
481 content._add_vba_project_signature()
482
483 # Add the custom properties if present.
484 if self.workbook.custom_properties:
485 content._add_custom_properties()
486
487 # Add the metadata file if present.
488 if self.workbook.has_metadata:
489 content._add_metadata()
490
491 # Add the metadata file if present.
492 if self.workbook._has_feature_property_bags():
493 content._add_feature_bag_property()
494
495 # Add the RichValue file if present.
496 if self.workbook.embedded_images.has_images():
497 content._add_rich_value()
498
499 content._set_xml_writer(self._filename("[Content_Types].xml"))
500 content._assemble_xml_file()
501
502 def _write_styles_file(self):
503 # Write the style xml file.
504 xf_formats = self.workbook.xf_formats
505 palette = self.workbook.palette
506 font_count = self.workbook.font_count
507 num_formats = self.workbook.num_formats
508 border_count = self.workbook.border_count
509 fill_count = self.workbook.fill_count
510 custom_colors = self.workbook.custom_colors
511 dxf_formats = self.workbook.dxf_formats
512 has_comments = self.workbook.has_comments
513
514 styles = Styles()
515 styles._set_style_properties(
516 [
517 xf_formats,
518 palette,
519 font_count,
520 num_formats,
521 border_count,
522 fill_count,
523 custom_colors,
524 dxf_formats,
525 has_comments,
526 ]
527 )
528
529 styles._set_xml_writer(self._filename("xl/styles.xml"))
530 styles._assemble_xml_file()
531
532 def _write_theme_file(self):
533 # Write the theme xml file.
534 theme = Theme()
535
536 theme._set_xml_writer(self._filename("xl/theme/theme1.xml"))
537 theme._assemble_xml_file()
538
539 def _write_table_files(self):
540 # Write the table files.
541 index = 1
542 for worksheet in self.workbook.worksheets():
543 table_props = worksheet.tables
544
545 if not table_props:
546 continue
547
548 for table_props in table_props:
549 table = Table()
550 table._set_xml_writer(
551 self._filename("xl/tables/table" + str(index) + ".xml")
552 )
553 table._set_properties(table_props)
554 table._assemble_xml_file()
555 index += 1
556
557 def _get_table_count(self):
558 # Count the table files. Required for the [Content_Types] file.
559 for worksheet in self.workbook.worksheets():
560 for _ in worksheet.tables:
561 self.table_count += 1
562
563 def _write_root_rels_file(self):
564 # Write the _rels/.rels xml file.
565 rels = Relationships()
566
567 rels._add_document_relationship("/officeDocument", "xl/workbook.xml")
568
569 rels._add_package_relationship("/metadata/core-properties", "docProps/core.xml")
570
571 rels._add_document_relationship("/extended-properties", "docProps/app.xml")
572
573 if self.workbook.custom_properties:
574 rels._add_document_relationship("/custom-properties", "docProps/custom.xml")
575
576 rels._set_xml_writer(self._filename("_rels/.rels"))
577
578 rels._assemble_xml_file()
579
580 def _write_workbook_rels_file(self):
581 # Write the _rels/.rels xml file.
582 rels = Relationships()
583
584 worksheet_index = 1
585 chartsheet_index = 1
586
587 for worksheet in self.workbook.worksheets():
588 if worksheet.is_chartsheet:
589 rels._add_document_relationship(
590 "/chartsheet", "chartsheets/sheet" + str(chartsheet_index) + ".xml"
591 )
592 chartsheet_index += 1
593 else:
594 rels._add_document_relationship(
595 "/worksheet", "worksheets/sheet" + str(worksheet_index) + ".xml"
596 )
597 worksheet_index += 1
598
599 rels._add_document_relationship("/theme", "theme/theme1.xml")
600 rels._add_document_relationship("/styles", "styles.xml")
601
602 # Add the sharedString rel if there is string data in the workbook.
603 if self.workbook.str_table.count:
604 rels._add_document_relationship("/sharedStrings", "sharedStrings.xml")
605
606 # Add vbaProject if present.
607 if self.workbook.vba_project:
608 rels._add_ms_package_relationship("/vbaProject", "vbaProject.bin")
609
610 # Add the metadata file if required.
611 if self.workbook.has_metadata:
612 rels._add_document_relationship("/sheetMetadata", "metadata.xml")
613
614 # Add the RichValue files if present.
615 if self.workbook.embedded_images.has_images():
616 rels._add_rich_value_relationship()
617
618 # Add the checkbox/FeaturePropertyBag file if present.
619 if self.workbook._has_feature_property_bags():
620 rels._add_feature_bag_relationship()
621
622 rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
623 rels._assemble_xml_file()
624
625 def _write_worksheet_rels_files(self):
626 # Write data such as hyperlinks or drawings.
627 index = 0
628 for worksheet in self.workbook.worksheets():
629 if worksheet.is_chartsheet:
630 continue
631
632 index += 1
633
634 external_links = (
635 worksheet.external_hyper_links
636 + worksheet.external_drawing_links
637 + worksheet.external_vml_links
638 + worksheet.external_background_links
639 + worksheet.external_table_links
640 + worksheet.external_comment_links
641 )
642
643 if not external_links:
644 continue
645
646 # Create the worksheet .rels dirs.
647 rels = Relationships()
648
649 for link_data in external_links:
650 rels._add_document_relationship(*link_data)
651
652 # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
653 rels._set_xml_writer(
654 self._filename("xl/worksheets/_rels/sheet" + str(index) + ".xml.rels")
655 )
656 rels._assemble_xml_file()
657
658 def _write_chartsheet_rels_files(self):
659 # Write the chartsheet .rels files for links to drawing files.
660 index = 0
661 for worksheet in self.workbook.worksheets():
662 if not worksheet.is_chartsheet:
663 continue
664
665 index += 1
666
667 external_links = (
668 worksheet.external_drawing_links + worksheet.external_vml_links
669 )
670
671 if not external_links:
672 continue
673
674 # Create the chartsheet .rels xlsx_dir.
675 rels = Relationships()
676
677 for link_data in external_links:
678 rels._add_document_relationship(*link_data)
679
680 # Create .rels file such as /xl/chartsheets/_rels/sheet1.xml.rels.
681 rels._set_xml_writer(
682 self._filename("xl/chartsheets/_rels/sheet" + str(index) + ".xml.rels")
683 )
684 rels._assemble_xml_file()
685
686 def _write_drawing_rels_files(self):
687 # Write the drawing .rels files for worksheets with charts or drawings.
688 index = 0
689 for worksheet in self.workbook.worksheets():
690 if worksheet.drawing:
691 index += 1
692
693 if not worksheet.drawing_links:
694 continue
695
696 # Create the drawing .rels xlsx_dir.
697 rels = Relationships()
698
699 for drawing_data in worksheet.drawing_links:
700 rels._add_document_relationship(*drawing_data)
701
702 # Create .rels file such as /xl/drawings/_rels/sheet1.xml.rels.
703 rels._set_xml_writer(
704 self._filename("xl/drawings/_rels/drawing" + str(index) + ".xml.rels")
705 )
706 rels._assemble_xml_file()
707
708 def _write_vml_drawing_rels_file(self, worksheet, index):
709 # Write the vmlDdrawing .rels files for worksheets with images in
710 # headers or footers.
711
712 # Create the drawing .rels dir.
713 rels = Relationships()
714
715 for drawing_data in worksheet.vml_drawing_links:
716 rels._add_document_relationship(*drawing_data)
717
718 # Create .rels file such as /xl/drawings/_rels/vmlDrawing1.vml.rels.
719 rels._set_xml_writer(
720 self._filename("xl/drawings/_rels/vmlDrawing" + str(index) + ".vml.rels")
721 )
722 rels._assemble_xml_file()
723
724 def _write_vba_project_rels_file(self):
725 # Write the vbaProject.rels xml file if signed macros exist.
726 vba_project_signature = self.workbook.vba_project_signature
727
728 if not vba_project_signature:
729 return
730
731 # Create the vbaProject .rels dir.
732 rels = Relationships()
733
734 rels._add_ms_package_relationship(
735 "/vbaProjectSignature", "vbaProjectSignature.bin"
736 )
737
738 rels._set_xml_writer(self._filename("xl/_rels/vbaProject.bin.rels"))
739 rels._assemble_xml_file()
740
741 def _write_rich_value_rels_files(self):
742 # Write the richValueRel.xml.rels for embedded images.
743 if not self.workbook.embedded_images.has_images():
744 return
745
746 # Create the worksheet .rels dirs.
747 rels = Relationships()
748
749 index = 1
750 for image in self.workbook.embedded_images.images:
751 image_extension = image.image_type.lower()
752 image_file = f"../media/image{index}.{image_extension}"
753 rels._add_document_relationship("/image", image_file)
754 index += 1
755
756 # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
757 rels._set_xml_writer(self._filename("/xl/richData/_rels/richValueRel.xml.rels"))
758
759 rels._assemble_xml_file()
760
761 def _add_image_files(self):
762 # pylint: disable=consider-using-with
763 # Write the /xl/media/image?.xml files.
764 workbook = self.workbook
765 index = 1
766
767 images = workbook.embedded_images.images + workbook.images
768
769 for image in images:
770 xml_image_name = (
771 "xl/media/image" + str(index) + "." + image._image_extension
772 )
773
774 if not self.in_memory:
775 # In file mode we just write or copy the image file.
776 os_filename = self._filename(xml_image_name)
777
778 if image.image_data:
779 # The data is in a byte stream. Write it to the target.
780 os_file = open(os_filename, mode="wb")
781 os_file.write(image.image_data.getvalue())
782 os_file.close()
783 else:
784 copy(image.filename, os_filename)
785
786 # Allow copies of Windows read-only images to be deleted.
787 try:
788 os.chmod(
789 os_filename, os.stat(os_filename).st_mode | stat.S_IWRITE
790 )
791 except OSError:
792 pass
793 else:
794 # For in-memory mode we read the image into a stream.
795 if image.image_data:
796 # The data is already in a byte stream.
797 os_filename = image.image_data
798 else:
799 image_file = open(image.filename, mode="rb")
800 image_data = image_file.read()
801 os_filename = BytesIO(image_data)
802 image_file.close()
803
804 self.filenames.append((os_filename, xml_image_name, True))
805
806 index += 1
807
808 def _add_vba_project_signature(self):
809 # pylint: disable=consider-using-with
810 # Copy in a vbaProjectSignature.bin file.
811 vba_project_signature = self.workbook.vba_project_signature
812 vba_project_signature_is_stream = self.workbook.vba_project_signature_is_stream
813
814 if not vba_project_signature:
815 return
816
817 xml_vba_signature_name = "xl/vbaProjectSignature.bin"
818
819 if not self.in_memory:
820 # In file mode we just write or copy the VBA project signature file.
821 os_filename = self._filename(xml_vba_signature_name)
822
823 if vba_project_signature_is_stream:
824 # The data is in a byte stream. Write it to the target.
825 os_file = open(os_filename, mode="wb")
826 os_file.write(vba_project_signature.getvalue())
827 os_file.close()
828 else:
829 copy(vba_project_signature, os_filename)
830
831 else:
832 # For in-memory mode we read the vba into a stream.
833 if vba_project_signature_is_stream:
834 # The data is already in a byte stream.
835 os_filename = vba_project_signature
836 else:
837 vba_file = open(vba_project_signature, mode="rb")
838 vba_data = vba_file.read()
839 os_filename = BytesIO(vba_data)
840 vba_file.close()
841
842 self.filenames.append((os_filename, xml_vba_signature_name, True))
843
844 def _add_vba_project(self):
845 # pylint: disable=consider-using-with
846 # Copy in a vbaProject.bin file.
847 vba_project = self.workbook.vba_project
848 vba_project_is_stream = self.workbook.vba_project_is_stream
849
850 if not vba_project:
851 return
852
853 xml_vba_name = "xl/vbaProject.bin"
854
855 if not self.in_memory:
856 # In file mode we just write or copy the VBA file.
857 os_filename = self._filename(xml_vba_name)
858
859 if vba_project_is_stream:
860 # The data is in a byte stream. Write it to the target.
861 os_file = open(os_filename, mode="wb")
862 os_file.write(vba_project.getvalue())
863 os_file.close()
864 else:
865 copy(vba_project, os_filename)
866
867 else:
868 # For in-memory mode we read the vba into a stream.
869 if vba_project_is_stream:
870 # The data is already in a byte stream.
871 os_filename = vba_project
872 else:
873 vba_file = open(vba_project, mode="rb")
874 vba_data = vba_file.read()
875 os_filename = BytesIO(vba_data)
876 vba_file.close()
877
878 self.filenames.append((os_filename, xml_vba_name, True))