1# Copyright (c) 2010-2024 openpyxl
2
3from array import array
4
5from openpyxl.descriptors.serialisable import Serialisable
6from openpyxl.descriptors import (
7 Typed,
8 Float,
9 Bool,
10 Integer,
11 Sequence,
12)
13from openpyxl.descriptors.excel import ExtensionList
14from openpyxl.utils.indexed_list import IndexedList
15
16
17from .alignment import Alignment
18from .protection import Protection
19
20
21class ArrayDescriptor:
22
23 def __init__(self, key):
24 self.key = key
25
26 def __get__(self, instance, cls):
27 return instance[self.key]
28
29 def __set__(self, instance, value):
30 instance[self.key] = value
31
32
33class StyleArray(array):
34 """
35 Simplified named tuple with an array
36 """
37
38 __slots__ = ()
39 tagname = 'xf'
40
41 fontId = ArrayDescriptor(0)
42 fillId = ArrayDescriptor(1)
43 borderId = ArrayDescriptor(2)
44 numFmtId = ArrayDescriptor(3)
45 protectionId = ArrayDescriptor(4)
46 alignmentId = ArrayDescriptor(5)
47 pivotButton = ArrayDescriptor(6)
48 quotePrefix = ArrayDescriptor(7)
49 xfId = ArrayDescriptor(8)
50
51
52 def __new__(cls, args=[0]*9):
53 return array.__new__(cls, 'i', args)
54
55
56 def __hash__(self):
57 return hash(tuple(self))
58
59
60 def __copy__(self):
61 return StyleArray((self))
62
63
64 def __deepcopy__(self, memo):
65 return StyleArray((self))
66
67
68class CellStyle(Serialisable):
69
70 tagname = "xf"
71
72 numFmtId = Integer()
73 fontId = Integer()
74 fillId = Integer()
75 borderId = Integer()
76 xfId = Integer(allow_none=True)
77 quotePrefix = Bool(allow_none=True)
78 pivotButton = Bool(allow_none=True)
79 applyNumberFormat = Bool(allow_none=True)
80 applyFont = Bool(allow_none=True)
81 applyFill = Bool(allow_none=True)
82 applyBorder = Bool(allow_none=True)
83 applyAlignment = Bool(allow_none=True)
84 applyProtection = Bool(allow_none=True)
85 alignment = Typed(expected_type=Alignment, allow_none=True)
86 protection = Typed(expected_type=Protection, allow_none=True)
87 extLst = Typed(expected_type=ExtensionList, allow_none=True)
88
89 __elements__ = ('alignment', 'protection')
90 __attrs__ = ("numFmtId", "fontId", "fillId", "borderId",
91 "applyAlignment", "applyProtection", "pivotButton", "quotePrefix", "xfId")
92
93 def __init__(self,
94 numFmtId=0,
95 fontId=0,
96 fillId=0,
97 borderId=0,
98 xfId=None,
99 quotePrefix=None,
100 pivotButton=None,
101 applyNumberFormat=None,
102 applyFont=None,
103 applyFill=None,
104 applyBorder=None,
105 applyAlignment=None,
106 applyProtection=None,
107 alignment=None,
108 protection=None,
109 extLst=None,
110 ):
111 self.numFmtId = numFmtId
112 self.fontId = fontId
113 self.fillId = fillId
114 self.borderId = borderId
115 self.xfId = xfId
116 self.quotePrefix = quotePrefix
117 self.pivotButton = pivotButton
118 self.applyNumberFormat = applyNumberFormat
119 self.applyFont = applyFont
120 self.applyFill = applyFill
121 self.applyBorder = applyBorder
122 self.alignment = alignment
123 self.protection = protection
124
125
126 def to_array(self):
127 """
128 Convert to StyleArray
129 """
130 style = StyleArray()
131 for k in ("fontId", "fillId", "borderId", "numFmtId", "pivotButton",
132 "quotePrefix", "xfId"):
133 v = getattr(self, k, 0)
134 if v is not None:
135 setattr(style, k, v)
136 return style
137
138
139 @classmethod
140 def from_array(cls, style):
141 """
142 Convert from StyleArray
143 """
144 return cls(numFmtId=style.numFmtId, fontId=style.fontId,
145 fillId=style.fillId, borderId=style.borderId, xfId=style.xfId,
146 quotePrefix=style.quotePrefix, pivotButton=style.pivotButton,)
147
148
149 @property
150 def applyProtection(self):
151 return self.protection is not None or None
152
153
154 @property
155 def applyAlignment(self):
156 return self.alignment is not None or None
157
158
159class CellStyleList(Serialisable):
160
161 tagname = "cellXfs"
162
163 __attrs__ = ("count",)
164
165 count = Integer(allow_none=True)
166 xf = Sequence(expected_type=CellStyle)
167 alignment = Sequence(expected_type=Alignment)
168 protection = Sequence(expected_type=Protection)
169
170 __elements__ = ('xf',)
171
172 def __init__(self,
173 count=None,
174 xf=(),
175 ):
176 self.xf = xf
177
178
179 @property
180 def count(self):
181 return len(self.xf)
182
183
184 def __getitem__(self, idx):
185 try:
186 return self.xf[idx]
187 except IndexError:
188 print((f"{idx} is out of range"))
189 return self.xf[idx]
190
191
192 def _to_array(self):
193 """
194 Extract protection and alignments, convert to style array
195 """
196 self.prots = IndexedList([Protection()])
197 self.alignments = IndexedList([Alignment()])
198 styles = [] # allow duplicates
199 for xf in self.xf:
200 style = xf.to_array()
201 if xf.alignment is not None:
202 style.alignmentId = self.alignments.add(xf.alignment)
203 if xf.protection is not None:
204 style.protectionId = self.prots.add(xf.protection)
205 styles.append(style)
206 return IndexedList(styles)