Skip to content

Commit

Permalink
worksheet: add support for embedded images
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Feb 18, 2024
1 parent 3dbed61 commit 7b0c6e2
Show file tree
Hide file tree
Showing 34 changed files with 1,185 additions and 284 deletions.
27 changes: 27 additions & 0 deletions xlsxwriter/contenttypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
139 changes: 117 additions & 22 deletions xlsxwriter/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

###########################################################################
#
Expand All @@ -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()

Expand All @@ -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")

Expand All @@ -68,28 +80,40 @@ def _assemble_xml_file(self):
def _write_metadata(self):
# Write the <metadata> 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 <metadataTypes> 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 <metadataType> element.
attributes = [
("name", "XLDAPR"),
Expand All @@ -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 <metadataType> 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 <futureMetadata> element.
attributes = [
("name", "XLDAPR"),
Expand All @@ -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 <futureMetadata> 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 <ext> element.
attributes = [("uri", "{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}")]

Expand All @@ -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 <ext> 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 <xlrd:rvb> element.
attributes = [("i", index)]

self._xml_empty_tag("xlrd:rvb", attributes)

def _write_cell_metadata(self):
# Write the <cellMetadata> element.
attributes = [("count", 1)]
Expand All @@ -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 <valueMetadata> 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 <rc> element.
attributes = [
("t", 1),
("v", 0),
("t", type),
("v", index),
]

self._xml_empty_tag("rc", attributes)
82 changes: 81 additions & 1 deletion xlsxwriter/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -149,13 +153,15 @@ 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()
self._write_vba_project_rels_file()
self._write_core_file()
self._write_app_file()
self._write_metadata_file()
self._write_rich_value_files()

return self.filenames

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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]
Expand Down
Loading

0 comments on commit 7b0c6e2

Please sign in to comment.