###############################################################################
#
# Packager - A class for writing the Excel XLSX Worksheet file.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
#

# Standard packages.
import os
import stat
import tempfile
from io import BytesIO, StringIO
from shutil import copy

# Package imports.
from .app import App
from .comments import Comments
from .contenttypes import ContentTypes
from .core import Core
from .custom import Custom
from .exceptions import EmptyChartSeries
from .feature_property_bag import FeaturePropertyBag
from .metadata import Metadata
from .relationships import Relationships
from .rich_value import RichValue
from .rich_value_rel import RichValueRel
from .rich_value_structure import RichValueStructure
from .rich_value_types import RichValueTypes
from .sharedstrings import SharedStrings
from .styles import Styles
from .table import Table
from .theme import Theme
from .vml import Vml


class Packager:
    """
    A class for writing the Excel XLSX Packager file.

    This module is used in conjunction with XlsxWriter to create an
    Excel XLSX container file.

    From Wikipedia: The Open Packaging Conventions (OPC) is a
    container-file technology initially created by Microsoft to store
    a combination of XML and non-XML files that together form a single
    entity such as an Open XML Paper Specification (OpenXPS)
    document. http://en.wikipedia.org/wiki/Open_Packaging_Conventions.

    At its simplest an Excel XLSX file contains the following elements::

         ____ [Content_Types].xml
        |
        |____ docProps
        | |____ app.xml
        | |____ core.xml
        |
        |____ xl
        | |____ workbook.xml
        | |____ worksheets
        | | |____ sheet1.xml
        | |
        | |____ styles.xml
        | |
        | |____ theme
        | | |____ theme1.xml
        | |
        | |_____rels
        |   |____ workbook.xml.rels
        |
        |_____rels
          |____ .rels

    The Packager class coordinates the classes that represent the
    elements of the package and writes them into the XLSX file.

    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self) -> None:
        """
        Constructor.

        """

        super().__init__()

        self.tmpdir = ""
        self.in_memory = False
        self.workbook = None
        self.worksheet_count = 0
        self.chartsheet_count = 0
        self.chart_count = 0
        self.drawing_count = 0
        self.table_count = 0
        self.num_vml_files = 0
        self.num_comment_files = 0
        self.named_ranges = []
        self.filenames = []

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _set_tmpdir(self, tmpdir) -> None:
        # Set an optional user defined temp directory.
        self.tmpdir = tmpdir

    def _set_in_memory(self, in_memory) -> None:
        # Set the optional 'in_memory' mode.
        self.in_memory = in_memory

    def _add_workbook(self, workbook) -> None:
        # Add the Excel::Writer::XLSX::Workbook object to the package.
        self.workbook = workbook
        self.chart_count = len(workbook.charts)
        self.drawing_count = len(workbook.drawings)
        self.num_vml_files = workbook.num_vml_files
        self.num_comment_files = workbook.num_comment_files
        self.named_ranges = workbook.named_ranges

        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                self.chartsheet_count += 1
            else:
                self.worksheet_count += 1

    def _create_package(self):
        # Write the xml files that make up the XLSX OPC package.
        self._write_content_types_file()
        self._write_root_rels_file()
        self._write_workbook_rels_file()
        self._write_worksheet_files()
        self._write_chartsheet_files()
        self._write_workbook_file()
        self._write_chart_files()
        self._write_drawing_files()
        self._write_vml_files()
        self._write_comment_files()
        self._write_table_files()
        self._write_shared_strings_file()
        self._write_styles_file()
        self._write_custom_file()
        self._write_theme_file()
        self._write_worksheet_rels_files()
        self._write_chartsheet_rels_files()
        self._write_drawing_rels_files()
        self._write_rich_value_rels_files()
        self._add_image_files()
        self._add_vba_project()
        self._add_vba_project_signature()
        self._write_vba_project_rels_file()
        self._write_core_file()
        self._write_app_file()
        self._write_metadata_file()
        self._write_feature_bag_property()
        self._write_rich_value_files()

        return self.filenames

    def _filename(self, xml_filename):
        # Create a temp filename to write the XML data to and store the Excel
        # filename to use as the name in the Zip container.
        if self.in_memory:
            os_filename = StringIO()
        else:
            (fd, os_filename) = tempfile.mkstemp(dir=self.tmpdir)
            os.close(fd)

        self.filenames.append((os_filename, xml_filename, False))

        return os_filename

    def _write_workbook_file(self) -> None:
        # Write the workbook.xml file.
        workbook = self.workbook

        workbook._set_xml_writer(self._filename("xl/workbook.xml"))
        workbook._assemble_xml_file()

    def _write_worksheet_files(self) -> None:
        # Write the worksheet files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            if worksheet.constant_memory:
                worksheet._opt_reopen()
                worksheet._write_single_row()

            worksheet._set_xml_writer(
                self._filename("xl/worksheets/sheet" + str(index) + ".xml")
            )
            worksheet._assemble_xml_file()
            index += 1

    def _write_chartsheet_files(self) -> None:
        # Write the chartsheet files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue

            worksheet._set_xml_writer(
                self._filename("xl/chartsheets/sheet" + str(index) + ".xml")
            )
            worksheet._assemble_xml_file()
            index += 1

    def _write_chart_files(self) -> None:
        # Write the chart files.
        if not self.workbook.charts:
            return

        index = 1
        for chart in self.workbook.charts:
            # Check that the chart has at least one data series.
            if not chart.series:
                raise EmptyChartSeries(
                    f"Chart{index} must contain at least one "
                    f"data series. See chart.add_series()."
                )

            chart._set_xml_writer(
                self._filename("xl/charts/chart" + str(index) + ".xml")
            )
            chart._assemble_xml_file()
            index += 1

    def _write_drawing_files(self) -> None:
        # Write the drawing files.
        if not self.drawing_count:
            return

        index = 1
        for drawing in self.workbook.drawings:
            drawing._set_xml_writer(
                self._filename("xl/drawings/drawing" + str(index) + ".xml")
            )
            drawing._assemble_xml_file()
            index += 1

    def _write_vml_files(self) -> None:
        # Write the comment VML files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.has_vml and not worksheet.has_header_vml:
                continue
            if worksheet.has_vml:
                vml = Vml()
                vml._set_xml_writer(
                    self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
                )
                vml._assemble_xml_file(
                    worksheet.vml_data_id,
                    worksheet.vml_shape_id,
                    worksheet.comments_list,
                    worksheet.buttons_list,
                )
                index += 1

            if worksheet.has_header_vml:
                vml = Vml()

                vml._set_xml_writer(
                    self._filename("xl/drawings/vmlDrawing" + str(index) + ".vml")
                )
                vml._assemble_xml_file(
                    worksheet.vml_header_id,
                    worksheet.vml_header_id * 1024,
                    None,
                    None,
                    worksheet.header_images_list,
                )

                self._write_vml_drawing_rels_file(worksheet, index)
                index += 1

    def _write_comment_files(self) -> None:
        # Write the comment files.
        index = 1
        for worksheet in self.workbook.worksheets():
            if not worksheet.has_comments:
                continue

            comment = Comments()
            comment._set_xml_writer(self._filename("xl/comments" + str(index) + ".xml"))
            comment._assemble_xml_file(worksheet.comments_list)
            index += 1

    def _write_shared_strings_file(self) -> None:
        # Write the sharedStrings.xml file.
        sst = SharedStrings()
        sst.string_table = self.workbook.str_table

        if not self.workbook.str_table.count:
            return

        sst._set_xml_writer(self._filename("xl/sharedStrings.xml"))
        sst._assemble_xml_file()

    def _write_app_file(self) -> None:
        # Write the app.xml file.
        properties = self.workbook.doc_properties
        app = App()

        # Add the Worksheet parts.
        worksheet_count = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            # Don't write/count veryHidden sheets.
            if worksheet.hidden != 2:
                app._add_part_name(worksheet.name)
                worksheet_count += 1

        # Add the Worksheet heading pairs.
        app._add_heading_pair(["Worksheets", worksheet_count])

        # Add the Chartsheet parts.
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue
            app._add_part_name(worksheet.name)

        # Add the Chartsheet heading pairs.
        app._add_heading_pair(["Charts", self.chartsheet_count])

        # Add the Named Range heading pairs.
        if self.named_ranges:
            app._add_heading_pair(["Named Ranges", len(self.named_ranges)])

        # Add the Named Ranges parts.
        for named_range in self.named_ranges:
            app._add_part_name(named_range)

        app._set_properties(properties)
        app.doc_security = self.workbook.read_only

        app._set_xml_writer(self._filename("docProps/app.xml"))
        app._assemble_xml_file()

    def _write_core_file(self) -> None:
        # Write the core.xml file.
        properties = self.workbook.doc_properties
        core = Core()

        core._set_properties(properties)
        core._set_xml_writer(self._filename("docProps/core.xml"))
        core._assemble_xml_file()

    def _write_metadata_file(self) -> None:
        # Write the metadata.xml file.
        if not self.workbook.has_metadata:
            return

        metadata = Metadata()
        metadata.has_dynamic_functions = self.workbook.has_dynamic_functions
        metadata.num_embedded_images = len(self.workbook.embedded_images.images)

        metadata._set_xml_writer(self._filename("xl/metadata.xml"))
        metadata._assemble_xml_file()

    def _write_feature_bag_property(self) -> None:
        # Write the featurePropertyBag.xml file.
        feature_property_bags = self.workbook._has_feature_property_bags()
        if not feature_property_bags:
            return

        property_bag = FeaturePropertyBag()
        property_bag.feature_property_bags = feature_property_bags

        property_bag._set_xml_writer(
            self._filename("xl/featurePropertyBag/featurePropertyBag.xml")
        )
        property_bag._assemble_xml_file()

    def _write_rich_value_files(self) -> None:

        if not self.workbook.embedded_images.has_images():
            return

        self._write_rich_value()
        self._write_rich_value_types()
        self._write_rich_value_structure()
        self._write_rich_value_rel()

    def _write_rich_value(self) -> None:
        # Write the rdrichvalue.xml file.
        filename = self._filename("xl/richData/rdrichvalue.xml")
        xml_file = RichValue()
        xml_file.embedded_images = self.workbook.embedded_images.images
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_types(self) -> None:
        # Write the rdRichValueTypes.xml file.
        filename = self._filename("xl/richData/rdRichValueTypes.xml")
        xml_file = RichValueTypes()
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_structure(self) -> None:
        # Write the rdrichvaluestructure.xml file.
        filename = self._filename("xl/richData/rdrichvaluestructure.xml")
        xml_file = RichValueStructure()
        xml_file.has_embedded_descriptions = self.workbook.has_embedded_descriptions
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_rich_value_rel(self) -> None:
        # Write the richValueRel.xml file.
        filename = self._filename("xl/richData/richValueRel.xml")
        xml_file = RichValueRel()
        xml_file.num_embedded_images = len(self.workbook.embedded_images.images)
        xml_file._set_xml_writer(filename)
        xml_file._assemble_xml_file()

    def _write_custom_file(self) -> None:
        # Write the custom.xml file.
        properties = self.workbook.custom_properties
        custom = Custom()

        if not properties:
            return

        custom._set_properties(properties)
        custom._set_xml_writer(self._filename("docProps/custom.xml"))
        custom._assemble_xml_file()

    def _write_content_types_file(self) -> None:
        # Write the ContentTypes.xml file.
        content = ContentTypes()
        content._add_image_types(self.workbook.image_types)

        self._get_table_count()

        worksheet_index = 1
        chartsheet_index = 1
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                content._add_chartsheet_name("sheet" + str(chartsheet_index))
                chartsheet_index += 1
            else:
                content._add_worksheet_name("sheet" + str(worksheet_index))
                worksheet_index += 1

        for i in range(1, self.chart_count + 1):
            content._add_chart_name("chart" + str(i))

        for i in range(1, self.drawing_count + 1):
            content._add_drawing_name("drawing" + str(i))

        if self.num_vml_files:
            content._add_vml_name()

        for i in range(1, self.table_count + 1):
            content._add_table_name("table" + str(i))

        for i in range(1, self.num_comment_files + 1):
            content._add_comment_name("comments" + str(i))

        # Add the sharedString rel if there is string data in the workbook.
        if self.workbook.str_table.count:
            content._add_shared_strings()

        # Add vbaProject (and optionally vbaProjectSignature) if present.
        if self.workbook.vba_project:
            content._add_vba_project()
            if self.workbook.vba_project_signature:
                content._add_vba_project_signature()

        # Add the custom properties if present.
        if self.workbook.custom_properties:
            content._add_custom_properties()

        # Add the metadata file if present.
        if self.workbook.has_metadata:
            content._add_metadata()

        # Add the metadata file if present.
        if self.workbook._has_feature_property_bags():
            content._add_feature_bag_property()

        # Add the RichValue file if present.
        if self.workbook.embedded_images.has_images():
            content._add_rich_value()

        content._set_xml_writer(self._filename("[Content_Types].xml"))
        content._assemble_xml_file()

    def _write_styles_file(self) -> None:
        # Write the style xml file.
        xf_formats = self.workbook.xf_formats
        palette = self.workbook.palette
        font_count = self.workbook.font_count
        num_formats = self.workbook.num_formats
        border_count = self.workbook.border_count
        fill_count = self.workbook.fill_count
        custom_colors = self.workbook.custom_colors
        dxf_formats = self.workbook.dxf_formats
        has_comments = self.workbook.has_comments

        styles = Styles()
        styles._set_style_properties(
            [
                xf_formats,
                palette,
                font_count,
                num_formats,
                border_count,
                fill_count,
                custom_colors,
                dxf_formats,
                has_comments,
            ]
        )

        styles._set_xml_writer(self._filename("xl/styles.xml"))
        styles._assemble_xml_file()

    def _write_theme_file(self) -> None:
        # Write the theme xml file.
        theme = Theme()

        theme._set_xml_writer(self._filename("xl/theme/theme1.xml"))
        theme._assemble_xml_file()

    def _write_table_files(self) -> None:
        # Write the table files.
        index = 1
        for worksheet in self.workbook.worksheets():
            table_props = worksheet.tables

            if not table_props:
                continue

            for table_props in table_props:
                table = Table()
                table._set_xml_writer(
                    self._filename("xl/tables/table" + str(index) + ".xml")
                )
                table._set_properties(table_props)
                table._assemble_xml_file()
                index += 1

    def _get_table_count(self) -> None:
        # Count the table files. Required for the [Content_Types] file.
        for worksheet in self.workbook.worksheets():
            for _ in worksheet.tables:
                self.table_count += 1

    def _write_root_rels_file(self) -> None:
        # Write the _rels/.rels xml file.
        rels = Relationships()

        rels._add_document_relationship("/officeDocument", "xl/workbook.xml")

        rels._add_package_relationship("/metadata/core-properties", "docProps/core.xml")

        rels._add_document_relationship("/extended-properties", "docProps/app.xml")

        if self.workbook.custom_properties:
            rels._add_document_relationship("/custom-properties", "docProps/custom.xml")

        rels._set_xml_writer(self._filename("_rels/.rels"))

        rels._assemble_xml_file()

    def _write_workbook_rels_file(self) -> None:
        # Write the _rels/.rels xml file.
        rels = Relationships()

        worksheet_index = 1
        chartsheet_index = 1

        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                rels._add_document_relationship(
                    "/chartsheet", "chartsheets/sheet" + str(chartsheet_index) + ".xml"
                )
                chartsheet_index += 1
            else:
                rels._add_document_relationship(
                    "/worksheet", "worksheets/sheet" + str(worksheet_index) + ".xml"
                )
                worksheet_index += 1

        rels._add_document_relationship("/theme", "theme/theme1.xml")
        rels._add_document_relationship("/styles", "styles.xml")

        # Add the sharedString rel if there is string data in the workbook.
        if self.workbook.str_table.count:
            rels._add_document_relationship("/sharedStrings", "sharedStrings.xml")

        # Add vbaProject if present.
        if self.workbook.vba_project:
            rels._add_ms_package_relationship("/vbaProject", "vbaProject.bin")

        # Add the metadata file if required.
        if self.workbook.has_metadata:
            rels._add_document_relationship("/sheetMetadata", "metadata.xml")

        # Add the RichValue files if present.
        if self.workbook.embedded_images.has_images():
            rels._add_rich_value_relationship()

        # Add the checkbox/FeaturePropertyBag file if present.
        if self.workbook._has_feature_property_bags():
            rels._add_feature_bag_relationship()

        rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
        rels._assemble_xml_file()

    def _write_worksheet_rels_files(self) -> None:
        # Write data such as hyperlinks or drawings.
        index = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.is_chartsheet:
                continue

            index += 1

            external_links = (
                worksheet.external_hyper_links
                + worksheet.external_drawing_links
                + worksheet.external_vml_links
                + worksheet.external_background_links
                + worksheet.external_table_links
                + worksheet.external_comment_links
            )

            if not external_links:
                continue

            # Create the worksheet .rels dirs.
            rels = Relationships()

            for link_data in external_links:
                rels._add_document_relationship(*link_data)

            # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/worksheets/_rels/sheet" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_chartsheet_rels_files(self) -> None:
        # Write the chartsheet .rels files for links to drawing files.
        index = 0
        for worksheet in self.workbook.worksheets():
            if not worksheet.is_chartsheet:
                continue

            index += 1

            external_links = (
                worksheet.external_drawing_links + worksheet.external_vml_links
            )

            if not external_links:
                continue

            # Create the chartsheet .rels xlsx_dir.
            rels = Relationships()

            for link_data in external_links:
                rels._add_document_relationship(*link_data)

            # Create .rels file such as /xl/chartsheets/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/chartsheets/_rels/sheet" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_drawing_rels_files(self) -> None:
        # Write the drawing .rels files for worksheets with charts or drawings.
        index = 0
        for worksheet in self.workbook.worksheets():
            if worksheet.drawing:
                index += 1

            if not worksheet.drawing_links:
                continue

            # Create the drawing .rels xlsx_dir.
            rels = Relationships()

            for drawing_data in worksheet.drawing_links:
                rels._add_document_relationship(*drawing_data)

            # Create .rels file such as /xl/drawings/_rels/sheet1.xml.rels.
            rels._set_xml_writer(
                self._filename("xl/drawings/_rels/drawing" + str(index) + ".xml.rels")
            )
            rels._assemble_xml_file()

    def _write_vml_drawing_rels_file(self, worksheet, index) -> None:
        # Write the vmlDdrawing .rels files for worksheets with images in
        # headers or footers.

        # Create the drawing .rels dir.
        rels = Relationships()

        for drawing_data in worksheet.vml_drawing_links:
            rels._add_document_relationship(*drawing_data)

        # Create .rels file such as /xl/drawings/_rels/vmlDrawing1.vml.rels.
        rels._set_xml_writer(
            self._filename("xl/drawings/_rels/vmlDrawing" + str(index) + ".vml.rels")
        )
        rels._assemble_xml_file()

    def _write_vba_project_rels_file(self) -> None:
        # Write the vbaProject.rels xml file if signed macros exist.
        vba_project_signature = self.workbook.vba_project_signature

        if not vba_project_signature:
            return

        # Create the vbaProject .rels dir.
        rels = Relationships()

        rels._add_ms_package_relationship(
            "/vbaProjectSignature", "vbaProjectSignature.bin"
        )

        rels._set_xml_writer(self._filename("xl/_rels/vbaProject.bin.rels"))
        rels._assemble_xml_file()

    def _write_rich_value_rels_files(self) -> None:
        # Write the richValueRel.xml.rels for embedded images.
        if not self.workbook.embedded_images.has_images():
            return

        # Create the worksheet .rels dirs.
        rels = Relationships()

        index = 1
        for image in self.workbook.embedded_images.images:
            image_extension = image.image_type.lower()
            image_file = f"../media/image{index}.{image_extension}"
            rels._add_document_relationship("/image", image_file)
            index += 1

        # Create .rels file such as /xl/worksheets/_rels/sheet1.xml.rels.
        rels._set_xml_writer(self._filename("/xl/richData/_rels/richValueRel.xml.rels"))

        rels._assemble_xml_file()

    def _add_image_files(self) -> None:
        # pylint: disable=consider-using-with
        # Write the /xl/media/image?.xml files.
        workbook = self.workbook
        index = 1

        images = workbook.embedded_images.images + workbook.images

        for image in images:
            xml_image_name = (
                "xl/media/image" + str(index) + "." + image._image_extension
            )

            if not self.in_memory:
                # In file mode we just write or copy the image file.
                os_filename = self._filename(xml_image_name)

                if image.image_data:
                    # The data is in a byte stream. Write it to the target.
                    os_file = open(os_filename, mode="wb")
                    os_file.write(image.image_data.getvalue())
                    os_file.close()
                else:
                    copy(image.filename, os_filename)

                    # Allow copies of Windows read-only images to be deleted.
                    try:
                        os.chmod(
                            os_filename, os.stat(os_filename).st_mode | stat.S_IWRITE
                        )
                    except OSError:
                        pass
            else:
                # For in-memory mode we read the image into a stream.
                if image.image_data:
                    # The data is already in a byte stream.
                    os_filename = image.image_data
                else:
                    image_file = open(image.filename, mode="rb")
                    image_data = image_file.read()
                    os_filename = BytesIO(image_data)
                    image_file.close()

                self.filenames.append((os_filename, xml_image_name, True))

            index += 1

    def _add_vba_project_signature(self) -> None:
        # pylint: disable=consider-using-with
        # Copy in a vbaProjectSignature.bin file.
        vba_project_signature = self.workbook.vba_project_signature
        vba_project_signature_is_stream = self.workbook.vba_project_signature_is_stream

        if not vba_project_signature:
            return

        xml_vba_signature_name = "xl/vbaProjectSignature.bin"

        if not self.in_memory:
            # In file mode we just write or copy the VBA project signature file.
            os_filename = self._filename(xml_vba_signature_name)

            if vba_project_signature_is_stream:
                # The data is in a byte stream. Write it to the target.
                os_file = open(os_filename, mode="wb")
                os_file.write(vba_project_signature.getvalue())
                os_file.close()
            else:
                copy(vba_project_signature, os_filename)

        else:
            # For in-memory mode we read the vba into a stream.
            if vba_project_signature_is_stream:
                # The data is already in a byte stream.
                os_filename = vba_project_signature
            else:
                vba_file = open(vba_project_signature, mode="rb")
                vba_data = vba_file.read()
                os_filename = BytesIO(vba_data)
                vba_file.close()

            self.filenames.append((os_filename, xml_vba_signature_name, True))

    def _add_vba_project(self) -> None:
        # pylint: disable=consider-using-with
        # Copy in a vbaProject.bin file.
        vba_project = self.workbook.vba_project
        vba_project_is_stream = self.workbook.vba_project_is_stream

        if not vba_project:
            return

        xml_vba_name = "xl/vbaProject.bin"

        if not self.in_memory:
            # In file mode we just write or copy the VBA file.
            os_filename = self._filename(xml_vba_name)

            if vba_project_is_stream:
                # The data is in a byte stream. Write it to the target.
                os_file = open(os_filename, mode="wb")
                os_file.write(vba_project.getvalue())
                os_file.close()
            else:
                copy(vba_project, os_filename)

        else:
            # For in-memory mode we read the vba into a stream.
            if vba_project_is_stream:
                # The data is already in a byte stream.
                os_filename = vba_project
            else:
                vba_file = open(vba_project, mode="rb")
                vba_data = vba_file.read()
                os_filename = BytesIO(vba_data)
                vba_file.close()

            self.filenames.append((os_filename, xml_vba_name, True))
