Updated script that can be controled by Nodejs web app
This commit is contained in:
11
lib/python3.13/site-packages/openpyxl/styles/__init__.py
Normal file
11
lib/python3.13/site-packages/openpyxl/styles/__init__.py
Normal file
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
62
lib/python3.13/site-packages/openpyxl/styles/alignment.py
Normal file
62
lib/python3.13/site-packages/openpyxl/styles/alignment.py
Normal file
@ -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)
|
103
lib/python3.13/site-packages/openpyxl/styles/borders.py
Normal file
103
lib/python3.13/site-packages/openpyxl/styles/borders.py
Normal file
@ -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())
|
1397
lib/python3.13/site-packages/openpyxl/styles/builtins.py
Normal file
1397
lib/python3.13/site-packages/openpyxl/styles/builtins.py
Normal file
File diff suppressed because it is too large
Load Diff
206
lib/python3.13/site-packages/openpyxl/styles/cell_style.py
Normal file
206
lib/python3.13/site-packages/openpyxl/styles/cell_style.py
Normal file
@ -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)
|
172
lib/python3.13/site-packages/openpyxl/styles/colors.py
Normal file
172
lib/python3.13/site-packages/openpyxl/styles/colors.py
Normal file
@ -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]
|
95
lib/python3.13/site-packages/openpyxl/styles/differential.py
Normal file
95
lib/python3.13/site-packages/openpyxl/styles/differential.py
Normal file
@ -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)
|
224
lib/python3.13/site-packages/openpyxl/styles/fills.py
Normal file
224
lib/python3.13/site-packages/openpyxl/styles/fills.py
Normal file
@ -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
|
113
lib/python3.13/site-packages/openpyxl/styles/fonts.py
Normal file
113
lib/python3.13/site-packages/openpyxl/styles/fonts.py
Normal file
@ -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")
|
282
lib/python3.13/site-packages/openpyxl/styles/named_styles.py
Normal file
282
lib/python3.13/site-packages/openpyxl/styles/named_styles.py
Normal file
@ -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
|
200
lib/python3.13/site-packages/openpyxl/styles/numbers.py
Normal file
200
lib/python3.13/site-packages/openpyxl/styles/numbers.py
Normal file
@ -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]
|
17
lib/python3.13/site-packages/openpyxl/styles/protection.py
Normal file
17
lib/python3.13/site-packages/openpyxl/styles/protection.py
Normal file
@ -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
|
62
lib/python3.13/site-packages/openpyxl/styles/proxy.py
Normal file
62
lib/python3.13/site-packages/openpyxl/styles/proxy.py
Normal file
@ -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
|
151
lib/python3.13/site-packages/openpyxl/styles/styleable.py
Normal file
151
lib/python3.13/site-packages/openpyxl/styles/styleable.py
Normal file
@ -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)
|
||||
|
274
lib/python3.13/site-packages/openpyxl/styles/stylesheet.py
Normal file
274
lib/python3.13/site-packages/openpyxl/styles/stylesheet.py
Normal file
@ -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()
|
94
lib/python3.13/site-packages/openpyxl/styles/table.py
Normal file
94
lib/python3.13/site-packages/openpyxl/styles/table.py
Normal file
@ -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)
|
Reference in New Issue
Block a user