Skip to content

Commit

Permalink
checkbox: add table format support
Browse files Browse the repository at this point in the history
Feature request #1092
  • Loading branch information
jmcnamara committed Jan 28, 2025
1 parent 061a881 commit 5167a73
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 28 deletions.
39 changes: 38 additions & 1 deletion xlsxwriter/feature_property_bag.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ class FeaturePropertyBag(xmlwriter.XMLwriter):
"""

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

def __init__(self):
"""
Constructor.
"""

super().__init__()

self.feature_property_bags = set()

###########################################################################
#
# Private API.
Expand Down Expand Up @@ -46,6 +62,12 @@ def _assemble_xml_file(self):
# Write the XFComplements bag element.
self._write_xf_compliments_bag()

print("FeaturePropertyBags", self.feature_property_bags)

# Write the DXFComplements bag element.
if "DXFComplements" in self.feature_property_bags:
self._write_dxf_compliments_bag()

self._xml_end_tag("FeaturePropertyBags")

# Close the file.
Expand Down Expand Up @@ -97,7 +119,7 @@ def _write_xf_compliment_bag(self):
self._xml_end_tag("bag")

def _write_xf_compliments_bag(self):
# Write the _write_xf_compliment_bag<bag> element.
# Write the XFComplements <bag> element.
attributes = [
("type", "XFComplements"),
("extRef", "XFComplementsMapperExtRef"),
Expand All @@ -111,6 +133,21 @@ def _write_xf_compliments_bag(self):
self._xml_end_tag("a")
self._xml_end_tag("bag")

def _write_dxf_compliments_bag(self):
# Write the DXFComplements <bag> element.
attributes = [
("type", "DXFComplements"),
("extRef", "DXFComplementsMapperExtRef"),
]

self._xml_start_tag("bag", attributes)
self._xml_start_tag("a", [("k", "MappedFeaturePropertyBags")])

self._write_bag_id("", 2)

self._xml_end_tag("a")
self._xml_end_tag("bag")

def _write_bag_id(self, key, bag_id):
# Write the <bagId> element.
attributes = []
Expand Down
8 changes: 5 additions & 3 deletions xlsxwriter/packager.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,12 @@ def _write_metadata_file(self):

def _write_feature_bag_property(self):
# Write the featurePropertyBag.xml file.
if not self.workbook.has_checkboxes:
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")
Expand Down Expand Up @@ -487,7 +489,7 @@ def _write_content_types_file(self):
content._add_metadata()

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

# Add the RichValue file if present.
Expand Down Expand Up @@ -614,7 +616,7 @@ def _write_workbook_rels_file(self):
rels._add_rich_value_relationship()

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

rels._set_xml_writer(self._filename("xl/_rels/workbook.xml.rels"))
Expand Down
48 changes: 36 additions & 12 deletions xlsxwriter/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ def _write_xf(self, xf_format):
self._xml_empty_tag("protection", protection)

if has_checkbox:
self._write_checkbox_ext()
self._write_xf_format_extensions()

self._xml_end_tag("xf")
else:
Expand Down Expand Up @@ -684,20 +684,25 @@ def _write_dxfs(self):
self._xml_start_tag("dxfs", attributes)

# Write the font elements for xf_format objects that have them.
for xf_format in self.dxf_formats:
for dxf_format in self.dxf_formats:
self._xml_start_tag("dxf")
if xf_format.has_dxf_font:
self._write_font(xf_format, True)
if dxf_format.has_dxf_font:
self._write_font(dxf_format, True)

if xf_format.num_format_index:
if dxf_format.num_format_index:
self._write_num_fmt(
xf_format.num_format_index, xf_format.num_format
dxf_format.num_format_index, dxf_format.num_format
)

if xf_format.has_dxf_fill:
self._write_fill(xf_format, True)
if xf_format.has_dxf_border:
self._write_border(xf_format, True)
if dxf_format.has_dxf_fill:
self._write_fill(dxf_format, True)

if dxf_format.has_dxf_border:
self._write_border(dxf_format, True)

if dxf_format.checkbox:
self._write_dxf_format_extensions()

self._xml_end_tag("dxf")

self._xml_end_tag("dxfs")
Expand Down Expand Up @@ -759,8 +764,8 @@ def _write_extend(self):

self._xml_empty_tag("extend", attributes)

def _write_checkbox_ext(self):
# Write the Checkbox <extLst> element.
def _write_xf_format_extensions(self):
# Write the xfComplement <extLst> elements.
schema = "http://schemas.microsoft.com/office/spreadsheetml"
attributes = [
("uri", "{C7286773-470A-42A8-94C5-96B5CB345126}"),
Expand All @@ -777,3 +782,22 @@ def _write_checkbox_ext(self):

self._xml_end_tag("ext")
self._xml_end_tag("extLst")

def _write_dxf_format_extensions(self):
# Write the DXFComplement <extLst> elements.
schema = "http://schemas.microsoft.com/office/spreadsheetml"
attributes = [
("uri", "{0417FA29-78FA-4A13-93AC-8FF0FAFDF519}"),
(
"xmlns:xfpb",
schema + "/2022/featurepropertybag",
),
]

self._xml_start_tag("extLst")
self._xml_start_tag("ext", attributes)

self._xml_empty_tag("xfpb:DXFComplement", [("i", "0")])

self._xml_end_tag("ext")
self._xml_end_tag("extLst")
67 changes: 67 additions & 0 deletions xlsxwriter/test/comparison/test_checkbox06.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
###############################################################################
#
# Tests for XlsxWriter.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c), 2013-2025, John McNamara, [email protected]
#

from ...workbook import Workbook
from ..excel_comparison_test import ExcelComparisonTest


class TestCompareXLSXFiles(ExcelComparisonTest):
"""
Test file created by XlsxWriter against a file created by Excel.
"""

def setUp(self):
self.set_filename("checkbox06.xlsx")

def test_create_file_with_insert_checkbox(self):
"""Test the creation of a simple XlsxWriter file."""

workbook = Workbook(self.got_filename)
worksheet = workbook.add_worksheet()

worksheet.write(0, 0, "Col1")
worksheet.write(1, 0, 1)
worksheet.write(2, 0, 2)
worksheet.write(3, 0, 3)
worksheet.write(4, 0, 4)

worksheet.write(0, 1, "Col2")
worksheet.insert_checkbox(1, 1, True)
worksheet.insert_checkbox(2, 1, False)
worksheet.insert_checkbox(3, 1, False)
worksheet.insert_checkbox(4, 1, True)

workbook.close()

self.assertExcelEqual()

def test_create_file_with_boolean_and_format(self):
"""Test the creation of a simple XlsxWriter file."""

workbook = Workbook(self.got_filename)
worksheet = workbook.add_worksheet()

cell_format = workbook.add_format({"checkbox": True})

worksheet.write(0, 0, "Col1")
worksheet.write(1, 0, 1)
worksheet.write(2, 0, 2)
worksheet.write(3, 0, 3)
worksheet.write(4, 0, 4)

worksheet.write(0, 1, "Col2")
worksheet.write(1, 1, True, cell_format)
worksheet.write(2, 1, False, cell_format)
worksheet.write(3, 1, False, cell_format)
worksheet.write(4, 1, True, cell_format)

workbook.close()

self.assertExcelEqual()
50 changes: 50 additions & 0 deletions xlsxwriter/test/comparison/test_checkbox07.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
###############################################################################
#
# Tests for XlsxWriter.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c), 2013-2025, John McNamara, [email protected]
#

from ...workbook import Workbook
from ..excel_comparison_test import ExcelComparisonTest


class TestCompareXLSXFiles(ExcelComparisonTest):
"""
Test file created by XlsxWriter against a file created by Excel.
"""

def setUp(self):
self.set_filename("checkbox07.xlsx")

def test_create_file_with_checkboxes_in_table(self):
"""Test the creation of a simple XlsxWriter file."""

workbook = Workbook(self.got_filename)
worksheet = workbook.add_worksheet()
cell_format = workbook.add_format({"checkbox": True})

data = [
[1, True],
[2, False],
[3, False],
[4, True],
]

worksheet.add_table(
"A1:B5",
{
"data": data,
"columns": [
{"header": "Col1"},
{"header": "Col2", "format": cell_format},
],
},
)

workbook.close()

self.assertExcelEqual()
Binary file not shown.
Binary file not shown.
24 changes: 12 additions & 12 deletions xlsxwriter/workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def __init__(self, filename=None, options=None):
self.has_dynamic_functions = False
self.has_embedded_descriptions = False
self.embedded_images = EmbeddedImages()
self.has_checkboxes = False
self.feature_property_bags = set()

# We can't do 'constant_memory' mode while doing 'in_memory' mode.
if self.in_memory:
Expand Down Expand Up @@ -1122,17 +1122,17 @@ def _prepare_fills(self):

self.fill_count = index

def _has_checkboxes(self):
# Check if any of the formats has a checkbox property.
if self.has_checkboxes:
return True

for cell_format in self.formats:
if cell_format.checkbox:
self.has_checkboxes = True
break

return self.has_checkboxes
def _has_feature_property_bags(self):
# Check for any format properties that require a feature bag. Currently
# this only applies to checkboxes.
if not self.feature_property_bags:
for xf_format in self.formats:
if xf_format.checkbox:
self.feature_property_bags.add("XFComplements")
if xf_format.dxf_index is not None and xf_format.checkbox:
self.feature_property_bags.add("DXFComplements")

return self.feature_property_bags

def _prepare_defined_names(self):
# Iterate through the worksheets and store any defined names in
Expand Down

0 comments on commit 5167a73

Please sign in to comment.