Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 40 additions & 38 deletions packages/dsw-templating/dsw/templating/steps/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,37 +666,41 @@ 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),
data['autofilter'].get('first_col', 0),
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:
Expand Down Expand Up @@ -725,34 +729,37 @@ 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(
item.get('first_row', 0),
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(
item.get('first_row', 0),
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):
Expand All @@ -765,15 +772,15 @@ 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(
item.get('first_row', 0),
item.get('first_col', 0),
item.get('last_row', 0),
item.get('last_col', 0),
options=item.get('options', ''),
options=item.get('options', {}),
)

@staticmethod
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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()
Expand Down
16 changes: 11 additions & 5 deletions packages/dsw-templating/support/steps/excel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
Expand Down Expand Up @@ -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
{
Expand All @@ -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)
Expand Down
53 changes: 52 additions & 1 deletion packages/dsw-templating/tests/test_steps_excel.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# cspell:ignore sqref
# cspell:ignore sqref dxf
import io
import pathlib
import zipfile
Expand Down Expand Up @@ -71,3 +71,54 @@ def test_sparkline(tmp_path, key):
{'row': 0, key: 3, 'options': {'range': 'Sheet1!A1:C1'}},
]}})
assert '<xm:sqref>D1</xm:sqref>' 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 '<autoFilter ref="A1:C6">' in xml
assert '<filterColumn colId="0"><filters><filter val="x"/>' in xml
assert '<customFilter operator="greaterThan" val="5"/>' 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 '<cfRule type="notContainsBlanks" dxfId="0"' in sheet_xml(workbook)


def test_ranges_without_options(tmp_path):
workbook = build(tmp_path, {'options': {
'conditional_formats': [{'range': 'A1:A2'}],
'data_validations': [{'range': 'A1'}],
'tables': [{'range': 'A1:B3'}],
'sparklines': [{'cell': 'D1'}],
}})
assert 'xl/tables/table1.xml' in workbook.namelist()
Loading