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