diff --git a/packages/dsw-templating/dsw/templating/steps/excel.py b/packages/dsw-templating/dsw/templating/steps/excel.py
index c4dfd663..8aa450ea 100644
--- a/packages/dsw-templating/dsw/templating/steps/excel.py
+++ b/packages/dsw-templating/dsw/templating/steps/excel.py
@@ -666,30 +666,22 @@ def _setup_worksheet_panes(worksheet: Worksheet, data: dict):
left_col=data['split_panes'].get('left_col', 0),
)
if 'freeze_panes' in data:
- worksheet.split_panes(
- data['split_panes'].get('x', 0),
- data['split_panes'].get('y', 0),
- top_row=data['split_panes'].get('top_row', 0),
- left_col=data['split_panes'].get('left_col', 0),
- )
+ if isinstance(data['freeze_panes'], str):
+ worksheet.freeze_panes(data['freeze_panes'])
+ else:
+ worksheet.freeze_panes(
+ data['freeze_panes'].get('row', 0),
+ data['freeze_panes'].get('col', 0),
+ top_row=data['freeze_panes'].get('top_row'),
+ left_col=data['freeze_panes'].get('left_col'),
+ )
@staticmethod
def _setup_worksheet_filters(worksheet: Worksheet, data: dict):
- if 'filter_column_lists' in data:
- for f in data['filter_column_list']:
- worksheet.filter_column_list(
- f.get('col', 0),
- filters=f.get('filters', _EMPTY_LIST),
- )
- if 'filter_columns' in data:
- for f in data['filter_column_list']:
- worksheet.filter_column(
- f.get('col', 0),
- criteria=f.get('criteria', _EMPTY_LIST),
- )
+ # XlsxWriter ignores column filters until the autofilter is set
if 'autofilter' in data:
if isinstance(data['autofilter'], str):
- worksheet.set_selection(data['autofilter'])
+ worksheet.autofilter(data['autofilter'])
else:
worksheet.autofilter(
data['autofilter'].get('first_row', 0),
@@ -697,6 +689,18 @@ def _setup_worksheet_filters(worksheet: Worksheet, data: dict):
data['autofilter'].get('last_row', 0),
data['autofilter'].get('last_col', 0),
)
+ if 'filter_column_lists' in data:
+ for f in data['filter_column_lists']:
+ worksheet.filter_column_list(
+ f.get('col', 0),
+ filters=f.get('filters', _EMPTY_LIST),
+ )
+ if 'filter_columns' in data:
+ for f in data['filter_columns']:
+ worksheet.filter_column(
+ f.get('col', 0),
+ criteria=f.get('criteria', ''),
+ )
def _setup_worksheet_merge_ranges(self, worksheet: Worksheet, data: dict):
if 'merge_ranges' in data:
@@ -725,7 +729,7 @@ def _setup_worksheet_data_validations(worksheet: Worksheet, data: dict):
if 'range' in item:
worksheet.data_validation(
item['range'],
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
else:
worksheet.data_validation(
@@ -733,18 +737,21 @@ def _setup_worksheet_data_validations(worksheet: Worksheet, data: dict):
item.get('first_col', 0),
item.get('last_row', 0),
item.get('last_col', 0),
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
def _setup_worksheet_conditional_formats(self, worksheet: Worksheet, data: dict):
if 'conditional_formats' in data:
for item in data['conditional_formats']:
- if 'format' in item:
- item['format'] = self.formats.get(item['format'])
+ options = item.get('options', {})
+ format_name = options.get('format', item.get('format'))
+ if format_name is not None:
+ options['format'] = self.formats.get(format_name)
+ item['options'] = options
if 'range' in item:
worksheet.conditional_format(
item['range'],
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
else:
worksheet.conditional_format(
@@ -752,7 +759,7 @@ def _setup_worksheet_conditional_formats(self, worksheet: Worksheet, data: dict)
item.get('first_col', 0),
item.get('last_row', 0),
item.get('last_col', 0),
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
def _setup_worksheet_tables(self, worksheet: Worksheet, data: dict):
@@ -765,7 +772,7 @@ def _setup_worksheet_tables(self, worksheet: Worksheet, data: dict):
if 'range' in item:
worksheet.add_table(
item['range'],
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
else:
worksheet.add_table(
@@ -773,7 +780,7 @@ def _setup_worksheet_tables(self, worksheet: Worksheet, data: dict):
item.get('first_col', 0),
item.get('last_row', 0),
item.get('last_col', 0),
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
@staticmethod
@@ -783,13 +790,13 @@ def _setup_worksheet_sparklines(worksheet: Worksheet, data: dict):
if 'cell' in item:
worksheet.add_sparkline(
item['cell'],
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
else:
worksheet.add_sparkline(
item.get('row', 0),
item.get('col', item.get('column', 0)),
- options=item.get('options', ''),
+ options=item.get('options', {}),
)
def _setup_worksheet_row_sizing(self, worksheet: Worksheet, data: dict):
@@ -844,16 +851,10 @@ def _setup_worksheet_col_sizing(self, worksheet: Worksheet, data: dict):
def _setup_worksheet_background(worksheet: Worksheet, data: dict):
if 'background' in data:
if 'filename' in data['background']:
- worksheet.set_background(
- source=data['background']['filename'],
- is_byte_stream=False,
- )
+ worksheet.set_background(data['background']['filename'])
elif 'b64bytes' in data['background']:
bytes_io = _b64img2io(data['background']['b64bytes'])
- worksheet.set_background(
- bytes_io,
- is_byte_stream=True,
- )
+ worksheet.set_background(bytes_io)
def _replace_nested_formats(self, data: dict, keys: frozenset[str]):
if not isinstance(data, dict):
@@ -870,7 +871,6 @@ def _replace_nested_formats(self, data: dict, keys: frozenset[str]):
def build(self, data: dict):
self._add_workbook_properties(data)
- self._add_workbook_options(data)
self._add_workbook_definitions(data)
for vba_project in data.get('vba_projects', _EMPTY_LIST):
@@ -891,6 +891,8 @@ def build(self, data: dict):
else:
self._add_worksheet(sheet_data)
+ self._add_workbook_options(data)
+
def cleanup(self):
for stream in self.byte_streams:
stream.close()
diff --git a/packages/dsw-templating/support/steps/excel.md b/packages/dsw-templating/support/steps/excel.md
index 73d94683..79ea3f6a 100644
--- a/packages/dsw-templating/support/steps/excel.md
+++ b/packages/dsw-templating/support/steps/excel.md
@@ -29,7 +29,13 @@ It allows to set both [basic](https://xlsxwriter.readthedocs.io/workbook.html#se
},
"custom": [
{
- "projectUuid": "...",
+ "name": "projectUuid",
+ "value": "..."
+ },
+ {
+ "name": "exportedAt",
+ "value": "2018-01-01",
+ "type": "date"
}
]
}
@@ -143,7 +149,7 @@ A chartsheet simply refers to a `chart` (by its `name`) that should be placed in
The possible `options` are:
* Basic: [`first_sheet`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-first-sheet), [`protect`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-protect), [`zoom`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-zoom), [`tab_color`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-tab-color), [`page_view`](https://xlsxwriter.readthedocs.io/page_setup.html#set-page-view), [`select`](https://xlsxwriter.readthedocs.io/worksheet.html#select), [`hide`](https://xlsxwriter.readthedocs.io/worksheet.html#hide)
-* Print: `orientation` ([`landspace`](https://xlsxwriter.readthedocs.io/page_setup.html#set-landscape) or [`portrait`](https://xlsxwriter.readthedocs.io/page_setup.html#set-portrait)), [`paper`](https://xlsxwriter.readthedocs.io/page-setup.html#set-paper), [`margins`](https://xlsxwriter.readthedocs.io/page_setup.html#set-margins), [`header`](https://xlsxwriter.readthedocs.io/page_setup.html#set-header), [`footer`](https://xlsxwriter.readthedocs.io/page_setup.html#set-footer), [`center_horizontally`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-horizontally), [`center_vertically`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-vertically)
+* Print: `orientation` ([`landscape`](https://xlsxwriter.readthedocs.io/page_setup.html#set-landscape) or [`portrait`](https://xlsxwriter.readthedocs.io/page_setup.html#set-portrait)), [`paper`](https://xlsxwriter.readthedocs.io/page-setup.html#set-paper), [`margins`](https://xlsxwriter.readthedocs.io/page_setup.html#set-margins), [`header`](https://xlsxwriter.readthedocs.io/page_setup.html#set-header), [`footer`](https://xlsxwriter.readthedocs.io/page_setup.html#set-footer), [`center_horizontally`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-horizontally), [`center_vertically`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-vertically)
```json
{
@@ -163,17 +169,17 @@ Traditional worksheet with many options and data placed into cells. There are mo
The possible `options` are:
* Basic (common): [`first_sheet`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-first-sheet), [`protect`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-protect), [`zoom`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-zoom), [`tab_color`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-set-tab-color), [`page_view`](https://xlsxwriter.readthedocs.io/page_setup.html#set-page-view), [`select`](https://xlsxwriter.readthedocs.io/worksheet.html#select), [`hide`](https://xlsxwriter.readthedocs.io/worksheet.html#hide)
-* Print (common): `orientation` ([`landspace`](https://xlsxwriter.readthedocs.io/page_setup.html#set-landscape) or [`portrait`](https://xlsxwriter.readthedocs.io/page_setup.html#set-portrait)), [`paper`](https://xlsxwriter.readthedocs.io/page-setup.html#set-paper), [`margins`](https://xlsxwriter.readthedocs.io/page_setup.html#set-margins), [`header`](https://xlsxwriter.readthedocs.io/page_setup.html#set-header), [`footer`](https://xlsxwriter.readthedocs.io/page_setup.html#set-footer), [`center_horizontally`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-horizontally), [`center_vertically`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-vertically)
+* Print (common): `orientation` ([`landscape`](https://xlsxwriter.readthedocs.io/page_setup.html#set-landscape) or [`portrait`](https://xlsxwriter.readthedocs.io/page_setup.html#set-portrait)), [`paper`](https://xlsxwriter.readthedocs.io/page-setup.html#set-paper), [`margins`](https://xlsxwriter.readthedocs.io/page_setup.html#set-margins), [`header`](https://xlsxwriter.readthedocs.io/page_setup.html#set-header), [`footer`](https://xlsxwriter.readthedocs.io/page_setup.html#set-footer), [`center_horizontally`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-horizontally), [`center_vertically`](https://xlsxwriter.readthedocs.io/page_setup.html#worksheet-center-vertically)
* Basic: [`comments_author`](https://xlsxwriter.readthedocs.io/worksheet.html#set_comments_author), [`hide_zero`](https://xlsxwriter.readthedocs.io/worksheet.html#hide_zero), [`hide_row_col_headers`](https://xlsxwriter.readthedocs.io/page_setup.html#hide_row_col_headers), [`right_to_left`](https://xlsxwriter.readthedocs.io/worksheet.html#right_to_left), [`hide_gridlines`](), [`ignore_errors`](https://xlsxwriter.readthedocs.io/worksheet.html#ignore_errors), [`vba_name`](https://xlsxwriter.readthedocs.io/workbook.html#set_vba_name)
* Print (advanced): [`print_row_col_headers`](https://xlsxwriter.readthedocs.io/page_setup.html#print_row_col_headers), [`print_area`](), [`print_across`](https://xlsxwriter.readthedocs.io/page_setup.html#print_across), [`fit_to_pages`](https://xlsxwriter.readthedocs.io/page_setup.html#fit_to_pages), [`start_page`](https://xlsxwriter.readthedocs.io/page_setup.html#set_start_page), [`print_scale`](https://xlsxwriter.readthedocs.io/page_setup.html#set_print_scale), [`print_black_and_white`](https://xlsxwriter.readthedocs.io/page_setup.html#print_black_and_white)
* Special ranges: [`unprotect_ranges`](https://xlsxwriter.readthedocs.io/worksheet.html#unprotect_range), [`top_left_cell`](https://xlsxwriter.readthedocs.io/worksheet.html#set_top_left_cell), [`selection`](https://xlsxwriter.readthedocs.io/worksheet.html#set_selection)
* Repeats: [`repeat_rows`](https://xlsxwriter.readthedocs.io/page_setup.html#repeat_rows), [`repeat_columns`](https://xlsxwriter.readthedocs.io/page_setup.html#repeat_columns), [`default_row`](https://xlsxwriter.readthedocs.io/worksheet.html#set_default_row)
* Paging: [`h_pagebreaks`](https://xlsxwriter.readthedocs.io/page_setup.html#set_h_pagebreaks), [`v_pagebreaks`](https://xlsxwriter.readthedocs.io/page_setup.html#set_v_pagebreaks), [`outline_settings`](https://xlsxwriter.readthedocs.io/worksheet.html#outline_settings)
-* Panes: [`split_panes`](https://xlsxwriter.readthedocs.io/worksheet.html#split_panes), [`freeze_panes`](https://xlsxwriter.readthedocs.io/worksheet.html#freeze_panes)
+* Panes: [`split_panes`](https://xlsxwriter.readthedocs.io/worksheet.html#split_panes) with `x`, `y`, `top_row`, `left_col` attributes, [`freeze_panes`](https://xlsxwriter.readthedocs.io/worksheet.html#freeze_panes) with a cell directly or `row`, `col`, `top_row`, `left_col` attributes
* Filters: [`filter_column_lists`](https://xlsxwriter.readthedocs.io/worksheet.html#filter_column_list) each with `col` and `filters`, [`filter_columns`](https://xlsxwriter.readthedocs.io/worksheet.html#filter_column) each with `col` and `criteria`, [`autofilter`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-autofilter) with range directly or `first_row`, `first_col`, `last_row`, `last_col` attributes (see [docs](https://xlsxwriter.readthedocs.io/working_with_autofilters.html))
* Merge ranges: [`merge_ranges`](https://xlsxwriter.readthedocs.io/worksheet.html#merge_range) list can be used to merge cells (`range` or combination of `first_row`, `first_col`, `last_row`, `last_col` attributes) together and apply a format (via `format` reference attribute), also contents can be set via `data` attribute
* Data validations: [`data_validations`](https://xlsxwriter.readthedocs.io/worksheet.html#data_validation) list can be used to validate data in cell ranges (see [docs](https://xlsxwriter.readthedocs.io/working_with_data_validation.html))
-* Conditional formats: [`conditional_formats`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-conditional-format) list can be used to define conditional formats on cell ranges (see [docs](https://xlsxwriter.readthedocs.io/example_conditional_format.html#ex-cond-format))
+* Conditional formats: [`conditional_formats`](https://xlsxwriter.readthedocs.io/worksheet.html#worksheet-conditional-format) list can be used to define conditional formats on cell ranges, with `format` in `options` referring to a defined format by its name (see [docs](https://xlsxwriter.readthedocs.io/example_conditional_format.html#ex-cond-format))
* Tables: [`tables`](https://xlsxwriter.readthedocs.io/worksheet.html#add_table) list can be used to define formatted tables (see [docs](https://xlsxwriter.readthedocs.io/working_with_tables.html))
* Sparklines: [`sparklines`](https://xlsxwriter.readthedocs.io/worksheet.html#add_sparkline) list (see [docs](https://xlsxwriter.readthedocs.io/working_with_sparklines.html))
* Row/col sizing: [`columns`](https://xlsxwriter.readthedocs.io/worksheet.html?highlight=column_pixels#set_column), [`column_pixels`](https://xlsxwriter.readthedocs.io/worksheet.html#set_column_pixels), [`rows`](https://xlsxwriter.readthedocs.io/worksheet.html?highlight=column_pixels#set_row), and [`row_pixels`](https://xlsxwriter.readthedocs.io/worksheet.html#set_row_pixels) lists can be used to adjust cell sizing (unfortunately automatic sizing is not possible)
diff --git a/packages/dsw-templating/tests/test_steps_excel.py b/packages/dsw-templating/tests/test_steps_excel.py
index bcf08b20..e74f7e70 100644
--- a/packages/dsw-templating/tests/test_steps_excel.py
+++ b/packages/dsw-templating/tests/test_steps_excel.py
@@ -1,4 +1,4 @@
-# cspell:ignore sqref
+# cspell:ignore sqref dxf
import io
import pathlib
import zipfile
@@ -71,3 +71,54 @@ def test_sparkline(tmp_path, key):
{'row': 0, key: 3, 'options': {'range': 'Sheet1!A1:C1'}},
]}})
assert 'D1' in sheet_xml(workbook)
+
+
+def test_active_sheet(tmp_path):
+ content = WorkbookBuilder.build_to_bytes(
+ tmp_file=tmp_path / 'result.xlsx',
+ input_data={'sheets': [{'name': 'A'}, {'name': 'B'}], 'options': {'active_sheet': 1}},
+ )
+ workbook = zipfile.ZipFile(io.BytesIO(content))
+ assert 'activeTab="1"' in workbook.read('xl/workbook.xml').decode('utf-8')
+
+
+@pytest.mark.parametrize('freeze', ['B3', {'row': 2, 'col': 1}])
+def test_freeze_panes(tmp_path, freeze):
+ workbook = build(tmp_path, {'options': {'freeze_panes': freeze}})
+ assert 'topLeftCell="B3" activePane="bottomRight" state="frozen"' in sheet_xml(workbook)
+
+
+@pytest.mark.parametrize('autofilter', ['A1:C6', {'last_row': 5, 'last_col': 2}])
+def test_filters(tmp_path, autofilter):
+ workbook = build(tmp_path, {'options': {
+ 'autofilter': autofilter,
+ 'filter_column_lists': [{'col': 0, 'filters': ['x']}],
+ 'filter_columns': [{'col': 1, 'criteria': 'x > 5'}],
+ }})
+ xml = sheet_xml(workbook)
+ assert '' in xml
+ assert '' in xml
+ assert '' in xml
+
+
+@pytest.mark.parametrize('item', [
+ {'range': 'A1:A2', 'options': {'type': 'no_blanks', 'format': 'bold'}},
+ {'range': 'A1:A2', 'format': 'bold', 'options': {'type': 'no_blanks'}},
+])
+def test_conditional_format(tmp_path, item):
+ workbook = build(
+ tmp_path,
+ {'options': {'conditional_formats': [item]}},
+ formats={'bold': {'bold': True}},
+ )
+ assert '