about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/styles
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/openpyxl/styles
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/styles')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py11
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py62
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py103
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py1397
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py206
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py172
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py95
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py224
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py113
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py282
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py200
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py17
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py62
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py151
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/stylesheet.py274
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/styles/table.py94
16 files changed, 3463 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py
new file mode 100644
index 00000000..ea20d0d1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from .alignment import Alignment
+from .borders import Border, Side
+from .colors import Color
+from .fills import PatternFill, GradientFill, Fill
+from .fonts import Font, DEFAULT_FONT
+from .numbers import NumberFormatDescriptor, is_date_format, is_builtin
+from .protection import Protection
+from .named_styles import NamedStyle
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py
new file mode 100644
index 00000000..a727f673
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/alignment.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.compat import safe_string
+
+from openpyxl.descriptors import Bool, MinMax, Min, Alias, NoneSet
+from openpyxl.descriptors.serialisable import Serialisable
+
+
+horizontal_alignments = (
+    "general", "left", "center", "right", "fill", "justify", "centerContinuous",
+    "distributed", )
+vertical_aligments = (
+    "top", "center", "bottom", "justify", "distributed",
+)
+
+class Alignment(Serialisable):
+    """Alignment options for use in styles."""
+
+    tagname = "alignment"
+
+    horizontal = NoneSet(values=horizontal_alignments)
+    vertical = NoneSet(values=vertical_aligments)
+    textRotation = NoneSet(values=range(181))
+    textRotation.values.add(255)
+    text_rotation = Alias('textRotation')
+    wrapText = Bool(allow_none=True)
+    wrap_text = Alias('wrapText')
+    shrinkToFit = Bool(allow_none=True)
+    shrink_to_fit = Alias('shrinkToFit')
+    indent = MinMax(min=0, max=255)
+    relativeIndent = MinMax(min=-255, max=255)
+    justifyLastLine = Bool(allow_none=True)
+    readingOrder = Min(min=0)
+
+    def __init__(self, horizontal=None, vertical=None,
+                 textRotation=0, wrapText=None, shrinkToFit=None, indent=0, relativeIndent=0,
+                 justifyLastLine=None, readingOrder=0, text_rotation=None,
+                 wrap_text=None, shrink_to_fit=None, mergeCell=None):
+        self.horizontal = horizontal
+        self.vertical = vertical
+        self.indent = indent
+        self.relativeIndent = relativeIndent
+        self.justifyLastLine = justifyLastLine
+        self.readingOrder = readingOrder
+        if text_rotation is not None:
+            textRotation = text_rotation
+        if textRotation is not None:
+            self.textRotation = int(textRotation)
+        if wrap_text is not None:
+            wrapText = wrap_text
+        self.wrapText = wrapText
+        if shrink_to_fit is not None:
+            shrinkToFit = shrink_to_fit
+        self.shrinkToFit = shrinkToFit
+        # mergeCell is vestigial
+
+
+    def __iter__(self):
+        for attr in self.__attrs__:
+            value = getattr(self, attr)
+            if value is not None and value != 0:
+                yield attr, safe_string(value)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py
new file mode 100644
index 00000000..f9fce814
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/borders.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.compat import safe_string
+from openpyxl.descriptors import (
+    NoneSet,
+    Typed,
+    Bool,
+    Alias,
+    Sequence,
+    Integer,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+
+from .colors import ColorDescriptor
+
+
+BORDER_NONE = None
+BORDER_DASHDOT = 'dashDot'
+BORDER_DASHDOTDOT = 'dashDotDot'
+BORDER_DASHED = 'dashed'
+BORDER_DOTTED = 'dotted'
+BORDER_DOUBLE = 'double'
+BORDER_HAIR = 'hair'
+BORDER_MEDIUM = 'medium'
+BORDER_MEDIUMDASHDOT = 'mediumDashDot'
+BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot'
+BORDER_MEDIUMDASHED = 'mediumDashed'
+BORDER_SLANTDASHDOT = 'slantDashDot'
+BORDER_THICK = 'thick'
+BORDER_THIN = 'thin'
+
+
+class Side(Serialisable):
+
+    """Border options for use in styles.
+    Caution: if you do not specify a border_style, other attributes will
+    have no effect !"""
+
+
+    color = ColorDescriptor(allow_none=True)
+    style = NoneSet(values=('dashDot','dashDotDot', 'dashed','dotted',
+                            'double','hair', 'medium', 'mediumDashDot', 'mediumDashDotDot',
+                            'mediumDashed', 'slantDashDot', 'thick', 'thin')
+                    )
+    border_style = Alias('style')
+
+    def __init__(self, style=None, color=None, border_style=None):
+        if border_style is not None:
+            style = border_style
+        self.style = style
+        self.color = color
+
+
+class Border(Serialisable):
+    """Border positioning for use in styles."""
+
+    tagname = "border"
+
+    __elements__ = ('start', 'end', 'left', 'right', 'top', 'bottom',
+                    'diagonal', 'vertical', 'horizontal')
+
+    # child elements
+    start = Typed(expected_type=Side, allow_none=True)
+    end = Typed(expected_type=Side, allow_none=True)
+    left = Typed(expected_type=Side, allow_none=True)
+    right = Typed(expected_type=Side, allow_none=True)
+    top = Typed(expected_type=Side, allow_none=True)
+    bottom = Typed(expected_type=Side, allow_none=True)
+    diagonal = Typed(expected_type=Side, allow_none=True)
+    vertical = Typed(expected_type=Side, allow_none=True)
+    horizontal = Typed(expected_type=Side, allow_none=True)
+    # attributes
+    outline = Bool()
+    diagonalUp = Bool()
+    diagonalDown = Bool()
+
+    def __init__(self, left=None, right=None, top=None,
+                 bottom=None, diagonal=None, diagonal_direction=None,
+                 vertical=None, horizontal=None, diagonalUp=False, diagonalDown=False,
+                 outline=True, start=None, end=None):
+        self.left = left
+        self.right = right
+        self.top = top
+        self.bottom = bottom
+        self.diagonal = diagonal
+        self.vertical = vertical
+        self.horizontal = horizontal
+        self.diagonal_direction = diagonal_direction
+        self.diagonalUp = diagonalUp
+        self.diagonalDown = diagonalDown
+        self.outline = outline
+        self.start = start
+        self.end = end
+
+    def __iter__(self):
+        for attr in self.__attrs__:
+            value = getattr(self, attr)
+            if value and attr != "outline":
+                yield attr, safe_string(value)
+            elif attr == "outline" and not value:
+                yield attr, safe_string(value)
+
+DEFAULT_BORDER = Border(left=Side(), right=Side(), top=Side(), bottom=Side(), diagonal=Side())
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py
new file mode 100644
index 00000000..7095eb32
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/builtins.py
@@ -0,0 +1,1397 @@
+# Copyright (c) 2010-2024 openpyxl
+
+# Builtins styles as defined in Part 4 Annex G.2
+
+from .named_styles import NamedStyle
+from openpyxl.xml.functions import fromstring
+
+
+normal = """
+  <namedStyle builtinId="0" name="Normal">
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+comma = """
+  <namedStyle builtinId="3" name="Comma">
+    <alignment/>
+    <number_format>_-* #,##0.00\\ _$_-;\\-* #,##0.00\\ _$_-;_-* "-"??\\ _$_-;_-@_-</number_format>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+comma_0 = """
+  <namedStyle builtinId="6" name="Comma [0]">
+    <alignment/>
+    <number_format>_-* #,##0\\ _$_-;\\-* #,##0\\ _$_-;_-* "-"\\ _$_-;_-@_-</number_format>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+currency = """
+  <namedStyle builtinId="4" name="Currency">
+    <alignment/>
+    <number_format>_-* #,##0.00\\ "$"_-;\\-* #,##0.00\\ "$"_-;_-* "-"??\\ "$"_-;_-@_-</number_format>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+currency_0 = """
+  <namedStyle builtinId="7" name="Currency [0]">
+    <alignment/>
+    <number_format>_-* #,##0\\ "$"_-;\\-* #,##0\\ "$"_-;_-* "-"\\ "$"_-;_-@_-</number_format>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+percent = """
+  <namedStyle builtinId="5" name="Percent">
+    <alignment/>
+    <number_format>0%</number_format>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+hyperlink = """
+  <namedStyle builtinId="8" name="Hyperlink" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="10"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+followed_hyperlink = """
+  <namedStyle builtinId="9" name="Followed Hyperlink" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="11"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+title = """
+  <namedStyle builtinId="15" name="Title">
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Cambria"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="3"/>
+      <sz val="18"/>
+      <scheme val="major"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+headline_1 = """
+  <namedStyle builtinId="16" name="Headline 1" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom style="thick">
+        <color theme="4"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="3"/>
+      <sz val="15"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+headline_2 = """
+  <namedStyle builtinId="17" name="Headline 2" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom style="thick">
+        <color theme="4" tint="0.5"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="3"/>
+      <sz val="13"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+headline_3 = """
+   <namedStyle builtinId="18" name="Headline 3" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom style="medium">
+        <color theme="4" tint="0.4"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="3"/>
+      <sz val="11"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+
+"""
+
+headline_4 = """
+  <namedStyle builtinId="19" name="Headline 4">
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="3"/>
+      <sz val="11"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+good = """
+  <namedStyle builtinId="26" name="Good" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFC6EFCE"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FF006100"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+bad = """
+  <namedStyle builtinId="27" name="Bad" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFFFC7CE"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FF9C0006"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+neutral = """
+  <namedStyle builtinId="28" name="Neutral" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFFFEB9C"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FF9C6500"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+input = """
+  <namedStyle builtinId="20" name="Input" >
+    <alignment/>
+    <border>
+      <left style="thin">
+        <color rgb="FF7F7F7F"/>
+      </left>
+      <right style="thin">
+        <color rgb="FF7F7F7F"/>
+      </right>
+      <top style="thin">
+        <color rgb="FF7F7F7F"/>
+      </top>
+      <bottom style="thin">
+        <color rgb="FF7F7F7F"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFFFCC99"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FF3F3F76"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+output = """
+  <namedStyle builtinId="21" name="Output" >
+    <alignment/>
+    <border>
+      <left style="thin">
+        <color rgb="FF3F3F3F"/>
+      </left>
+      <right style="thin">
+        <color rgb="FF3F3F3F"/>
+      </right>
+      <top style="thin">
+        <color rgb="FF3F3F3F"/>
+      </top>
+      <bottom style="thin">
+        <color rgb="FF3F3F3F"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFF2F2F2"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color rgb="FF3F3F3F"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+calculation = """
+  <namedStyle builtinId="22" name="Calculation" >
+    <alignment/>
+    <border>
+      <left style="thin">
+        <color rgb="FF7F7F7F"/>
+      </left>
+      <right style="thin">
+        <color rgb="FF7F7F7F"/>
+      </right>
+      <top style="thin">
+        <color rgb="FF7F7F7F"/>
+      </top>
+      <bottom style="thin">
+        <color rgb="FF7F7F7F"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFF2F2F2"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color rgb="FFFA7D00"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+linked_cell = """
+  <namedStyle builtinId="24" name="Linked Cell" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom style="double">
+        <color rgb="FFFF8001"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FFFA7D00"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+check_cell = """
+  <namedStyle builtinId="23" name="Check Cell" >
+    <alignment/>
+    <border>
+      <left style="double">
+        <color rgb="FF3F3F3F"/>
+      </left>
+      <right style="double">
+        <color rgb="FF3F3F3F"/>
+      </right>
+      <top style="double">
+        <color rgb="FF3F3F3F"/>
+      </top>
+      <bottom style="double">
+        <color rgb="FF3F3F3F"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFA5A5A5"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+warning = """
+  <namedStyle builtinId="11" name="Warning Text" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color rgb="FFFF0000"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+note = """
+  <namedStyle builtinId="10" name="Note" >
+    <alignment/>
+    <border>
+      <left style="thin">
+        <color rgb="FFB2B2B2"/>
+      </left>
+      <right style="thin">
+        <color rgb="FFB2B2B2"/>
+      </right>
+      <top style="thin">
+        <color rgb="FFB2B2B2"/>
+      </top>
+      <bottom style="thin">
+        <color rgb="FFB2B2B2"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor rgb="FFFFFFCC"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+explanatory = """
+  <namedStyle builtinId="53" name="Explanatory Text" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <i val="1"/>
+      <color rgb="FF7F7F7F"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+total = """
+  <namedStyle builtinId="25" name="Total" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top style="thin">
+        <color theme="4"/>
+      </top>
+      <bottom style="double">
+        <color theme="4"/>
+      </bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <b val="1"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_1 = """
+  <namedStyle builtinId="29" name="Accent1" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="4"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_1_20 = """
+  <namedStyle builtinId="30" name="20 % - Accent1" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="4" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_1_40 = """
+  <namedStyle builtinId="31" name="40 % - Accent1" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="4" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_1_60 = """
+  <namedStyle builtinId="32" name="60 % - Accent1" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="4" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_2 = """<namedStyle builtinId="33" name="Accent2" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="5"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_2_20 = """
+  <namedStyle builtinId="34" name="20 % - Accent2" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="5" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_2_40 = """
+<namedStyle builtinId="35" name="40 % - Accent2" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="5" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_2_60 = """
+<namedStyle builtinId="36" name="60 % - Accent2" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="5" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_3 = """
+<namedStyle builtinId="37" name="Accent3" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="6"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_3_20 = """
+  <namedStyle builtinId="38" name="20 % - Accent3" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="6" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>"""
+
+accent_3_40 = """
+  <namedStyle builtinId="39" name="40 % - Accent3" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="6" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+accent_3_60 = """
+  <namedStyle builtinId="40" name="60 % - Accent3" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="6" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+accent_4 = """
+  <namedStyle builtinId="41" name="Accent4" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="7"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_4_20 = """
+  <namedStyle builtinId="42" name="20 % - Accent4" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="7" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_4_40 = """
+  <namedStyle builtinId="43" name="40 % - Accent4" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="7" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_4_60 = """
+<namedStyle builtinId="44" name="60 % - Accent4" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="7" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_5 = """
+  <namedStyle builtinId="45" name="Accent5" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="8"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_5_20 = """
+  <namedStyle builtinId="46" name="20 % - Accent5" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="8" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_5_40 = """
+  <namedStyle builtinId="47" name="40 % - Accent5" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="8" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_5_60 = """
+  <namedStyle builtinId="48" name="60 % - Accent5" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="8" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_6 = """
+  <namedStyle builtinId="49" name="Accent6" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="9"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_6_20 = """
+  <namedStyle builtinId="50" name="20 % - Accent6" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="9" tint="0.7999816888943144"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_6_40 = """
+  <namedStyle builtinId="51" name="40 % - Accent6" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="9" tint="0.5999938962981048"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="1"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+accent_6_60 = """
+  <namedStyle builtinId="52" name="60 % - Accent6" >
+    <alignment/>
+    <border>
+      <left/>
+      <right/>
+      <top/>
+      <bottom/>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill patternType="solid">
+        <fgColor theme="9" tint="0.3999755851924192"/>
+        <bgColor indexed="65"/>
+      </patternFill>
+    </fill>
+    <font>
+      <name val="Calibri"/>
+      <family val="2"/>
+      <color theme="0"/>
+      <sz val="12"/>
+      <scheme val="minor"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+pandas_highlight = """
+  <namedStyle hidden="0" name="Pandas">
+    <alignment horizontal="center"/>
+    <border>
+      <left style="thin"><color rgb="00000000"/></left>
+      <right style="thin"><color rgb="00000000"/></right>
+      <top style="thin"><color rgb="00000000"/></top>
+      <bottom style="thin"><color rgb="00000000"/></bottom>
+      <diagonal/>
+    </border>
+    <fill>
+      <patternFill/>
+    </fill>
+    <font>
+      <b val="1"/>
+    </font>
+    <protection hidden="0" locked="1"/>
+  </namedStyle>
+"""
+
+styles = dict(
+    [
+        ('Normal', NamedStyle.from_tree(fromstring(normal))),
+        ('Comma', NamedStyle.from_tree(fromstring(comma))),
+        ('Currency', NamedStyle.from_tree(fromstring(currency))),
+        ('Percent', NamedStyle.from_tree(fromstring(percent))),
+        ('Comma [0]', NamedStyle.from_tree(fromstring(comma_0))),
+        ('Currency [0]', NamedStyle.from_tree(fromstring(currency_0))),
+        ('Hyperlink', NamedStyle.from_tree(fromstring(hyperlink))),
+        ('Followed Hyperlink', NamedStyle.from_tree(fromstring(followed_hyperlink))),
+        ('Note', NamedStyle.from_tree(fromstring(note))),
+        ('Warning Text', NamedStyle.from_tree(fromstring(warning))),
+        ('Title', NamedStyle.from_tree(fromstring(title))),
+        ('Headline 1', NamedStyle.from_tree(fromstring(headline_1))),
+        ('Headline 2', NamedStyle.from_tree(fromstring(headline_2))),
+        ('Headline 3', NamedStyle.from_tree(fromstring(headline_3))),
+        ('Headline 4', NamedStyle.from_tree(fromstring(headline_4))),
+        ('Input', NamedStyle.from_tree(fromstring(input))),
+        ('Output', NamedStyle.from_tree(fromstring(output))),
+        ('Calculation',NamedStyle.from_tree(fromstring(calculation))),
+        ('Check Cell', NamedStyle.from_tree(fromstring(check_cell))),
+        ('Linked Cell', NamedStyle.from_tree(fromstring(linked_cell))),
+        ('Total', NamedStyle.from_tree(fromstring(total))),
+        ('Good', NamedStyle.from_tree(fromstring(good))),
+        ('Bad', NamedStyle.from_tree(fromstring(bad))),
+        ('Neutral', NamedStyle.from_tree(fromstring(neutral))),
+        ('Accent1', NamedStyle.from_tree(fromstring(accent_1))),
+        ('20 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_20))),
+        ('40 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_40))),
+        ('60 % - Accent1', NamedStyle.from_tree(fromstring(accent_1_60))),
+        ('Accent2', NamedStyle.from_tree(fromstring(accent_2))),
+        ('20 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_20))),
+        ('40 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_40))),
+        ('60 % - Accent2', NamedStyle.from_tree(fromstring(accent_2_60))),
+        ('Accent3', NamedStyle.from_tree(fromstring(accent_3))),
+        ('20 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_20))),
+        ('40 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_40))),
+        ('60 % - Accent3', NamedStyle.from_tree(fromstring(accent_3_60))),
+        ('Accent4', NamedStyle.from_tree(fromstring(accent_4))),
+        ('20 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_20))),
+        ('40 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_40))),
+        ('60 % - Accent4', NamedStyle.from_tree(fromstring(accent_4_60))),
+        ('Accent5', NamedStyle.from_tree(fromstring(accent_5))),
+        ('20 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_20))),
+        ('40 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_40))),
+        ('60 % - Accent5', NamedStyle.from_tree(fromstring(accent_5_60))),
+        ('Accent6', NamedStyle.from_tree(fromstring(accent_6))),
+        ('20 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_20))),
+        ('40 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_40))),
+        ('60 % - Accent6', NamedStyle.from_tree(fromstring(accent_6_60))),
+        ('Explanatory Text', NamedStyle.from_tree(fromstring(explanatory))),
+        ('Pandas', NamedStyle.from_tree(fromstring(pandas_highlight)))
+    ]
+)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py
new file mode 100644
index 00000000..51091aa5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/cell_style.py
@@ -0,0 +1,206 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from array import array
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Float,
+    Bool,
+    Integer,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.utils.indexed_list import IndexedList
+
+
+from .alignment import Alignment
+from .protection import Protection
+
+
+class ArrayDescriptor:
+
+    def __init__(self, key):
+        self.key = key
+
+    def __get__(self, instance, cls):
+        return instance[self.key]
+
+    def __set__(self, instance, value):
+        instance[self.key] = value
+
+
+class StyleArray(array):
+    """
+    Simplified named tuple with an array
+    """
+
+    __slots__ = ()
+    tagname = 'xf'
+
+    fontId = ArrayDescriptor(0)
+    fillId = ArrayDescriptor(1)
+    borderId = ArrayDescriptor(2)
+    numFmtId = ArrayDescriptor(3)
+    protectionId = ArrayDescriptor(4)
+    alignmentId = ArrayDescriptor(5)
+    pivotButton = ArrayDescriptor(6)
+    quotePrefix = ArrayDescriptor(7)
+    xfId = ArrayDescriptor(8)
+
+
+    def __new__(cls, args=[0]*9):
+        return array.__new__(cls, 'i', args)
+
+
+    def __hash__(self):
+        return hash(tuple(self))
+
+
+    def __copy__(self):
+        return StyleArray((self))
+
+
+    def __deepcopy__(self, memo):
+        return StyleArray((self))
+
+
+class CellStyle(Serialisable):
+
+    tagname = "xf"
+
+    numFmtId = Integer()
+    fontId = Integer()
+    fillId = Integer()
+    borderId = Integer()
+    xfId = Integer(allow_none=True)
+    quotePrefix = Bool(allow_none=True)
+    pivotButton = Bool(allow_none=True)
+    applyNumberFormat = Bool(allow_none=True)
+    applyFont = Bool(allow_none=True)
+    applyFill = Bool(allow_none=True)
+    applyBorder = Bool(allow_none=True)
+    applyAlignment = Bool(allow_none=True)
+    applyProtection = Bool(allow_none=True)
+    alignment = Typed(expected_type=Alignment, allow_none=True)
+    protection = Typed(expected_type=Protection, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('alignment', 'protection')
+    __attrs__ = ("numFmtId", "fontId", "fillId", "borderId",
+                 "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId")
+
+    def __init__(self,
+                 numFmtId=0,
+                 fontId=0,
+                 fillId=0,
+                 borderId=0,
+                 xfId=None,
+                 quotePrefix=None,
+                 pivotButton=None,
+                 applyNumberFormat=None,
+                 applyFont=None,
+                 applyFill=None,
+                 applyBorder=None,
+                 applyAlignment=None,
+                 applyProtection=None,
+                 alignment=None,
+                 protection=None,
+                 extLst=None,
+                ):
+        self.numFmtId = numFmtId
+        self.fontId = fontId
+        self.fillId = fillId
+        self.borderId = borderId
+        self.xfId = xfId
+        self.quotePrefix = quotePrefix
+        self.pivotButton = pivotButton
+        self.applyNumberFormat = applyNumberFormat
+        self.applyFont = applyFont
+        self.applyFill = applyFill
+        self.applyBorder = applyBorder
+        self.alignment = alignment
+        self.protection = protection
+
+
+    def to_array(self):
+        """
+        Convert to StyleArray
+        """
+        style = StyleArray()
+        for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton",
+                  "quotePrefix", "xfId"):
+            v = getattr(self, k, 0)
+            if v is not None:
+                setattr(style, k, v)
+        return style
+
+
+    @classmethod
+    def from_array(cls, style):
+        """
+        Convert from StyleArray
+        """
+        return cls(numFmtId=style.numFmtId, fontId=style.fontId,
+                   fillId=style.fillId, borderId=style.borderId, xfId=style.xfId,
+                   quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,)
+
+
+    @property
+    def applyProtection(self):
+        return self.protection is not None or None
+
+
+    @property
+    def applyAlignment(self):
+        return self.alignment is not None or None
+
+
+class CellStyleList(Serialisable):
+
+    tagname = "cellXfs"
+
+    __attrs__ = ("count",)
+
+    count = Integer(allow_none=True)
+    xf = Sequence(expected_type=CellStyle)
+    alignment = Sequence(expected_type=Alignment)
+    protection = Sequence(expected_type=Protection)
+
+    __elements__ = ('xf',)
+
+    def __init__(self,
+                 count=None,
+                 xf=(),
+                ):
+        self.xf = xf
+
+
+    @property
+    def count(self):
+        return len(self.xf)
+
+
+    def __getitem__(self, idx):
+        try:
+            return self.xf[idx]
+        except IndexError:
+            print((f"{idx} is out of range"))
+        return self.xf[idx]
+
+
+    def _to_array(self):
+        """
+        Extract protection and alignments, convert to style array
+        """
+        self.prots = IndexedList([Protection()])
+        self.alignments = IndexedList([Alignment()])
+        styles = [] # allow duplicates
+        for xf in self.xf:
+            style = xf.to_array()
+            if xf.alignment is not None:
+                style.alignmentId = self.alignments.add(xf.alignment)
+            if xf.protection is not None:
+                style.protectionId = self.prots.add(xf.protection)
+            styles.append(style)
+        return IndexedList(styles)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py
new file mode 100644
index 00000000..6fa7476d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/colors.py
@@ -0,0 +1,172 @@
+# Copyright (c) 2010-2024 openpyxl
+
+import re
+from openpyxl.compat import safe_string
+from openpyxl.descriptors import (
+    String,
+    Bool,
+    MinMax,
+    Integer,
+    Typed,
+)
+from openpyxl.descriptors.sequence import NestedSequence
+from openpyxl.descriptors.serialisable import Serialisable
+
+# Default Color Index as per 18.8.27 of ECMA Part 4
+COLOR_INDEX = (
+    '00000000', '00FFFFFF', '00FF0000', '0000FF00', '000000FF', #0-4
+    '00FFFF00', '00FF00FF', '0000FFFF', '00000000', '00FFFFFF', #5-9
+    '00FF0000', '0000FF00', '000000FF', '00FFFF00', '00FF00FF', #10-14
+    '0000FFFF', '00800000', '00008000', '00000080', '00808000', #15-19
+    '00800080', '00008080', '00C0C0C0', '00808080', '009999FF', #20-24
+    '00993366', '00FFFFCC', '00CCFFFF', '00660066', '00FF8080', #25-29
+    '000066CC', '00CCCCFF', '00000080', '00FF00FF', '00FFFF00', #30-34
+    '0000FFFF', '00800080', '00800000', '00008080', '000000FF', #35-39
+    '0000CCFF', '00CCFFFF', '00CCFFCC', '00FFFF99', '0099CCFF', #40-44
+    '00FF99CC', '00CC99FF', '00FFCC99', '003366FF', '0033CCCC', #45-49
+    '0099CC00', '00FFCC00', '00FF9900', '00FF6600', '00666699', #50-54
+    '00969696', '00003366', '00339966', '00003300', '00333300', #55-59
+    '00993300', '00993366', '00333399', '00333333',  #60-63
+)
+# indices 64 and 65 are reserved for the system foreground and background colours respectively
+
+# Will remove these definitions in a future release
+BLACK = COLOR_INDEX[0]
+WHITE = COLOR_INDEX[1]
+#RED = COLOR_INDEX[2]
+#DARKRED = COLOR_INDEX[8]
+BLUE = COLOR_INDEX[4]
+#DARKBLUE = COLOR_INDEX[12]
+#GREEN = COLOR_INDEX[3]
+#DARKGREEN = COLOR_INDEX[9]
+#YELLOW = COLOR_INDEX[5]
+#DARKYELLOW = COLOR_INDEX[19]
+
+
+aRGB_REGEX = re.compile("^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$")
+
+
+class RGB(Typed):
+    """
+    Descriptor for aRGB values
+    If not supplied alpha is 00
+    """
+
+    expected_type = str
+
+    def __set__(self, instance, value):
+        if not self.allow_none:
+            m = aRGB_REGEX.match(value)
+            if m is None:
+                raise ValueError("Colors must be aRGB hex values")
+            if len(value) == 6:
+                value = "00" + value
+        super().__set__(instance, value)
+
+
+class Color(Serialisable):
+    """Named colors for use in styles."""
+
+    tagname = "color"
+
+    rgb = RGB()
+    indexed = Integer()
+    auto = Bool()
+    theme = Integer()
+    tint = MinMax(min=-1, max=1, expected_type=float)
+    type = String()
+
+
+    def __init__(self, rgb=BLACK, indexed=None, auto=None, theme=None, tint=0.0, index=None, type='rgb'):
+        if index is not None:
+            indexed = index
+        if indexed is not None:
+            self.type = 'indexed'
+            self.indexed = indexed
+        elif theme is not None:
+            self.type = 'theme'
+            self.theme = theme
+        elif auto is not None:
+            self.type = 'auto'
+            self.auto = auto
+        else:
+            self.rgb = rgb
+            self.type = 'rgb'
+        self.tint = tint
+
+    @property
+    def value(self):
+        return getattr(self, self.type)
+
+    @value.setter
+    def value(self, value):
+        setattr(self, self.type, value)
+
+    def __iter__(self):
+        attrs = [(self.type, self.value)]
+        if self.tint != 0:
+            attrs.append(('tint', self.tint))
+        for k, v in attrs:
+            yield k, safe_string(v)
+
+    @property
+    def index(self):
+        # legacy
+        return self.value
+
+
+    def __add__(self, other):
+        """
+        Adding colours is undefined behaviour best do nothing
+        """
+        if not isinstance(other, Color):
+            return super().__add__(other)
+        return self
+
+
+class ColorDescriptor(Typed):
+
+    expected_type = Color
+
+    def __set__(self, instance, value):
+        if isinstance(value, str):
+            value = Color(rgb=value)
+        super().__set__(instance, value)
+
+
+class RgbColor(Serialisable):
+
+    tagname = "rgbColor"
+
+    rgb = RGB()
+
+    def __init__(self,
+                 rgb=None,
+                ):
+        self.rgb = rgb
+
+
+class ColorList(Serialisable):
+
+    tagname = "colors"
+
+    indexedColors = NestedSequence(expected_type=RgbColor)
+    mruColors = NestedSequence(expected_type=Color)
+
+    __elements__ = ('indexedColors', 'mruColors')
+
+    def __init__(self,
+                 indexedColors=(),
+                 mruColors=(),
+                ):
+        self.indexedColors = indexedColors
+        self.mruColors = mruColors
+
+
+    def __bool__(self):
+        return bool(self.indexedColors) or bool(self.mruColors)
+
+
+    @property
+    def index(self):
+        return [val.rgb for val in self.indexedColors]
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py
new file mode 100644
index 00000000..109577e4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/differential.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors import (
+    Typed,
+    Sequence,
+    Alias,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.styles import (
+    Font,
+    Fill,
+    Border,
+    Alignment,
+    Protection,
+    )
+from .numbers import NumberFormat
+
+
+class DifferentialStyle(Serialisable):
+
+    tagname = "dxf"
+
+    __elements__ = ("font", "numFmt", "fill", "alignment", "border", "protection")
+
+    font = Typed(expected_type=Font, allow_none=True)
+    numFmt = Typed(expected_type=NumberFormat, allow_none=True)
+    fill = Typed(expected_type=Fill, allow_none=True)
+    alignment = Typed(expected_type=Alignment, allow_none=True)
+    border = Typed(expected_type=Border, allow_none=True)
+    protection = Typed(expected_type=Protection, allow_none=True)
+
+    def __init__(self,
+                 font=None,
+                 numFmt=None,
+                 fill=None,
+                 alignment=None,
+                 border=None,
+                 protection=None,
+                 extLst=None,
+                ):
+        self.font = font
+        self.numFmt = numFmt
+        self.fill = fill
+        self.alignment = alignment
+        self.border = border
+        self.protection = protection
+        self.extLst = extLst
+
+
+class DifferentialStyleList(Serialisable):
+    """
+    Dedupable container for differential styles.
+    """
+
+    tagname = "dxfs"
+
+    dxf = Sequence(expected_type=DifferentialStyle)
+    styles = Alias("dxf")
+    __attrs__ = ("count",)
+
+
+    def __init__(self, dxf=(), count=None):
+        self.dxf = dxf
+
+
+    def append(self, dxf):
+        """
+        Check to see whether style already exists and append it if does not.
+        """
+        if not isinstance(dxf, DifferentialStyle):
+            raise TypeError('expected ' + str(DifferentialStyle))
+        if dxf in self.styles:
+            return
+        self.styles.append(dxf)
+
+
+    def add(self, dxf):
+        """
+        Add a differential style and return its index
+        """
+        self.append(dxf)
+        return self.styles.index(dxf)
+
+
+    def __bool__(self):
+        return bool(self.styles)
+
+
+    def __getitem__(self, idx):
+        return self.styles[idx]
+
+
+    @property
+    def count(self):
+        return len(self.dxf)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py
new file mode 100644
index 00000000..7071abd6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/fills.py
@@ -0,0 +1,224 @@
+
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors import (
+    Float,
+    Set,
+    Alias,
+    NoneSet,
+    Sequence,
+    Integer,
+    MinMax,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.compat import safe_string
+
+from .colors import ColorDescriptor, Color
+
+from openpyxl.xml.functions import Element, localname
+from openpyxl.xml.constants import SHEET_MAIN_NS
+
+
+FILL_NONE = 'none'
+FILL_SOLID = 'solid'
+FILL_PATTERN_DARKDOWN = 'darkDown'
+FILL_PATTERN_DARKGRAY = 'darkGray'
+FILL_PATTERN_DARKGRID = 'darkGrid'
+FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'
+FILL_PATTERN_DARKTRELLIS = 'darkTrellis'
+FILL_PATTERN_DARKUP = 'darkUp'
+FILL_PATTERN_DARKVERTICAL = 'darkVertical'
+FILL_PATTERN_GRAY0625 = 'gray0625'
+FILL_PATTERN_GRAY125 = 'gray125'
+FILL_PATTERN_LIGHTDOWN = 'lightDown'
+FILL_PATTERN_LIGHTGRAY = 'lightGray'
+FILL_PATTERN_LIGHTGRID = 'lightGrid'
+FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'
+FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'
+FILL_PATTERN_LIGHTUP = 'lightUp'
+FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'
+FILL_PATTERN_MEDIUMGRAY = 'mediumGray'
+
+fills = (FILL_SOLID, FILL_PATTERN_DARKDOWN, FILL_PATTERN_DARKGRAY,
+         FILL_PATTERN_DARKGRID, FILL_PATTERN_DARKHORIZONTAL, FILL_PATTERN_DARKTRELLIS,
+         FILL_PATTERN_DARKUP, FILL_PATTERN_DARKVERTICAL, FILL_PATTERN_GRAY0625,
+         FILL_PATTERN_GRAY125, FILL_PATTERN_LIGHTDOWN, FILL_PATTERN_LIGHTGRAY,
+         FILL_PATTERN_LIGHTGRID, FILL_PATTERN_LIGHTHORIZONTAL,
+         FILL_PATTERN_LIGHTTRELLIS, FILL_PATTERN_LIGHTUP, FILL_PATTERN_LIGHTVERTICAL,
+         FILL_PATTERN_MEDIUMGRAY)
+
+
+class Fill(Serialisable):
+
+    """Base class"""
+
+    tagname = "fill"
+
+    @classmethod
+    def from_tree(cls, el):
+        children = [c for c in el]
+        if not children:
+            return
+        child = children[0]
+        if "patternFill" in child.tag:
+            return PatternFill._from_tree(child)
+        return super(Fill, GradientFill).from_tree(child)
+
+
+class PatternFill(Fill):
+    """Area fill patterns for use in styles.
+    Caution: if you do not specify a fill_type, other attributes will have
+    no effect !"""
+
+    tagname = "patternFill"
+
+    __elements__ = ('fgColor', 'bgColor')
+
+    patternType = NoneSet(values=fills)
+    fill_type = Alias("patternType")
+    fgColor = ColorDescriptor()
+    start_color = Alias("fgColor")
+    bgColor = ColorDescriptor()
+    end_color = Alias("bgColor")
+
+    def __init__(self, patternType=None, fgColor=Color(), bgColor=Color(),
+                 fill_type=None, start_color=None, end_color=None):
+        if fill_type is not None:
+            patternType = fill_type
+        self.patternType = patternType
+        if start_color is not None:
+            fgColor = start_color
+        self.fgColor = fgColor
+        if end_color is not None:
+            bgColor = end_color
+        self.bgColor = bgColor
+
+    @classmethod
+    def _from_tree(cls, el):
+        attrib = dict(el.attrib)
+        for child in el:
+            desc = localname(child)
+            attrib[desc] = Color.from_tree(child)
+        return cls(**attrib)
+
+
+    def to_tree(self, tagname=None, idx=None):
+        parent = Element("fill")
+        el = Element(self.tagname)
+        if self.patternType is not None:
+            el.set('patternType', self.patternType)
+        for c in self.__elements__:
+            value = getattr(self, c)
+            if value != Color():
+                el.append(value.to_tree(c))
+        parent.append(el)
+        return parent
+
+
+DEFAULT_EMPTY_FILL = PatternFill()
+DEFAULT_GRAY_FILL = PatternFill(patternType='gray125')
+
+
+class Stop(Serialisable):
+
+    tagname = "stop"
+
+    position = MinMax(min=0, max=1)
+    color = ColorDescriptor()
+
+    def __init__(self, color, position):
+        self.position = position
+        self.color = color
+
+
+def _assign_position(values):
+    """
+    Automatically assign positions if a list of colours is provided.
+
+    It is not permitted to mix colours and stops
+    """
+    n_values = len(values)
+    n_stops = sum(isinstance(value, Stop) for value in values)
+
+    if n_stops == 0:
+        interval = 1
+        if n_values > 2:
+            interval = 1 / (n_values - 1)
+        values = [Stop(value, i * interval)
+                  for i, value in enumerate(values)]
+
+    elif n_stops < n_values:
+        raise ValueError('Cannot interpret mix of Stops and Colors in GradientFill')
+
+    pos = set()
+    for stop in values:
+        if stop.position in pos:
+            raise ValueError("Duplicate position {0}".format(stop.position))
+        pos.add(stop.position)
+
+    return values
+
+
+class StopList(Sequence):
+
+    expected_type = Stop
+
+    def __set__(self, obj, values):
+        values = _assign_position(values)
+        super().__set__(obj, values)
+
+
+class GradientFill(Fill):
+    """Fill areas with gradient
+
+    Two types of gradient fill are supported:
+
+        - A type='linear' gradient interpolates colours between
+          a set of specified Stops, across the length of an area.
+          The gradient is left-to-right by default, but this
+          orientation can be modified with the degree
+          attribute.  A list of Colors can be provided instead
+          and they will be positioned with equal distance between them.
+
+        - A type='path' gradient applies a linear gradient from each
+          edge of the area. Attributes top, right, bottom, left specify
+          the extent of fill from the respective borders. Thus top="0.2"
+          will fill the top 20% of the cell.
+
+    """
+
+    tagname = "gradientFill"
+
+    type = Set(values=('linear', 'path'))
+    fill_type = Alias("type")
+    degree = Float()
+    left = Float()
+    right = Float()
+    top = Float()
+    bottom = Float()
+    stop = StopList()
+
+
+    def __init__(self, type="linear", degree=0, left=0, right=0, top=0,
+                 bottom=0, stop=()):
+        self.degree = degree
+        self.left = left
+        self.right = right
+        self.top = top
+        self.bottom = bottom
+        self.stop = stop
+        self.type = type
+
+
+    def __iter__(self):
+        for attr in self.__attrs__:
+            value = getattr(self, attr)
+            if value:
+                yield attr, safe_string(value)
+
+
+    def to_tree(self, tagname=None, namespace=None, idx=None):
+        parent = Element("fill")
+        el = super().to_tree()
+        parent.append(el)
+        return parent
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py
new file mode 100644
index 00000000..06e343fc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/fonts.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from openpyxl.descriptors import (
+    Alias,
+    Sequence,
+    Integer
+)
+from openpyxl.descriptors.serialisable import Serialisable
+
+from openpyxl.descriptors.nested import (
+    NestedValue,
+    NestedBool,
+    NestedNoneSet,
+    NestedMinMax,
+    NestedString,
+    NestedInteger,
+    NestedFloat,
+)
+from .colors import ColorDescriptor, Color, BLACK
+
+from openpyxl.compat import safe_string
+from openpyxl.xml.functions import Element, SubElement
+from openpyxl.xml.constants import SHEET_MAIN_NS
+
+
+def _no_value(tagname, value, namespace=None):
+    if value:
+        return Element(tagname, val=safe_string(value))
+
+
+class Font(Serialisable):
+    """Font options used in styles."""
+
+    UNDERLINE_DOUBLE = 'double'
+    UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting'
+    UNDERLINE_SINGLE = 'single'
+    UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting'
+
+    name = NestedString(allow_none=True)
+    charset = NestedInteger(allow_none=True)
+    family = NestedMinMax(min=0, max=14, allow_none=True)
+    sz = NestedFloat(allow_none=True)
+    size = Alias("sz")
+    b = NestedBool(to_tree=_no_value)
+    bold = Alias("b")
+    i = NestedBool(to_tree=_no_value)
+    italic = Alias("i")
+    strike = NestedBool(allow_none=True)
+    strikethrough = Alias("strike")
+    outline = NestedBool(allow_none=True)
+    shadow = NestedBool(allow_none=True)
+    condense = NestedBool(allow_none=True)
+    extend = NestedBool(allow_none=True)
+    u = NestedNoneSet(values=('single', 'double', 'singleAccounting',
+                             'doubleAccounting'))
+    underline = Alias("u")
+    vertAlign = NestedNoneSet(values=('superscript', 'subscript', 'baseline'))
+    color = ColorDescriptor(allow_none=True)
+    scheme = NestedNoneSet(values=("major", "minor"))
+
+    tagname = "font"
+
+    __elements__ = ('name', 'charset', 'family', 'b', 'i', 'strike', 'outline',
+                  'shadow', 'condense', 'color', 'extend', 'sz', 'u', 'vertAlign',
+                  'scheme')
+
+
+    def __init__(self, name=None, sz=None, b=None, i=None, charset=None,
+                 u=None, strike=None, color=None, scheme=None, family=None, size=None,
+                 bold=None, italic=None, strikethrough=None, underline=None,
+                 vertAlign=None, outline=None, shadow=None, condense=None,
+                 extend=None):
+        self.name = name
+        self.family = family
+        if size is not None:
+            sz = size
+        self.sz = sz
+        if bold is not None:
+            b = bold
+        self.b = b
+        if italic is not None:
+            i = italic
+        self.i = i
+        if underline is not None:
+            u = underline
+        self.u = u
+        if strikethrough is not None:
+            strike = strikethrough
+        self.strike = strike
+        self.color = color
+        self.vertAlign = vertAlign
+        self.charset = charset
+        self.outline = outline
+        self.shadow = shadow
+        self.condense = condense
+        self.extend = extend
+        self.scheme = scheme
+
+
+    @classmethod
+    def from_tree(cls, node):
+        """
+        Set default value for underline if child element is present
+        """
+        underline = node.find("{%s}u" % SHEET_MAIN_NS)
+        if underline is not None and underline.get('val') is None:
+            underline.set("val", "single")
+        return super().from_tree(node)
+
+
+DEFAULT_FONT = Font(name="Calibri", sz=11, family=2, b=False, i=False,
+                    color=Color(theme=1), scheme="minor")
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py
new file mode 100644
index 00000000..221d333b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/named_styles.py
@@ -0,0 +1,282 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.compat import safe_string
+
+from openpyxl.descriptors import (
+    Typed,
+    Integer,
+    Bool,
+    String,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.serialisable import Serialisable
+
+from .fills import PatternFill, Fill
+from .fonts import Font
+from .borders import Border
+from .alignment import Alignment
+from .protection import Protection
+from .numbers import (
+    NumberFormatDescriptor,
+    BUILTIN_FORMATS_MAX_SIZE,
+    BUILTIN_FORMATS_REVERSE,
+)
+from .cell_style import (
+    StyleArray,
+    CellStyle,
+)
+
+
+class NamedStyle(Serialisable):
+
+    """
+    Named and editable styles
+    """
+
+    font = Typed(expected_type=Font)
+    fill = Typed(expected_type=Fill)
+    border = Typed(expected_type=Border)
+    alignment = Typed(expected_type=Alignment)
+    number_format = NumberFormatDescriptor()
+    protection = Typed(expected_type=Protection)
+    builtinId = Integer(allow_none=True)
+    hidden = Bool(allow_none=True)
+    name = String()
+    _wb = None
+    _style = StyleArray()
+
+
+    def __init__(self,
+                 name="Normal",
+                 font=None,
+                 fill=None,
+                 border=None,
+                 alignment=None,
+                 number_format=None,
+                 protection=None,
+                 builtinId=None,
+                 hidden=False,
+                 ):
+        self.name = name
+        self.font = font or Font()
+        self.fill = fill or PatternFill()
+        self.border = border or Border()
+        self.alignment = alignment or Alignment()
+        self.number_format = number_format
+        self.protection = protection or Protection()
+        self.builtinId = builtinId
+        self.hidden = hidden
+        self._wb = None
+        self._style = StyleArray()
+
+
+    def __setattr__(self, attr, value):
+        super().__setattr__(attr, value)
+        if getattr(self, '_wb', None) and attr in (
+           'font', 'fill', 'border', 'alignment', 'number_format', 'protection',
+            ):
+            self._recalculate()
+
+
+    def __iter__(self):
+        for key in ('name', 'builtinId', 'hidden', 'xfId'):
+            value = getattr(self, key, None)
+            if value is not None:
+                yield key, safe_string(value)
+
+
+    def bind(self, wb):
+        """
+        Bind a named style to a workbook
+        """
+        self._wb = wb
+        self._recalculate()
+
+
+    def _recalculate(self):
+        self._style.fontId =  self._wb._fonts.add(self.font)
+        self._style.borderId = self._wb._borders.add(self.border)
+        self._style.fillId =  self._wb._fills.add(self.fill)
+        self._style.protectionId = self._wb._protections.add(self.protection)
+        self._style.alignmentId = self._wb._alignments.add(self.alignment)
+        fmt = self.number_format
+        if fmt in BUILTIN_FORMATS_REVERSE:
+            fmt = BUILTIN_FORMATS_REVERSE[fmt]
+        else:
+            fmt = self._wb._number_formats.add(self.number_format) + (
+                  BUILTIN_FORMATS_MAX_SIZE)
+        self._style.numFmtId = fmt
+
+
+    def as_tuple(self):
+        """Return a style array representing the current style"""
+        return self._style
+
+
+    def as_xf(self):
+        """
+        Return equivalent XfStyle
+        """
+        xf = CellStyle.from_array(self._style)
+        xf.xfId = None
+        xf.pivotButton = None
+        xf.quotePrefix = None
+        if self.alignment != Alignment():
+            xf.alignment = self.alignment
+        if self.protection != Protection():
+            xf.protection = self.protection
+        return xf
+
+
+    def as_name(self):
+        """
+        Return relevant named style
+
+        """
+        named = _NamedCellStyle(
+            name=self.name,
+            builtinId=self.builtinId,
+            hidden=self.hidden,
+            xfId=self._style.xfId
+        )
+        return named
+
+
+class NamedStyleList(list):
+    """
+    Named styles are editable and can be applied to multiple objects
+
+    As only the index is stored in referencing objects the order mus
+    be preserved.
+
+    Returns a list of NamedStyles
+    """
+
+    def __init__(self, iterable=()):
+        """
+        Allow a list of named styles to be passed in and index them.
+        """
+
+        for idx, s in enumerate(iterable, len(self)):
+            s._style.xfId = idx
+        super().__init__(iterable)
+
+
+    @property
+    def names(self):
+        return [s.name for s in self]
+
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return super().__getitem__(key)
+
+
+        for idx, name in enumerate(self.names):
+            if name == key:
+                return self[idx]
+
+        raise KeyError("No named style with the name{0} exists".format(key))
+
+    def append(self, style):
+        if not isinstance(style, NamedStyle):
+            raise TypeError("""Only NamedStyle instances can be added""")
+        elif style.name in self.names: # hotspot
+            raise ValueError("""Style {0} exists already""".format(style.name))
+        style._style.xfId = (len(self))
+        super().append(style)
+
+
+class _NamedCellStyle(Serialisable):
+
+    """
+    Pointer-based representation of named styles in XML
+    xfId refers to the corresponding CellStyleXfs
+
+    Not used in client code.
+    """
+
+    tagname = "cellStyle"
+
+    name = String()
+    xfId = Integer()
+    builtinId = Integer(allow_none=True)
+    iLevel = Integer(allow_none=True)
+    hidden = Bool(allow_none=True)
+    customBuiltin = Bool(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ()
+
+
+    def __init__(self,
+                 name=None,
+                 xfId=None,
+                 builtinId=None,
+                 iLevel=None,
+                 hidden=None,
+                 customBuiltin=None,
+                 extLst=None,
+                ):
+        self.name = name
+        self.xfId = xfId
+        self.builtinId = builtinId
+        self.iLevel = iLevel
+        self.hidden = hidden
+        self.customBuiltin = customBuiltin
+
+
+class _NamedCellStyleList(Serialisable):
+    """
+    Container for named cell style objects
+
+    Not used in client code
+    """
+
+    tagname = "cellStyles"
+
+    count = Integer(allow_none=True)
+    cellStyle = Sequence(expected_type=_NamedCellStyle)
+
+    __attrs__ = ("count",)
+
+    def __init__(self,
+                 count=None,
+                 cellStyle=(),
+                ):
+        self.cellStyle = cellStyle
+
+
+    @property
+    def count(self):
+        return len(self.cellStyle)
+
+
+    def remove_duplicates(self):
+        """
+        Some applications contain duplicate definitions either by name or
+        referenced style.
+
+        As the references are 0-based indices, styles are sorted by
+        index.
+
+        Returns a list of style references with duplicates removed
+        """
+
+        def sort_fn(v):
+            return v.xfId
+
+        styles = []
+        names = set()
+        ids = set()
+
+        for ns in sorted(self.cellStyle, key=sort_fn):
+            if ns.xfId in ids or ns.name in names: # skip duplicates
+                continue
+            ids.add(ns.xfId)
+            names.add(ns.name)
+
+            styles.append(ns)
+
+        return styles
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py
new file mode 100644
index 00000000..b548cc7c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/numbers.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2010-2024 openpyxl
+
+import re
+
+from openpyxl.descriptors import (
+    String,
+    Sequence,
+    Integer,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+
+
+BUILTIN_FORMATS = {
+    0: 'General',
+    1: '0',
+    2: '0.00',
+    3: '#,##0',
+    4: '#,##0.00',
+    5: '"$"#,##0_);("$"#,##0)',
+    6: '"$"#,##0_);[Red]("$"#,##0)',
+    7: '"$"#,##0.00_);("$"#,##0.00)',
+    8: '"$"#,##0.00_);[Red]("$"#,##0.00)',
+    9: '0%',
+    10: '0.00%',
+    11: '0.00E+00',
+    12: '# ?/?',
+    13: '# ??/??',
+    14: 'mm-dd-yy',
+    15: 'd-mmm-yy',
+    16: 'd-mmm',
+    17: 'mmm-yy',
+    18: 'h:mm AM/PM',
+    19: 'h:mm:ss AM/PM',
+    20: 'h:mm',
+    21: 'h:mm:ss',
+    22: 'm/d/yy h:mm',
+
+    37: '#,##0_);(#,##0)',
+    38: '#,##0_);[Red](#,##0)',
+    39: '#,##0.00_);(#,##0.00)',
+    40: '#,##0.00_);[Red](#,##0.00)',
+
+    41: r'_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
+    42: r'_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
+    43: r'_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',
+
+    44: r'_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)',
+    45: 'mm:ss',
+    46: '[h]:mm:ss',
+    47: 'mmss.0',
+    48: '##0.0E+0',
+    49: '@', }
+
+BUILTIN_FORMATS_MAX_SIZE = 164
+BUILTIN_FORMATS_REVERSE = dict(
+        [(value, key) for key, value in BUILTIN_FORMATS.items()])
+
+FORMAT_GENERAL = BUILTIN_FORMATS[0]
+FORMAT_TEXT = BUILTIN_FORMATS[49]
+FORMAT_NUMBER = BUILTIN_FORMATS[1]
+FORMAT_NUMBER_00 = BUILTIN_FORMATS[2]
+FORMAT_NUMBER_COMMA_SEPARATED1 = BUILTIN_FORMATS[4]
+FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'
+FORMAT_PERCENTAGE = BUILTIN_FORMATS[9]
+FORMAT_PERCENTAGE_00 = BUILTIN_FORMATS[10]
+FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'
+FORMAT_DATE_YYMMDD = 'yy-mm-dd'
+FORMAT_DATE_DDMMYY = 'dd/mm/yy'
+FORMAT_DATE_DMYSLASH = 'd/m/y'
+FORMAT_DATE_DMYMINUS = 'd-m-y'
+FORMAT_DATE_DMMINUS = 'd-m'
+FORMAT_DATE_MYMINUS = 'm-y'
+FORMAT_DATE_XLSX14 = BUILTIN_FORMATS[14]
+FORMAT_DATE_XLSX15 = BUILTIN_FORMATS[15]
+FORMAT_DATE_XLSX16 = BUILTIN_FORMATS[16]
+FORMAT_DATE_XLSX17 = BUILTIN_FORMATS[17]
+FORMAT_DATE_XLSX22 = BUILTIN_FORMATS[22]
+FORMAT_DATE_DATETIME = 'yyyy-mm-dd h:mm:ss'
+FORMAT_DATE_TIME1 = BUILTIN_FORMATS[18]
+FORMAT_DATE_TIME2 = BUILTIN_FORMATS[19]
+FORMAT_DATE_TIME3 = BUILTIN_FORMATS[20]
+FORMAT_DATE_TIME4 = BUILTIN_FORMATS[21]
+FORMAT_DATE_TIME5 = BUILTIN_FORMATS[45]
+FORMAT_DATE_TIME6 = BUILTIN_FORMATS[21]
+FORMAT_DATE_TIME7 = 'i:s.S'
+FORMAT_DATE_TIME8 = 'h:mm:ss@'
+FORMAT_DATE_TIMEDELTA = '[hh]:mm:ss'
+FORMAT_DATE_YYMMDDSLASH = 'yy/mm/dd@'
+FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-'
+FORMAT_CURRENCY_USD = '$#,##0_-'
+FORMAT_CURRENCY_EUR_SIMPLE = '[$EUR ]#,##0.00_-'
+
+
+COLORS = r"\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]"
+LITERAL_GROUP = r'".*?"' # anything in quotes
+LOCALE_GROUP = r'\[(?!hh?\]|mm?\]|ss?\])[^\]]*\]' # anything in square brackets, except hours or minutes or seconds
+STRIP_RE = re.compile(f"{LITERAL_GROUP}|{LOCALE_GROUP}")
+TIMEDELTA_RE = re.compile(r'\[hh?\](:mm(:ss(\.0*)?)?)?|\[mm?\](:ss(\.0*)?)?|\[ss?\](\.0*)?', re.I)
+
+
+# Spec 18.8.31 numFmts
+# +ve;-ve;zero;text
+
+def is_date_format(fmt):
+    if fmt is None:
+        return False
+    fmt = fmt.split(";")[0] # only look at the first format
+    fmt = STRIP_RE.sub("", fmt) # ignore some formats
+    return re.search(r"(?<![_\\])[dmhysDMHYS]", fmt) is not None
+
+
+def is_timedelta_format(fmt):
+    if fmt is None:
+        return False
+    fmt = fmt.split(";")[0] # only look at the first format
+    return TIMEDELTA_RE.search(fmt) is not None
+
+
+def is_datetime(fmt):
+    """
+    Return date, time or datetime
+    """
+    if not is_date_format(fmt):
+        return
+
+    DATE = TIME = False
+
+    if any((x in fmt for x in 'dy')):
+        DATE = True
+    if any((x in fmt for x in 'hs')):
+        TIME = True
+
+    if DATE and TIME:
+        return "datetime"
+    if DATE:
+        return "date"
+    return "time"
+
+
+def is_builtin(fmt):
+    return fmt in BUILTIN_FORMATS.values()
+
+
+def builtin_format_code(index):
+    """Return one of the standard format codes by index."""
+    try:
+        fmt = BUILTIN_FORMATS[index]
+    except KeyError:
+        fmt = None
+    return fmt
+
+
+def builtin_format_id(fmt):
+    """Return the id of a standard style."""
+    return BUILTIN_FORMATS_REVERSE.get(fmt)
+
+
+class NumberFormatDescriptor(String):
+
+    def __set__(self, instance, value):
+        if value is None:
+            value = FORMAT_GENERAL
+        super().__set__(instance, value)
+
+
+class NumberFormat(Serialisable):
+
+    numFmtId = Integer()
+    formatCode = String()
+
+    def __init__(self,
+                 numFmtId=None,
+                 formatCode=None,
+                ):
+        self.numFmtId = numFmtId
+        self.formatCode = formatCode
+
+
+class NumberFormatList(Serialisable):
+
+    count = Integer(allow_none=True)
+    numFmt = Sequence(expected_type=NumberFormat)
+
+    __elements__ = ('numFmt',)
+    __attrs__ = ("count",)
+
+    def __init__(self,
+                 count=None,
+                 numFmt=(),
+                ):
+        self.numFmt = numFmt
+
+
+    @property
+    def count(self):
+        return len(self.numFmt)
+
+
+    def __getitem__(self, idx):
+        return self.numFmt[idx]
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py
new file mode 100644
index 00000000..7c9238ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/protection.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors import Bool
+from openpyxl.descriptors.serialisable import Serialisable
+
+
+class Protection(Serialisable):
+    """Protection options for use in styles."""
+
+    tagname = "protection"
+
+    locked = Bool()
+    hidden = Bool()
+
+    def __init__(self, locked=True, hidden=False):
+        self.locked = locked
+        self.hidden = hidden
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py
new file mode 100644
index 00000000..bee780cd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/proxy.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from copy import copy
+
+from openpyxl.compat import deprecated
+
+
+class StyleProxy:
+    """
+    Proxy formatting objects so that they cannot be altered
+    """
+
+    __slots__ = ('__target')
+
+    def __init__(self, target):
+        self.__target = target
+
+
+    def __repr__(self):
+        return repr(self.__target)
+
+
+    def __getattr__(self, attr):
+        return getattr(self.__target, attr)
+
+
+    def __setattr__(self, attr, value):
+        if attr != "_StyleProxy__target":
+            raise AttributeError("Style objects are immutable and cannot be changed."
+                                 "Reassign the style with a copy")
+        super().__setattr__(attr, value)
+
+
+    def __copy__(self):
+        """
+        Return a copy of the proxied object.
+        """
+        return copy(self.__target)
+
+
+    def __add__(self, other):
+        """
+        Add proxied object to another instance and return the combined object
+        """
+        return self.__target + other
+
+
+    @deprecated("Use copy(obj) or cell.obj = cell.obj + other")
+    def copy(self, **kw):
+        """Return a copy of the proxied object. Keyword args will be passed through"""
+        cp = copy(self.__target)
+        for k, v in kw.items():
+            setattr(cp, k, v)
+        return cp
+
+
+    def __eq__(self, other):
+        return self.__target == other
+
+
+    def __ne__(self, other):
+        return not self == other
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py
new file mode 100644
index 00000000..2703096d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/styleable.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from copy import copy
+
+from .numbers import (
+    BUILTIN_FORMATS,
+    BUILTIN_FORMATS_MAX_SIZE,
+    BUILTIN_FORMATS_REVERSE,
+)
+from .proxy import StyleProxy
+from .cell_style import StyleArray
+from .named_styles import NamedStyle
+from .builtins import styles
+
+
+class StyleDescriptor:
+
+    def __init__(self, collection, key):
+        self.collection = collection
+        self.key = key
+
+    def __set__(self, instance, value):
+        coll = getattr(instance.parent.parent, self.collection)
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        setattr(instance._style, self.key, coll.add(value))
+
+
+    def __get__(self, instance, cls):
+        coll = getattr(instance.parent.parent, self.collection)
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        idx =  getattr(instance._style, self.key)
+        return StyleProxy(coll[idx])
+
+
+class NumberFormatDescriptor:
+
+    key = "numFmtId"
+    collection = '_number_formats'
+
+    def __set__(self, instance, value):
+        coll = getattr(instance.parent.parent, self.collection)
+        if value in BUILTIN_FORMATS_REVERSE:
+            idx = BUILTIN_FORMATS_REVERSE[value]
+        else:
+            idx = coll.add(value) + BUILTIN_FORMATS_MAX_SIZE
+
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        setattr(instance._style, self.key, idx)
+
+
+    def __get__(self, instance, cls):
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        idx = getattr(instance._style, self.key)
+        if idx < BUILTIN_FORMATS_MAX_SIZE:
+            return BUILTIN_FORMATS.get(idx, "General")
+        coll = getattr(instance.parent.parent, self.collection)
+        return coll[idx - BUILTIN_FORMATS_MAX_SIZE]
+
+
+class NamedStyleDescriptor:
+
+    key = "xfId"
+    collection = "_named_styles"
+
+
+    def __set__(self, instance, value):
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        coll = getattr(instance.parent.parent, self.collection)
+        if isinstance(value, NamedStyle):
+            style = value
+            if style not in coll:
+                instance.parent.parent.add_named_style(style)
+        elif value not in coll.names:
+            if value in styles: # is it builtin?
+                style = styles[value]
+                if style not in coll:
+                    instance.parent.parent.add_named_style(style)
+            else:
+                raise ValueError("{0} is not a known style".format(value))
+        else:
+            style = coll[value]
+        instance._style = copy(style.as_tuple())
+
+
+    def __get__(self, instance, cls):
+        if not getattr(instance, "_style"):
+            instance._style = StyleArray()
+        idx = getattr(instance._style, self.key)
+        coll = getattr(instance.parent.parent, self.collection)
+        return coll.names[idx]
+
+
+class StyleArrayDescriptor:
+
+    def __init__(self, key):
+        self.key = key
+
+    def __set__(self, instance, value):
+        if instance._style is None:
+            instance._style = StyleArray()
+        setattr(instance._style, self.key, value)
+
+
+    def __get__(self, instance, cls):
+        if instance._style is None:
+            return False
+        return bool(getattr(instance._style, self.key))
+
+
+class StyleableObject:
+    """
+    Base class for styleble objects implementing proxy and lookup functions
+    """
+
+    font = StyleDescriptor('_fonts', "fontId")
+    fill = StyleDescriptor('_fills', "fillId")
+    border = StyleDescriptor('_borders', "borderId")
+    number_format = NumberFormatDescriptor()
+    protection = StyleDescriptor('_protections', "protectionId")
+    alignment = StyleDescriptor('_alignments', "alignmentId")
+    style = NamedStyleDescriptor()
+    quotePrefix = StyleArrayDescriptor('quotePrefix')
+    pivotButton = StyleArrayDescriptor('pivotButton')
+
+    __slots__ = ('parent', '_style')
+
+    def __init__(self, sheet, style_array=None):
+        self.parent = sheet
+        if style_array is not None:
+            style_array = StyleArray(style_array)
+        self._style = style_array
+
+
+    @property
+    def style_id(self):
+        if self._style is None:
+            self._style = StyleArray()
+        return self.parent.parent._cell_styles.add(self._style)
+
+
+    @property
+    def has_style(self):
+        if self._style is None:
+            return False
+        return any(self._style)
+
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()
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py b/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py
new file mode 100644
index 00000000..18307198
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/styles/table.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Float,
+    Bool,
+    Set,
+    Integer,
+    NoneSet,
+    String,
+    Sequence
+)
+
+from .colors import Color
+
+
+class TableStyleElement(Serialisable):
+
+    tagname = "tableStyleElement"
+
+    type = Set(values=(['wholeTable', 'headerRow', 'totalRow', 'firstColumn',
+                        'lastColumn', 'firstRowStripe', 'secondRowStripe', 'firstColumnStripe',
+                        'secondColumnStripe', 'firstHeaderCell', 'lastHeaderCell',
+                        'firstTotalCell', 'lastTotalCell', 'firstSubtotalColumn',
+                        'secondSubtotalColumn', 'thirdSubtotalColumn', 'firstSubtotalRow',
+                        'secondSubtotalRow', 'thirdSubtotalRow', 'blankRow',
+                        'firstColumnSubheading', 'secondColumnSubheading',
+                        'thirdColumnSubheading', 'firstRowSubheading', 'secondRowSubheading',
+                        'thirdRowSubheading', 'pageFieldLabels', 'pageFieldValues']))
+    size = Integer(allow_none=True)
+    dxfId = Integer(allow_none=True)
+
+    def __init__(self,
+                 type=None,
+                 size=None,
+                 dxfId=None,
+                ):
+        self.type = type
+        self.size = size
+        self.dxfId = dxfId
+
+
+class TableStyle(Serialisable):
+
+    tagname = "tableStyle"
+
+    name = String()
+    pivot = Bool(allow_none=True)
+    table = Bool(allow_none=True)
+    count = Integer(allow_none=True)
+    tableStyleElement = Sequence(expected_type=TableStyleElement, allow_none=True)
+
+    __elements__ = ('tableStyleElement',)
+
+    def __init__(self,
+                 name=None,
+                 pivot=None,
+                 table=None,
+                 count=None,
+                 tableStyleElement=(),
+                ):
+        self.name = name
+        self.pivot = pivot
+        self.table = table
+        self.count = count
+        self.tableStyleElement = tableStyleElement
+
+
+class TableStyleList(Serialisable):
+
+    tagname = "tableStyles"
+
+    defaultTableStyle = String(allow_none=True)
+    defaultPivotStyle = String(allow_none=True)
+    tableStyle = Sequence(expected_type=TableStyle, allow_none=True)
+
+    __elements__ = ('tableStyle',)
+    __attrs__ = ("count", "defaultTableStyle", "defaultPivotStyle")
+
+    def __init__(self,
+                 count=None,
+                 defaultTableStyle="TableStyleMedium9",
+                 defaultPivotStyle="PivotStyleLight16",
+                 tableStyle=(),
+                ):
+        self.defaultTableStyle = defaultTableStyle
+        self.defaultPivotStyle = defaultPivotStyle
+        self.tableStyle = tableStyle
+
+
+    @property
+    def count(self):
+        return len(self.tableStyle)