about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py
diff options
context:
space:
mode:
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.py274
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()