1# Copyright (c) 2010-2024 openpyxl
2
3from copy import copy
4
5from .numbers import (
6 BUILTIN_FORMATS,
7 BUILTIN_FORMATS_MAX_SIZE,
8 BUILTIN_FORMATS_REVERSE,
9)
10from .proxy import StyleProxy
11from .cell_style import StyleArray
12from .named_styles import NamedStyle
13from .builtins import styles
14
15
16class StyleDescriptor:
17
18 def __init__(self, collection, key):
19 self.collection = collection
20 self.key = key
21
22 def __set__(self, instance, value):
23 coll = getattr(instance.parent.parent, self.collection)
24 if not getattr(instance, "_style"):
25 instance._style = StyleArray()
26 setattr(instance._style, self.key, coll.add(value))
27
28
29 def __get__(self, instance, cls):
30 coll = getattr(instance.parent.parent, self.collection)
31 if not getattr(instance, "_style"):
32 instance._style = StyleArray()
33 idx = getattr(instance._style, self.key)
34 return StyleProxy(coll[idx])
35
36
37class NumberFormatDescriptor:
38
39 key = "numFmtId"
40 collection = '_number_formats'
41
42 def __set__(self, instance, value):
43 coll = getattr(instance.parent.parent, self.collection)
44 if value in BUILTIN_FORMATS_REVERSE:
45 idx = BUILTIN_FORMATS_REVERSE[value]
46 else:
47 idx = coll.add(value) + BUILTIN_FORMATS_MAX_SIZE
48
49 if not getattr(instance, "_style"):
50 instance._style = StyleArray()
51 setattr(instance._style, self.key, idx)
52
53
54 def __get__(self, instance, cls):
55 if not getattr(instance, "_style"):
56 instance._style = StyleArray()
57 idx = getattr(instance._style, self.key)
58 if idx < BUILTIN_FORMATS_MAX_SIZE:
59 return BUILTIN_FORMATS.get(idx, "General")
60 coll = getattr(instance.parent.parent, self.collection)
61 return coll[idx - BUILTIN_FORMATS_MAX_SIZE]
62
63
64class NamedStyleDescriptor:
65
66 key = "xfId"
67 collection = "_named_styles"
68
69
70 def __set__(self, instance, value):
71 if not getattr(instance, "_style"):
72 instance._style = StyleArray()
73 coll = getattr(instance.parent.parent, self.collection)
74 if isinstance(value, NamedStyle):
75 style = value
76 if style not in coll:
77 instance.parent.parent.add_named_style(style)
78 elif value not in coll.names:
79 if value in styles: # is it builtin?
80 style = styles[value]
81 if style not in coll:
82 instance.parent.parent.add_named_style(style)
83 else:
84 raise ValueError("{0} is not a known style".format(value))
85 else:
86 style = coll[value]
87 instance._style = copy(style.as_tuple())
88
89
90 def __get__(self, instance, cls):
91 if not getattr(instance, "_style"):
92 instance._style = StyleArray()
93 idx = getattr(instance._style, self.key)
94 coll = getattr(instance.parent.parent, self.collection)
95 return coll.names[idx]
96
97
98class StyleArrayDescriptor:
99
100 def __init__(self, key):
101 self.key = key
102
103 def __set__(self, instance, value):
104 if instance._style is None:
105 instance._style = StyleArray()
106 setattr(instance._style, self.key, value)
107
108
109 def __get__(self, instance, cls):
110 if instance._style is None:
111 return False
112 return bool(getattr(instance._style, self.key))
113
114
115class StyleableObject:
116 """
117 Base class for styleble objects implementing proxy and lookup functions
118 """
119
120 font = StyleDescriptor('_fonts', "fontId")
121 fill = StyleDescriptor('_fills', "fillId")
122 border = StyleDescriptor('_borders', "borderId")
123 number_format = NumberFormatDescriptor()
124 protection = StyleDescriptor('_protections', "protectionId")
125 alignment = StyleDescriptor('_alignments', "alignmentId")
126 style = NamedStyleDescriptor()
127 quotePrefix = StyleArrayDescriptor('quotePrefix')
128 pivotButton = StyleArrayDescriptor('pivotButton')
129
130 __slots__ = ('parent', '_style')
131
132 def __init__(self, sheet, style_array=None):
133 self.parent = sheet
134 if style_array is not None:
135 style_array = StyleArray(style_array)
136 self._style = style_array
137
138
139 @property
140 def style_id(self):
141 if self._style is None:
142 self._style = StyleArray()
143 return self.parent.parent._cell_styles.add(self._style)
144
145
146 @property
147 def has_style(self):
148 if self._style is None:
149 return False
150 return any(self._style)
151