diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py new file mode 100644 index 00000000..dfaf875d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py @@ -0,0 +1,274 @@ +# Copyright (c) 2010-2024 openpyxl + +from warnings import warn + +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors import ( + Typed, +) +from openpyxl.descriptors.sequence import NestedSequence +from openpyxl.descriptors.excel import ExtensionList +from openpyxl.utils.indexed_list import IndexedList +from openpyxl.xml.constants import ARC_STYLE, SHEET_MAIN_NS +from openpyxl.xml.functions import fromstring + +from .builtins import styles +from .colors import ColorList +from .differential import DifferentialStyle +from .table import TableStyleList +from .borders import Border +from .fills import Fill +from .fonts import Font +from .numbers import ( + NumberFormatList, + BUILTIN_FORMATS, + BUILTIN_FORMATS_MAX_SIZE, + BUILTIN_FORMATS_REVERSE, + is_date_format, + is_timedelta_format, + builtin_format_code +) +from .named_styles import ( + _NamedCellStyleList, + NamedStyleList, + NamedStyle, +) +from .cell_style import CellStyle, CellStyleList + + +class Stylesheet(Serialisable): + + tagname = "styleSheet" + + numFmts = Typed(expected_type=NumberFormatList) + fonts = NestedSequence(expected_type=Font, count=True) + fills = NestedSequence(expected_type=Fill, count=True) + borders = NestedSequence(expected_type=Border, count=True) + cellStyleXfs = Typed(expected_type=CellStyleList) + cellXfs = Typed(expected_type=CellStyleList) + cellStyles = Typed(expected_type=_NamedCellStyleList) + dxfs = NestedSequence(expected_type=DifferentialStyle, count=True) + tableStyles = Typed(expected_type=TableStyleList, allow_none=True) + colors = Typed(expected_type=ColorList, allow_none=True) + extLst = Typed(expected_type=ExtensionList, allow_none=True) + + __elements__ = ('numFmts', 'fonts', 'fills', 'borders', 'cellStyleXfs', + 'cellXfs', 'cellStyles', 'dxfs', 'tableStyles', 'colors') + + def __init__(self, + numFmts=None, + fonts=(), + fills=(), + borders=(), + cellStyleXfs=None, + cellXfs=None, + cellStyles=None, + dxfs=(), + tableStyles=None, + colors=None, + extLst=None, + ): + if numFmts is None: + numFmts = NumberFormatList() + self.numFmts = numFmts + self.number_formats = IndexedList() + self.fonts = fonts + self.fills = fills + self.borders = borders + if cellStyleXfs is None: + cellStyleXfs = CellStyleList() + self.cellStyleXfs = cellStyleXfs + if cellXfs is None: + cellXfs = CellStyleList() + self.cellXfs = cellXfs + if cellStyles is None: + cellStyles = _NamedCellStyleList() + self.cellStyles = cellStyles + + self.dxfs = dxfs + self.tableStyles = tableStyles + self.colors = colors + + self.cell_styles = self.cellXfs._to_array() + self.alignments = self.cellXfs.alignments + self.protections = self.cellXfs.prots + self._normalise_numbers() + self.named_styles = self._merge_named_styles() + + + @classmethod + def from_tree(cls, node): + # strip all attribs + attrs = dict(node.attrib) + for k in attrs: + del node.attrib[k] + return super().from_tree(node) + + + def _merge_named_styles(self): + """ + Merge named style names "cellStyles" with their associated styles + "cellStyleXfs" + """ + style_refs = self.cellStyles.remove_duplicates() + from_ref = [self._expand_named_style(style_ref) for style_ref in style_refs] + + return NamedStyleList(from_ref) + + + def _expand_named_style(self, style_ref): + """ + Expand a named style reference element to a + named style object by binding the relevant + objects from the stylesheet + """ + xf = self.cellStyleXfs[style_ref.xfId] + named_style = NamedStyle( + name=style_ref.name, + hidden=style_ref.hidden, + builtinId=style_ref.builtinId, + ) + + named_style.font = self.fonts[xf.fontId] + named_style.fill = self.fills[xf.fillId] + named_style.border = self.borders[xf.borderId] + if xf.numFmtId < BUILTIN_FORMATS_MAX_SIZE: + formats = BUILTIN_FORMATS + else: + formats = self.custom_formats + + if xf.numFmtId in formats: + named_style.number_format = formats[xf.numFmtId] + if xf.alignment: + named_style.alignment = xf.alignment + if xf.protection: + named_style.protection = xf.protection + + return named_style + + + def _split_named_styles(self, wb): + """ + Convert NamedStyle into separate CellStyle and Xf objects + + """ + for style in wb._named_styles: + self.cellStyles.cellStyle.append(style.as_name()) + self.cellStyleXfs.xf.append(style.as_xf()) + + + @property + def custom_formats(self): + return dict([(n.numFmtId, n.formatCode) for n in self.numFmts.numFmt]) + + + def _normalise_numbers(self): + """ + Rebase custom numFmtIds with a floor of 164 when reading stylesheet + And index datetime formats + """ + date_formats = set() + timedelta_formats = set() + custom = self.custom_formats + formats = self.number_formats + for idx, style in enumerate(self.cell_styles): + if style.numFmtId in custom: + fmt = custom[style.numFmtId] + if fmt in BUILTIN_FORMATS_REVERSE: # remove builtins + style.numFmtId = BUILTIN_FORMATS_REVERSE[fmt] + else: + style.numFmtId = formats.add(fmt) + BUILTIN_FORMATS_MAX_SIZE + else: + fmt = builtin_format_code(style.numFmtId) + if is_date_format(fmt): + # Create an index of which styles refer to datetimes + date_formats.add(idx) + if is_timedelta_format(fmt): + # Create an index of which styles refer to timedeltas + timedelta_formats.add(idx) + self.date_formats = date_formats + self.timedelta_formats = timedelta_formats + + + def to_tree(self, tagname=None, idx=None, namespace=None): + tree = super().to_tree(tagname, idx, namespace) + tree.set("xmlns", SHEET_MAIN_NS) + return tree + + +def apply_stylesheet(archive, wb): + """ + Add styles to workbook if present + """ + try: + src = archive.read(ARC_STYLE) + except KeyError: + return wb + + node = fromstring(src) + stylesheet = Stylesheet.from_tree(node) + + if stylesheet.cell_styles: + + wb._borders = IndexedList(stylesheet.borders) + wb._fonts = IndexedList(stylesheet.fonts) + wb._fills = IndexedList(stylesheet.fills) + wb._differential_styles.styles = stylesheet.dxfs + wb._number_formats = stylesheet.number_formats + wb._protections = stylesheet.protections + wb._alignments = stylesheet.alignments + wb._table_styles = stylesheet.tableStyles + + # need to overwrite openpyxl defaults in case workbook has different ones + wb._cell_styles = stylesheet.cell_styles + wb._named_styles = stylesheet.named_styles + wb._date_formats = stylesheet.date_formats + wb._timedelta_formats = stylesheet.timedelta_formats + + for ns in wb._named_styles: + ns.bind(wb) + + else: + warn("Workbook contains no stylesheet, using openpyxl's defaults") + + if not wb._named_styles: + normal = styles['Normal'] + wb.add_named_style(normal) + warn("Workbook contains no default style, apply openpyxl's default") + + if stylesheet.colors is not None: + wb._colors = stylesheet.colors.index + + +def write_stylesheet(wb): + stylesheet = Stylesheet() + stylesheet.fonts = wb._fonts + stylesheet.fills = wb._fills + stylesheet.borders = wb._borders + stylesheet.dxfs = wb._differential_styles.styles + stylesheet.colors = ColorList(indexedColors=wb._colors) + + from .numbers import NumberFormat + fmts = [] + for idx, code in enumerate(wb._number_formats, BUILTIN_FORMATS_MAX_SIZE): + fmt = NumberFormat(idx, code) + fmts.append(fmt) + + stylesheet.numFmts.numFmt = fmts + + xfs = [] + for style in wb._cell_styles: + xf = CellStyle.from_array(style) + + if style.alignmentId: + xf.alignment = wb._alignments[style.alignmentId] + + if style.protectionId: + xf.protection = wb._protections[style.protectionId] + xfs.append(xf) + stylesheet.cellXfs = CellStyleList(xf=xfs) + + stylesheet._split_named_styles(wb) + stylesheet.tableStyles = wb._table_styles + + return stylesheet.to_tree() |