diff --git a/xltable/__init__.py b/xltable/__init__.py index 823dbed..d87dd2c 100644 --- a/xltable/__init__.py +++ b/xltable/__init__.py @@ -106,6 +106,8 @@ .. autoclass:: CellStyle +.. autoclass:: Value + """ from .expression import Column, Cell, Range, Formula, ConstExpr, Expression from .style import CellStyle, TableStyle diff --git a/xltable/style.py b/xltable/style.py index 1a014a7..37d4f1d 100644 --- a/xltable/style.py +++ b/xltable/style.py @@ -3,6 +3,7 @@ to direct how the tables and cells in the tables will be written to Excel. """ +from weakref import WeakKeyDictionary class TableStyle(object): @@ -24,6 +25,7 @@ class CellStyle(object): :param int decimal_places: Number of decimal places to display the cell value to. :param str date_format: Format to use for date values (use Python date format, e.g. '%Y-%m-%d'). :param bool thousands_sep: True to display numbers with thousand separator. + :param str excel_number_format: Excel number format; overrides other numeric settings (eg thousands_sep). :param bool bold: True to make cells bold. :param int size: Text size, or use one of the string size aliases x-small, small, normal, large, x-large or xx-large. @@ -40,22 +42,25 @@ class CellStyle(object): } def __init__(self, - is_percentage=False, + is_percentage=None, decimal_places=None, date_format=None, - thousands_sep=False, - bold=False, + thousands_sep=None, + excel_number_format=None, + bold=None, size=None, text_color=None, bg_color=None, - text_wrap=False, + text_wrap=None, border=None, align=None, valign=None): + self.__derived_styles = WeakKeyDictionary() self.is_percentage = is_percentage self.decimal_places = decimal_places self.date_format = date_format self.thousands_sep = thousands_sep + self.__excel_number_format = excel_number_format self.bold = bold if isinstance(size, str): size = self._sizes[size] @@ -70,20 +75,25 @@ def __init__(self, @property def excel_number_format(self): number_format = "0" - if self.thousands_sep: - number_format = "#,#00" + if self.__excel_number_format is not None: + number_format = self.__excel_number_format + else: + if self.thousands_sep: + number_format = "#,#00" - if self.decimal_places is not None: - if self.decimal_places > 0: - number_format = number_format + "." + ("0" * self.decimal_places) + if self.decimal_places is not None: + if self.decimal_places > 0: + number_format = number_format + "." + ("0" * self.decimal_places) - if self.is_percentage: - number_format = number_format + "%" + if self.is_percentage: + number_format = number_format + "%" if self.date_format is not None: number_format = self.date_format number_format = number_format.replace("%Y", "yyyy") + number_format = number_format.replace("%y", "yy") number_format = number_format.replace("%m", "mm") + number_format = number_format.replace("%b", "mmm") number_format = number_format.replace("%d", "dd") number_format = number_format.replace("%H", "hh") number_format = number_format.replace("%M", "mm") @@ -92,3 +102,32 @@ def excel_number_format(self): if number_format == "0": return None return number_format + + def __add__(self, other): + """Apply a style on top of this one and return the new style""" + try: + return self.__derived_styles[other] + except KeyError: + pass + + def _if_none(a, b): + """return a if a is not None, else b""" + return a if a is not None else b + + style = self.__class__( + is_percentage=_if_none(other.is_percentage, self.is_percentage), + decimal_places=_if_none(other.decimal_places, self.decimal_places), + date_format=_if_none(other.date_format, self.date_format), + thousands_sep=_if_none(other.thousands_sep, self.thousands_sep), + excel_number_format=_if_none(other.__excel_number_format, self.__excel_number_format), + bold=_if_none(other.bold, self.bold), + size=_if_none(other.size, self.size), + text_color=_if_none(other.text_color, self.text_color), + bg_color=_if_none(other.bg_color, self.bg_color), + text_wrap=_if_none(other.text_wrap, self.text_wrap), + border=_if_none(other.border, self.border), + align=_if_none(other.align, self.align), + valign=_if_none(other.valign, self.valign)) + + self.__derived_styles[other] = style + return style diff --git a/xltable/table.py b/xltable/table.py index 8cc309e..0f8e39d 100644 --- a/xltable/table.py +++ b/xltable/table.py @@ -9,7 +9,12 @@ class Value(object): - """value wrapper that can be used in a table to add a style""" + """ + Value wrapper that can be used in a table to add a style. + + :param value: Value that will be written to the cell. + :param xltable.CellStyle: Style to be applied to the cell. + """ def __init__(self, value, style=None): self.value = value self.style = style @@ -28,7 +33,8 @@ class Table(object): :param xltable.TableStyle style: Table style, or one of the named styles 'default' or 'plain'. :param xltable.CellStyle column_styles: Dictionary of column names to styles or named styles. :param float column_widths: Dictionary of column names to widths. - :param xltable.CellStyle style: Style or named style to use for the cells in the header row. + :param xltable.CellStyle header_style: Style or named style to use for the cells in the header row. + :param xltable.CellStyle index_style: Style or named style to use for the cells in the index column. Named table styles: - default: blue stripes @@ -60,7 +66,8 @@ def __init__(self, style="default", column_styles={}, column_widths={}, - header_style=None): + header_style=None, + index_style=None): self.__name = name self.__df = dataframe self.__position = None @@ -81,6 +88,7 @@ def __init__(self, self.__col_styles[col] = self._named_styles[style] self.header_style = header_style + self.index_style = index_style @property def name(self): diff --git a/xltable/workbook.py b/xltable/workbook.py index 73eb3fc..d5c9cf1 100644 --- a/xltable/workbook.py +++ b/xltable/workbook.py @@ -59,13 +59,16 @@ def itersheets(self): finally: self.active_worksheet = prev_ws - def to_xlsx(self): + def to_xlsx(self, **kwargs): """ Write workbook to a .xlsx file using xlsxwriter. Return a xlsxwriter.workbook.Workbook. + + :param kwargs: Extra arguments passed to the xlsxwriter.Workbook + constructor. """ from xlsxwriter.workbook import Workbook as _Workbook - self.workbook_obj = _Workbook() + self.workbook_obj = _Workbook(**kwargs) self.workbook_obj.set_calc_mode(self.calc_mode) for worksheet in self.itersheets(): diff --git a/xltable/worksheet.py b/xltable/worksheet.py index 4c9a347..00cdcc1 100644 --- a/xltable/worksheet.py +++ b/xltable/worksheet.py @@ -163,7 +163,7 @@ def _get_all_styles(self): _styles = {} def _get_style(bold=False, bg_col=None, border=None): if (bold, bg_col, border) not in _styles: - _styles[(bold, bg_col, border)] = CellStyle(bold, + _styles[(bold, bg_col, border)] = CellStyle(bold=bold, bg_color=bg_col, border=border) return _styles[(bold, bg_col, border)] @@ -175,8 +175,8 @@ def _get_style(bold=False, bg_col=None, border=None): ws_styles[(r, c)] = table.header_style or _get_style(bold=True) for c in range(col, col + table.row_labels_width): - for r in range(row, row + table.height): - ws_styles[(r, c)] = table.header_style or _get_style(bold=True) + for r in range(row + table.header_height, row + table.height): + ws_styles[(r, c)] = table.index_style or _get_style(bold=True) bg_cols = None num_bg_cols = 0 @@ -191,8 +191,11 @@ def _get_style(bold=False, bg_col=None, border=None): table.height)): for c in range(col, col + table.width): bg_col = bg_cols[i % num_bg_cols] if bg_cols else None - ws_styles[(row + row_offset, c)] = _get_style( - bold=False, bg_col=bg_col, border=border) + style = _get_style(bold=None, bg_col=bg_col, border=border) + if (row + row_offset, c) in ws_styles: + ws_styles[(row + row_offset, c)] += style + else: + ws_styles[(row + row_offset, c)] = style for col_name, col_style in table.column_styles.items(): try: