diff --git a/xlsxwriter/contenttypes.py b/xlsxwriter/contenttypes.py index 9b4f67b11..ecad3209e 100644 --- a/xlsxwriter/contenttypes.py +++ b/xlsxwriter/contenttypes.py @@ -179,6 +179,33 @@ def _add_metadata(self): ("/xl/metadata.xml", app_document + "spreadsheetml.sheetMetadata+xml") ) + def _add_rich_value(self): + # Add the richValue files to the ContentTypes overrides. + self._add_override( + ( + "/xl/richData/rdRichValueTypes.xml", + "application/vnd.ms-excel.rdrichvaluetypes+xml", + ) + ) + + self._add_override( + ("/xl/richData/rdrichvalue.xml", "application/vnd.ms-excel.rdrichvalue+xml") + ) + + self._add_override( + ( + "/xl/richData/rdrichvaluestructure.xml", + "application/vnd.ms-excel.rdrichvaluestructure+xml", + ) + ) + + self._add_override( + ( + "/xl/richData/richValueRel.xml", + "application/vnd.ms-excel.richvaluerel+xml", + ) + ) + ########################################################################### # # XML methods. diff --git a/xlsxwriter/metadata.py b/xlsxwriter/metadata.py index 5495a968b..335938ceb 100644 --- a/xlsxwriter/metadata.py +++ b/xlsxwriter/metadata.py @@ -29,6 +29,9 @@ def __init__(self): """ super(Metadata, self).__init__() + self.has_dynamic_functions = False + self.has_embedded_images = False + self.num_embedded_images = 0 ########################################################################### # @@ -39,6 +42,9 @@ def __init__(self): def _assemble_xml_file(self): # Assemble and write the XML file. + if self.num_embedded_images > 0: + self.has_embedded_images = True + # Write the XML declaration. self._xml_declaration() @@ -48,11 +54,17 @@ def _assemble_xml_file(self): # Write the metadataTypes element. self._write_metadata_types() - # Write the futureMetadata element. - self._write_future_metadata() + # Write the futureMetadata elements. + if self.has_dynamic_functions: + self._write_cell_future_metadata() + if self.has_embedded_images: + self._write_value_future_metadata() # Write the cellMetadata element. - self._write_cell_metadata() + if self.has_dynamic_functions: + self._write_cell_metadata() + if self.has_embedded_images: + self._write_value_metadata() self._xml_end_tag("metadata") @@ -68,28 +80,40 @@ def _assemble_xml_file(self): def _write_metadata(self): # Write the element. xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" - schema = "http://schemas.microsoft.com/office" - xmlns_xda = schema + "/spreadsheetml/2017/dynamicarray" + schema = "http://schemas.microsoft.com/office/spreadsheetml" - attributes = [ - ("xmlns", xmlns), - ("xmlns:xda", xmlns_xda), - ] + attributes = [("xmlns", xmlns)] + + if self.has_embedded_images: + attributes.append(("xmlns:xlrd", schema + "/2017/richdata")) + + if self.has_dynamic_functions: + attributes.append(("xmlns:xda", schema + "/2017/dynamicarray")) self._xml_start_tag("metadata", attributes) def _write_metadata_types(self): # Write the element. - attributes = [("count", 1)] + count = 0 + + if self.has_dynamic_functions: + count += 1 + if self.has_embedded_images: + count += 1 + + attributes = [("count", count)] self._xml_start_tag("metadataTypes", attributes) # Write the metadataType element. - self._write_metadata_type() + if self.has_dynamic_functions: + self._write_cell_metadata_type() + if self.has_embedded_images: + self._write_value_metadata_type() self._xml_end_tag("metadataTypes") - def _write_metadata_type(self): + def _write_cell_metadata_type(self): # Write the element. attributes = [ ("name", "XLDAPR"), @@ -109,7 +133,26 @@ def _write_metadata_type(self): self._xml_empty_tag("metadataType", attributes) - def _write_future_metadata(self): + def _write_value_metadata_type(self): + # Write the element. + attributes = [ + ("name", "XLRICHVALUE"), + ("minSupportedVersion", 120000), + ("copy", 1), + ("pasteAll", 1), + ("pasteValues", 1), + ("merge", 1), + ("splitFirst", 1), + ("rowColShift", 1), + ("clearFormats", 1), + ("clearComments", 1), + ("assign", 1), + ("coerce", 1), + ] + + self._xml_empty_tag("metadataType", attributes) + + def _write_cell_future_metadata(self): # Write the element. attributes = [ ("name", "XLDAPR"), @@ -119,15 +162,30 @@ def _write_future_metadata(self): self._xml_start_tag("futureMetadata", attributes) self._xml_start_tag("bk") self._xml_start_tag("extLst") - - # Write the ext element. - self._write_ext() - + self._write_cell_ext() self._xml_end_tag("extLst") self._xml_end_tag("bk") self._xml_end_tag("futureMetadata") - def _write_ext(self): + def _write_value_future_metadata(self): + # Write the element. + attributes = [ + ("name", "XLRICHVALUE"), + ("count", self.num_embedded_images), + ] + + self._xml_start_tag("futureMetadata", attributes) + + for index in range(self.num_embedded_images): + self._xml_start_tag("bk") + self._xml_start_tag("extLst") + self._write_value_ext(index) + self._xml_end_tag("extLst") + self._xml_end_tag("bk") + + self._xml_end_tag("futureMetadata") + + def _write_cell_ext(self): # Write the element. attributes = [("uri", "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}")] @@ -147,6 +205,23 @@ def _write_xda_dynamic_array_properties(self): self._xml_empty_tag("xda:dynamicArrayProperties", attributes) + def _write_value_ext(self, index): + # Write the element. + attributes = [("uri", "{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}")] + + self._xml_start_tag("ext", attributes) + + # Write the xlrd:rvb element. + self._write_xlrd_rvb(index) + + self._xml_end_tag("ext") + + def _write_xlrd_rvb(self, index): + # Write the element. + attributes = [("i", index)] + + self._xml_empty_tag("xlrd:rvb", attributes) + def _write_cell_metadata(self): # Write the element. attributes = [("count", 1)] @@ -155,16 +230,36 @@ def _write_cell_metadata(self): self._xml_start_tag("bk") # Write the rc element. - self._write_rc() + self._write_rc(1, 0) self._xml_end_tag("bk") self._xml_end_tag("cellMetadata") - def _write_rc(self): + def _write_value_metadata(self): + # Write the element. + count = self.num_embedded_images + type = 1 + + if self.has_dynamic_functions: + type = 2 + + attributes = [("count", count)] + + self._xml_start_tag("valueMetadata", attributes) + + # Write the rc elements. + for index in range(self.num_embedded_images): + self._xml_start_tag("bk") + self._write_rc(type, index) + self._xml_end_tag("bk") + + self._xml_end_tag("valueMetadata") + + def _write_rc(self, type, index): # Write the element. attributes = [ - ("t", 1), - ("v", 0), + ("t", type), + ("v", index), ] self._xml_empty_tag("rc", attributes) diff --git a/xlsxwriter/packager.py b/xlsxwriter/packager.py index 772d21344..16892c171 100644 --- a/xlsxwriter/packager.py +++ b/xlsxwriter/packager.py @@ -23,6 +23,10 @@ from .metadata import Metadata from .relationships import Relationships from .sharedstrings import SharedStrings +from .rich_value import RichValue +from .rich_value_types import RichValueTypes +from .rich_value_rel import RichValueRel +from .rich_value_structure import RichValueStructure from .styles import Styles from .theme import Theme from .vml import Vml @@ -149,6 +153,7 @@ def _create_package(self): 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() @@ -156,6 +161,7 @@ def _create_package(self): self._write_core_file() self._write_app_file() self._write_metadata_file() + self._write_rich_value_files() return self.filenames @@ -358,9 +364,53 @@ def _write_metadata_file(self): 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_rich_value_files(self): + + 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): + # 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): + # 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): + # 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): + # 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): # Write the custom.xml file. properties = self.workbook.custom_properties @@ -423,6 +473,10 @@ def _write_content_types_file(self): if self.workbook.has_metadata: content._add_metadata() + # 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() @@ -538,6 +592,10 @@ def _write_workbook_rels_file(self): 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() + rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels")) rels._assemble_xml_file() @@ -657,12 +715,34 @@ def _write_vba_project_rels_file(self): rels._set_xml_writer(self._filename("xl/_rels/vbaProject.bin.rels")) rels._assemble_xml_file() + def _write_rich_value_rels_files(self): + # 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_data in self.workbook.embedded_images.images: + file_type = image_data[1] + image_file = f"../media/image{index}.{file_type}" + 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): # Write the /xl/media/image?.xml files. workbook = self.workbook index = 1 - for image in workbook.images: + images = workbook.embedded_images.images + workbook.images + + for image in images: filename = image[0] ext = "." + image[1] image_data = image[2] diff --git a/xlsxwriter/relationships.py b/xlsxwriter/relationships.py index 0db069e40..435c10baa 100644 --- a/xlsxwriter/relationships.py +++ b/xlsxwriter/relationships.py @@ -75,6 +75,28 @@ def _add_ms_package_relationship(self, rel_type, target): self.relationships.append((rel_type, target, None)) + def _add_rich_value_relationship(self): + # Add RichValue relationship to XLSX .rels xml files. + schema = "http://schemas.microsoft.com/office/2022/10/relationships/" + rel_type = schema + "richValueRel" + target = "richData/richValueRel.xml" + self.relationships.append((rel_type, target, None)) + + schema = "http://schemas.microsoft.com/office/2017/06/relationships/" + rel_type = schema + "rdRichValue" + target = "richData/rdrichvalue.xml" + self.relationships.append((rel_type, target, None)) + + schema = "http://schemas.microsoft.com/office/2017/06/relationships/" + rel_type = schema + "rdRichValueStructure" + target = "richData/rdrichvaluestructure.xml" + self.relationships.append((rel_type, target, None)) + + schema = "http://schemas.microsoft.com/office/2017/06/relationships/" + rel_type = schema + "rdRichValueTypes" + target = "richData/rdRichValueTypes.xml" + self.relationships.append((rel_type, target, None)) + ########################################################################### # # XML methods. diff --git a/xlsxwriter/rich_value.py b/xlsxwriter/rich_value.py new file mode 100644 index 000000000..5054bab4f --- /dev/null +++ b/xlsxwriter/rich_value.py @@ -0,0 +1,96 @@ +############################################################################### +# +# RichValue - A class for writing the Excel XLSX rdrichvalue.xml file. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org +# + +# Package imports. +from . import xmlwriter + + +class RichValue(xmlwriter.XMLwriter): + """ + A class for writing the Excel XLSX rdrichvalue.xml file. + + + """ + + ########################################################################### + # + # Public API. + # + ########################################################################### + + def __init__(self): + """ + Constructor. + + """ + + super(RichValue, self).__init__() + self.embedded_images = [] + + ########################################################################### + # + # Private API. + # + ########################################################################### + + def _assemble_xml_file(self): + # Assemble and write the XML file. + + # Write the XML declaration. + self._xml_declaration() + + # Write the rvData element. + self._write_rv_data() + + self._xml_end_tag("rvData") + + # Close the file. + self._xml_close() + + ########################################################################### + # + # XML methods. + # + ########################################################################### + def _write_rv_data(self): + # Write the element. + xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" + + attributes = [ + ("xmlns", xmlns), + ("count", len(self.embedded_images)), + ] + + self._xml_start_tag("rvData", attributes) + + for index, image_data in enumerate(self.embedded_images): + # Write the rv element. + self._write_rv(index, image_data[3], image_data[4]) + + def _write_rv(self, index, description, decorative): + # Write the element. + attributes = [("s", 0)] + value = 5 + + if decorative: + value = 6 + + self._xml_start_tag("rv", attributes) + + # Write the v elements. + self._write_v(index) + self._write_v(value) + + if description: + self._write_v(description) + + self._xml_end_tag("rv") + + def _write_v(self, data): + # Write the element. + self._xml_data_element("v", data) diff --git a/xlsxwriter/rich_value_rel.py b/xlsxwriter/rich_value_rel.py new file mode 100644 index 000000000..c437b6853 --- /dev/null +++ b/xlsxwriter/rich_value_rel.py @@ -0,0 +1,81 @@ +############################################################################### +# +# RichValueRel - A class for writing the Excel XLSX richValueRel.xml file. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org +# + +# Package imports. +from . import xmlwriter + + +class RichValueRel(xmlwriter.XMLwriter): + """ + A class for writing the Excel XLSX richValueRel.xml file. + + + """ + + ########################################################################### + # + # Public API. + # + ########################################################################### + + def __init__(self): + """ + Constructor. + + """ + + super(RichValueRel, self).__init__() + self.num_embedded_images = 0 + + ########################################################################### + # + # Private API. + # + ########################################################################### + + def _assemble_xml_file(self): + # Assemble and write the XML file. + + # Write the XML declaration. + self._xml_declaration() + + # Write the richValueRels element. + self._write_rich_value_rels() + + self._xml_end_tag("richValueRels") + + # Close the file. + self._xml_close() + + ########################################################################### + # + # XML methods. + # + ########################################################################### + def _write_rich_value_rels(self): + # Write the element. + xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel" + xmlns_r = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + + attributes = [ + ("xmlns", xmlns), + ("xmlns:r", xmlns_r), + ] + + self._xml_start_tag("richValueRels", attributes) + + # Write the rel elements. + for index in range(self.num_embedded_images): + self._write_rel(index + 1) + + def _write_rel(self, index): + # Write the element. + r_id = f"rId{index}" + attributes = [("r:id", r_id)] + + self._xml_empty_tag("rel", attributes) diff --git a/xlsxwriter/rich_value_structure.py b/xlsxwriter/rich_value_structure.py new file mode 100644 index 000000000..64cb14b05 --- /dev/null +++ b/xlsxwriter/rich_value_structure.py @@ -0,0 +1,98 @@ +############################################################################### +# +# RichValueStructure - A class for writing the Excel XLSX rdrichvaluestructure.xml file. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org +# + +# Package imports. +from . import xmlwriter + + +class RichValueStructure(xmlwriter.XMLwriter): + """ + A class for writing the Excel XLSX rdrichvaluestructure.xml file. + + + """ + + ########################################################################### + # + # Public API. + # + ########################################################################### + + def __init__(self): + """ + Constructor. + + """ + + super(RichValueStructure, self).__init__() + self.has_embedded_descriptions = False + + ########################################################################### + # + # Private API. + # + ########################################################################### + + def _assemble_xml_file(self): + # Assemble and write the XML file. + + # Write the XML declaration. + self._xml_declaration() + + # Write the rvStructures element. + self._write_rv_structures() + + self._xml_end_tag("rvStructures") + + # Close the file. + self._xml_close() + + ########################################################################### + # + # XML methods. + # + ########################################################################### + def _write_rv_structures(self): + # Write the element. + xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" + count = "1" + + attributes = [ + ("xmlns", xmlns), + ("count", count), + ] + + self._xml_start_tag("rvStructures", attributes) + + # Write the s element. + self._write_s() + + def _write_s(self): + # Write the element. + t = "_localImage" + attributes = [("t", t)] + + self._xml_start_tag("s", attributes) + + # Write the k elements. + self._write_k("_rvRel:LocalImageIdentifier", "i") + self._write_k("CalcOrigin", "i") + + if self.has_embedded_descriptions: + self._write_k("Text", "s") + + self._xml_end_tag("s") + + def _write_k(self, name, type): + # Write the element. + attributes = [ + ("n", name), + ("t", type), + ] + + self._xml_empty_tag("k", attributes) diff --git a/xlsxwriter/rich_value_types.py b/xlsxwriter/rich_value_types.py new file mode 100644 index 000000000..c44e4eb13 --- /dev/null +++ b/xlsxwriter/rich_value_types.py @@ -0,0 +1,124 @@ +############################################################################### +# +# RichValueTypes - A class for writing the Excel XLSX rdRichValueTypes.xml file. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2013-2023, John McNamara, jmcnamara@cpan.org +# + +# Package imports. +from . import xmlwriter + + +class RichValueTypes(xmlwriter.XMLwriter): + """ + A class for writing the Excel XLSX rdRichValueTypes.xml file. + + + """ + + ########################################################################### + # + # Public API. + # + ########################################################################### + + def __init__(self): + """ + Constructor. + + """ + + super(RichValueTypes, self).__init__() + + ########################################################################### + # + # Private API. + # + ########################################################################### + + def _assemble_xml_file(self): + # Assemble and write the XML file. + + # Write the XML declaration. + self._xml_declaration() + + # Write the rvTypesInfo element. + self._write_rv_types_info() + + # Write the global element. + self._write_global() + + self._xml_end_tag("rvTypesInfo") + + # Close the file. + self._xml_close() + + ########################################################################### + # + # XML methods. + # + ########################################################################### + + def _write_rv_types_info(self): + # Write the element. + xmlns = "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2" + xmlns_x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" + xmlns_mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" + mc_ignorable = "x" + + attributes = [ + ("xmlns", xmlns), + ("xmlns:mc", xmlns_mc), + ("mc:Ignorable", mc_ignorable), + ("xmlns:x", xmlns_x), + ] + + self._xml_start_tag("rvTypesInfo", attributes) + + def _write_global(self): + # Write the element. + key_flags = [ + ["_Self", ["ExcludeFromFile", "ExcludeFromCalcComparison"]], + ["_DisplayString", ["ExcludeFromCalcComparison"]], + ["_Flags", ["ExcludeFromCalcComparison"]], + ["_Format", ["ExcludeFromCalcComparison"]], + ["_SubLabel", ["ExcludeFromCalcComparison"]], + ["_Attribution", ["ExcludeFromCalcComparison"]], + ["_Icon", ["ExcludeFromCalcComparison"]], + ["_Display", ["ExcludeFromCalcComparison"]], + ["_CanonicalPropertyNames", ["ExcludeFromCalcComparison"]], + ["_ClassificationId", ["ExcludeFromCalcComparison"]], + ] + + self._xml_start_tag("global") + self._xml_start_tag("keyFlags") + + for key_flag in key_flags: + # Write the key element. + self._write_key(key_flag) + + self._xml_end_tag("keyFlags") + self._xml_end_tag("global") + + def _write_key(self, key_flag): + # Write the element. + name = key_flag[0] + attributes = [("name", name)] + + self._xml_start_tag("key", attributes) + + # Write the flag element. + for name in key_flag[1]: + self._write_flag(name) + + self._xml_end_tag("key") + + def _write_flag(self, name): + # Write the element. + attributes = [ + ("name", name), + ("value", "1"), + ] + + self._xml_empty_tag("flag", attributes) diff --git a/xlsxwriter/test/comparison/test_embed_image01.py b/xlsxwriter/test/comparison/test_embed_image01.py new file mode 100644 index 000000000..59ab8420b --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image01.py @@ -0,0 +1,34 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image01.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image02.py b/xlsxwriter/test/comparison/test_embed_image02.py new file mode 100644 index 000000000..78838fc41 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image02.py @@ -0,0 +1,35 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image02.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + worksheet.embed_image(8, 4, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image03.py b/xlsxwriter/test/comparison/test_embed_image03.py new file mode 100644 index 000000000..16898efa0 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image03.py @@ -0,0 +1,35 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image03.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + worksheet.embed_image(8, 4, self.image_dir + "blue.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image04.py b/xlsxwriter/test/comparison/test_embed_image04.py new file mode 100644 index 000000000..39c037bdd --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image04.py @@ -0,0 +1,36 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image04.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet1 = workbook.add_worksheet() + worksheet2 = workbook.add_worksheet() + + worksheet1.embed_image(0, 0, self.image_dir + "red.png") + worksheet2.embed_image(8, 4, self.image_dir + "blue.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image05.py b/xlsxwriter/test/comparison/test_embed_image05.py new file mode 100644 index 000000000..ecc1b2969 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image05.py @@ -0,0 +1,36 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image05.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.write_dynamic_array_formula(0, 0, 2, 0, "=LEN(B1:B3)", None, 0) + + worksheet.embed_image(8, 4, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image06.py b/xlsxwriter/test/comparison/test_embed_image06.py new file mode 100644 index 000000000..c53193d2e --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image06.py @@ -0,0 +1,35 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image06.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + worksheet.insert_image(8, 4, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image07.py b/xlsxwriter/test/comparison/test_embed_image07.py new file mode 100644 index 000000000..500f9df62 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image07.py @@ -0,0 +1,36 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image07.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + worksheet.embed_image(2, 0, self.image_dir + "blue.png") + worksheet.insert_image(8, 4, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image08.py b/xlsxwriter/test/comparison/test_embed_image08.py new file mode 100644 index 000000000..07c9445d6 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image08.py @@ -0,0 +1,36 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image08.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image( + 0, 0, self.image_dir + "red.png", {"description": "Some alt text"} + ) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image09.py b/xlsxwriter/test/comparison/test_embed_image09.py new file mode 100644 index 000000000..ba6b820d9 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image09.py @@ -0,0 +1,39 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image09.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image( + 0, + 0, + self.image_dir + "red.png", + {"description": "Some alt text", "decorative": 1}, + ) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image10.py b/xlsxwriter/test/comparison/test_embed_image10.py new file mode 100644 index 000000000..36aa7ba25 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image10.py @@ -0,0 +1,36 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image10.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.embed_image( + 0, 0, self.image_dir + "red.png", {"url": "http://www.cpan.org/"} + ) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image11.py b/xlsxwriter/test/comparison/test_embed_image11.py new file mode 100644 index 000000000..0d7d91d94 --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image11.py @@ -0,0 +1,37 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image11.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + worksheet.set_column(0, 0, 11) + worksheet.set_row(0, 72) + + worksheet.embed_image(0, 0, self.image_dir + "red.png") + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_embed_image12.py b/xlsxwriter/test/comparison/test_embed_image12.py new file mode 100644 index 000000000..e36d03cfa --- /dev/null +++ b/xlsxwriter/test/comparison/test_embed_image12.py @@ -0,0 +1,38 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("embed_image12.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file with image(s).""" + + workbook = Workbook(self.got_filename) + + worksheet = workbook.add_worksheet() + + format1 = workbook.add_format({"bg_color": "#FFFF00"}) + + worksheet.embed_image( + 0, 0, self.image_dir + "red.png", {"cell_format": format1} + ) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image01.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image01.xlsx new file mode 100644 index 000000000..cd3818c64 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image01.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image02.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image02.xlsx new file mode 100644 index 000000000..a4870d866 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image02.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image03.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image03.xlsx new file mode 100644 index 000000000..cfb4cf6b8 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image03.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image04.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image04.xlsx new file mode 100644 index 000000000..b626b7711 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image04.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image05.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image05.xlsx new file mode 100644 index 000000000..5430cdc4f Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image05.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image06.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image06.xlsx new file mode 100644 index 000000000..3b9f0e358 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image06.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image07.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image07.xlsx new file mode 100644 index 000000000..af4fba330 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image07.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image08.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image08.xlsx new file mode 100644 index 000000000..ca94766f6 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image08.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image09.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image09.xlsx new file mode 100644 index 000000000..8f90d7631 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image09.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image10.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image10.xlsx new file mode 100644 index 000000000..38ae6342c Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image10.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image11.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image11.xlsx new file mode 100644 index 000000000..989ea03b6 Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image11.xlsx differ diff --git a/xlsxwriter/test/comparison/xlsx_files/embed_image12.xlsx b/xlsxwriter/test/comparison/xlsx_files/embed_image12.xlsx new file mode 100644 index 000000000..5961f64cc Binary files /dev/null and b/xlsxwriter/test/comparison/xlsx_files/embed_image12.xlsx differ diff --git a/xlsxwriter/workbook.py b/xlsxwriter/workbook.py index 0a54da011..268391501 100644 --- a/xlsxwriter/workbook.py +++ b/xlsxwriter/workbook.py @@ -7,7 +7,6 @@ # # Standard packages. -import hashlib import operator import os import re @@ -15,7 +14,6 @@ from datetime import datetime, timezone from decimal import Decimal from fractions import Fraction -from struct import unpack from warnings import warn from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, LargeZipFile @@ -39,10 +37,9 @@ from .chart_stock import ChartStock from .exceptions import InvalidWorksheetName from .exceptions import DuplicateWorksheetName -from .exceptions import UndefinedImageSize -from .exceptions import UnsupportedImageFormat from .exceptions import FileCreateError from .exceptions import FileSizeError +from .utility import get_image_properties class Workbook(xmlwriter.XMLwriter): @@ -146,6 +143,10 @@ def __init__(self, filename=None, options=None): self.has_comments = False self.read_only = 0 self.has_metadata = False + self.has_embedded_images = False + self.has_dynamic_functions = False + self.has_embedded_descriptions = False + self.embedded_images = EmbeddedImages() # We can't do 'constant_memory' mode while doing 'in_memory' mode. if self.in_memory: @@ -812,6 +813,7 @@ def _add_sheet(self, name, worksheet_class=None): "remove_timezone": self.remove_timezone, "max_url_length": self.max_url_length, "use_future_functions": self.use_future_functions, + "embedded_images": self.embedded_images, } worksheet._initialize(init_data) @@ -1176,13 +1178,21 @@ def _sort_defined_names(self, names): def _prepare_drawings(self): # Iterate through the worksheets and set up chart and image drawings. chart_ref_id = 0 - image_ref_id = 0 ref_id = 0 drawing_id = 0 image_ids = {} header_image_ids = {} background_ids = {} + # Store the image types for any embedded images. + for image_data in self.embedded_images.images: + image_type = image_data[1] + self.image_types[image_type] = True + if image_data[3]: + self.has_embedded_descriptions = True + + image_ref_id = len(self.embedded_images.images) + for sheet in self.worksheets(): chart_count = len(sheet.charts) image_count = len(sheet.images) @@ -1225,7 +1235,9 @@ def _prepare_drawings(self): _, _, digest, - ) = self._get_image_properties(filename, image_data) + ) = get_image_properties(filename, image_data) + + self.image_types[image_type] = True if digest in background_ids: ref_id = background_ids[digest] @@ -1249,7 +1261,9 @@ def _prepare_drawings(self): x_dpi, y_dpi, digest, - ) = self._get_image_properties(filename, image_data) + ) = get_image_properties(filename, image_data) + + self.image_types[image_type] = True if digest in image_ids: ref_id = image_ids[digest] @@ -1295,7 +1309,9 @@ def _prepare_drawings(self): x_dpi, y_dpi, digest, - ) = self._get_image_properties(filename, image_data) + ) = get_image_properties(filename, image_data) + + self.image_types[image_type] = True if digest in header_image_ids: ref_id = header_image_ids[digest] @@ -1331,7 +1347,9 @@ def _prepare_drawings(self): x_dpi, y_dpi, digest, - ) = self._get_image_properties(filename, image_data) + ) = get_image_properties(filename, image_data) + + self.image_types[image_type] = True if digest in header_image_ids: ref_id = header_image_ids[digest] @@ -1368,246 +1386,6 @@ def _prepare_drawings(self): self.drawing_count = drawing_id - def _get_image_properties(self, filename, image_data): - # Extract dimension information from the image file. - height = 0 - width = 0 - x_dpi = 96 - y_dpi = 96 - - if not image_data: - # Open the image file and read in the data. - fh = open(filename, "rb") - data = fh.read() - else: - # Read the image data from the user supplied byte stream. - data = image_data.getvalue() - - digest = hashlib.sha256(data).hexdigest() - - # Get the image filename without the path. - image_name = os.path.basename(filename) - - # Look for some common image file markers. - marker1 = unpack("3s", data[1:4])[0] - marker2 = unpack(">H", data[:2])[0] - marker3 = unpack("2s", data[:2])[0] - marker4 = unpack("I", data[offset + 0 : offset + 4])[0] - marker = unpack("4s", data[offset + 4 : offset + 8])[0] - - # Read the image dimensions. - if marker == b"IHDR": - width = unpack(">I", data[offset + 8 : offset + 12])[0] - height = unpack(">I", data[offset + 12 : offset + 16])[0] - - # Read the image DPI. - if marker == b"pHYs": - x_density = unpack(">I", data[offset + 8 : offset + 12])[0] - y_density = unpack(">I", data[offset + 12 : offset + 16])[0] - units = unpack("b", data[offset + 16 : offset + 17])[0] - - if units == 1: - x_dpi = x_density * 0.0254 - y_dpi = y_density * 0.0254 - - if marker == b"IEND": - end_marker = True - continue - - offset = offset + length + 12 - - return "png", width, height, x_dpi, y_dpi - - def _process_jpg(self, data): - # Extract width and height information from a JPEG file. - offset = 2 - data_length = len(data) - end_marker = False - width = 0 - height = 0 - x_dpi = 96 - y_dpi = 96 - - # Search through the image data to read the JPEG markers. - while not end_marker and offset < data_length: - marker = unpack(">H", data[offset + 0 : offset + 2])[0] - length = unpack(">H", data[offset + 2 : offset + 4])[0] - - # Read the height and width in the 0xFFCn elements (except C4, C8 - # and CC which aren't SOF markers). - if ( - (marker & 0xFFF0) == 0xFFC0 - and marker != 0xFFC4 - and marker != 0xFFC8 - and marker != 0xFFCC - ): - height = unpack(">H", data[offset + 5 : offset + 7])[0] - width = unpack(">H", data[offset + 7 : offset + 9])[0] - - # Read the DPI in the 0xFFE0 element. - if marker == 0xFFE0: - units = unpack("b", data[offset + 11 : offset + 12])[0] - x_density = unpack(">H", data[offset + 12 : offset + 14])[0] - y_density = unpack(">H", data[offset + 14 : offset + 16])[0] - - if units == 1: - x_dpi = x_density - y_dpi = y_density - - if units == 2: - x_dpi = x_density * 2.54 - y_dpi = y_density * 2.54 - - # Workaround for incorrect dpi. - if x_dpi == 1: - x_dpi = 96 - if y_dpi == 1: - y_dpi = 96 - - if marker == 0xFFDA: - end_marker = True - continue - - offset = offset + length + 2 - - return "jpeg", width, height, x_dpi, y_dpi - - def _process_gif(self, data): - # Extract width and height information from a GIF file. - x_dpi = 96 - y_dpi = 96 - - width = unpack(" 0 diff --git a/xlsxwriter/worksheet.py b/xlsxwriter/worksheet.py index 28af985d9..5b89ea1fb 100644 --- a/xlsxwriter/worksheet.py +++ b/xlsxwriter/worksheet.py @@ -39,6 +39,7 @@ from .utility import get_sparkline_style from .utility import supported_datetime from .utility import datetime_to_excel_datetime +from .utility import get_image_properties from .utility import preserve_whitespace from .utility import quote_sheetname from .exceptions import DuplicateTableName @@ -182,6 +183,7 @@ def column_wrapper(self, *args, **kwargs): "ArrayFormula", "formula, format, value, range, atype" ) cell_rich_string_tuple = namedtuple("RichString", "string, format, raw_string") +cell_error_tuple = namedtuple("Error", "error, format, value") ############################################################################### @@ -416,6 +418,7 @@ def __init__(self): self.has_dynamic_arrays = False self.use_future_functions = False + self.ignore_write_string = False # Utility function for writing different types of strings. def _write_token_as_string(self, token, row, col, *args): @@ -1261,16 +1264,17 @@ def _write_url(self, row, col, url, cell_format=None, string=None, tip=None): ) return -4 - # Write previous row if in in-line string constant_memory mode. - if self.constant_memory and row > self.previous_row: - self._write_single_row(row) - # Add the default URL format. if cell_format is None: cell_format = self.default_url_format - # Write the hyperlink string. - self._write_string(row, col, string, cell_format) + if not self.ignore_write_string: + # Write previous row if in in-line string constant_memory mode. + if self.constant_memory and row > self.previous_row: + self._write_single_row(row) + + # Write the hyperlink string. + self._write_string(row, col, string, cell_format) # Store the hyperlink data in a separate structure. self.hyperlinks[row][col] = { @@ -1566,23 +1570,49 @@ def embed_image(self, row, col, filename, options=None): """ # Check insert (row, col) without storing. - if self._check_dimensions(row, col, True, True): + if self._check_dimensions(row, col): warn("Cannot embed image at (%d, %d)." % (row, col)) return -1 if options is None: options = {} - # url = options.get("url", None) - # tip = options.get("tip", None) + url = options.get("url", None) + tip = options.get("tip", None) + cell_format = options.get("cell_format", None) image_data = options.get("image_data", None) - # description = options.get("description", None) - # decorative = options.get("decorative", False) + description = options.get("description", None) + decorative = options.get("decorative", False) if not image_data and not os.path.exists(filename): warn("Image file '%s' not found." % filename) return -1 + if url: + if cell_format is None: + cell_format = self.default_url_format + + self.ignore_write_string = True + self.write_url(row, col, url, cell_format, None, tip) + self.ignore_write_string = False + + # Get the image properties, for the type and checksum. + ( + image_type, + _, + _, + _, + _, + _, + digest, + ) = get_image_properties(filename, image_data) + + image = [filename, image_type, image_data, description, decorative] + image_index = self.embedded_images.get_image_index(image, digest) + + # Store the cell error and image index in the worksheet data table. + self.table[row][col] = cell_error_tuple("#VALUE!", cell_format, image_index) + return 0 @convert_cell_args @@ -4626,6 +4656,7 @@ def _initialize(self, init_data): self.remove_timezone = init_data["remove_timezone"] self.max_url_length = init_data["max_url_length"] self.use_future_functions = init_data["use_future_functions"] + self.embedded_images = init_data["embedded_images"] if self.excel2003_style: self.original_row_height = 12.75 @@ -6419,7 +6450,6 @@ def _write_col_info(self, col_min, col_max, col_info): def _write_sheet_data(self): # Write the element. - if self.dim_rowmin is None: # If the dimensions aren't defined there is no data to write. self._xml_empty_tag("sheetData") @@ -6885,6 +6915,14 @@ def _write_cell(self, row, col, cell): self._write_cell_value(cell.boolean) self._xml_end_tag("c") + elif type_cell_name == "Error": + # Write a boolean cell. + attributes.append(("t", "e")) + attributes.append(("vm", cell.value)) + self._xml_start_tag("c", attributes) + self._write_cell_value(cell.error) + self._xml_end_tag("c") + def _write_cell_value(self, value): # Write the cell value element. if value is None: