Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/styles/named_styles.py: 82%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

135 statements  

1# Copyright (c) 2010-2024 openpyxl 

2 

3from openpyxl.compat import safe_string 

4 

5from openpyxl.descriptors import ( 

6 Typed, 

7 Integer, 

8 Bool, 

9 String, 

10 Sequence, 

11) 

12from openpyxl.descriptors.excel import ExtensionList 

13from openpyxl.descriptors.serialisable import Serialisable 

14 

15from .fills import PatternFill, Fill 

16from .fonts import Font 

17from .borders import Border 

18from .alignment import Alignment 

19from .protection import Protection 

20from .numbers import ( 

21 NumberFormatDescriptor, 

22 BUILTIN_FORMATS_MAX_SIZE, 

23 BUILTIN_FORMATS_REVERSE, 

24) 

25from .cell_style import ( 

26 StyleArray, 

27 CellStyle, 

28) 

29 

30 

31class NamedStyle(Serialisable): 

32 

33 """ 

34 Named and editable styles 

35 """ 

36 

37 font = Typed(expected_type=Font) 

38 fill = Typed(expected_type=Fill) 

39 border = Typed(expected_type=Border) 

40 alignment = Typed(expected_type=Alignment) 

41 number_format = NumberFormatDescriptor() 

42 protection = Typed(expected_type=Protection) 

43 builtinId = Integer(allow_none=True) 

44 hidden = Bool(allow_none=True) 

45 name = String() 

46 _wb = None 

47 _style = StyleArray() 

48 

49 

50 def __init__(self, 

51 name="Normal", 

52 font=None, 

53 fill=None, 

54 border=None, 

55 alignment=None, 

56 number_format=None, 

57 protection=None, 

58 builtinId=None, 

59 hidden=False, 

60 ): 

61 self.name = name 

62 self.font = font or Font() 

63 self.fill = fill or PatternFill() 

64 self.border = border or Border() 

65 self.alignment = alignment or Alignment() 

66 self.number_format = number_format 

67 self.protection = protection or Protection() 

68 self.builtinId = builtinId 

69 self.hidden = hidden 

70 self._wb = None 

71 self._style = StyleArray() 

72 

73 

74 def __setattr__(self, attr, value): 

75 super().__setattr__(attr, value) 

76 if getattr(self, '_wb', None) and attr in ( 

77 'font', 'fill', 'border', 'alignment', 'number_format', 'protection', 

78 ): 

79 self._recalculate() 

80 

81 

82 def __iter__(self): 

83 for key in ('name', 'builtinId', 'hidden', 'xfId'): 

84 value = getattr(self, key, None) 

85 if value is not None: 

86 yield key, safe_string(value) 

87 

88 

89 def bind(self, wb): 

90 """ 

91 Bind a named style to a workbook 

92 """ 

93 self._wb = wb 

94 self._recalculate() 

95 

96 

97 def _recalculate(self): 

98 self._style.fontId = self._wb._fonts.add(self.font) 

99 self._style.borderId = self._wb._borders.add(self.border) 

100 self._style.fillId = self._wb._fills.add(self.fill) 

101 self._style.protectionId = self._wb._protections.add(self.protection) 

102 self._style.alignmentId = self._wb._alignments.add(self.alignment) 

103 fmt = self.number_format 

104 if fmt in BUILTIN_FORMATS_REVERSE: 

105 fmt = BUILTIN_FORMATS_REVERSE[fmt] 

106 else: 

107 fmt = self._wb._number_formats.add(self.number_format) + ( 

108 BUILTIN_FORMATS_MAX_SIZE) 

109 self._style.numFmtId = fmt 

110 

111 

112 def as_tuple(self): 

113 """Return a style array representing the current style""" 

114 return self._style 

115 

116 

117 def as_xf(self): 

118 """ 

119 Return equivalent XfStyle 

120 """ 

121 xf = CellStyle.from_array(self._style) 

122 xf.xfId = None 

123 xf.pivotButton = None 

124 xf.quotePrefix = None 

125 if self.alignment != Alignment(): 

126 xf.alignment = self.alignment 

127 if self.protection != Protection(): 

128 xf.protection = self.protection 

129 return xf 

130 

131 

132 def as_name(self): 

133 """ 

134 Return relevant named style 

135 

136 """ 

137 named = _NamedCellStyle( 

138 name=self.name, 

139 builtinId=self.builtinId, 

140 hidden=self.hidden, 

141 xfId=self._style.xfId 

142 ) 

143 return named 

144 

145 

146class NamedStyleList(list): 

147 """ 

148 Named styles are editable and can be applied to multiple objects 

149 

150 As only the index is stored in referencing objects the order mus 

151 be preserved. 

152 

153 Returns a list of NamedStyles 

154 """ 

155 

156 def __init__(self, iterable=()): 

157 """ 

158 Allow a list of named styles to be passed in and index them. 

159 """ 

160 

161 for idx, s in enumerate(iterable, len(self)): 

162 s._style.xfId = idx 

163 super().__init__(iterable) 

164 

165 

166 @property 

167 def names(self): 

168 return [s.name for s in self] 

169 

170 

171 def __getitem__(self, key): 

172 if isinstance(key, int): 

173 return super().__getitem__(key) 

174 

175 

176 for idx, name in enumerate(self.names): 

177 if name == key: 

178 return self[idx] 

179 

180 raise KeyError("No named style with the name{0} exists".format(key)) 

181 

182 def append(self, style): 

183 if not isinstance(style, NamedStyle): 

184 raise TypeError("""Only NamedStyle instances can be added""") 

185 elif style.name in self.names: # hotspot 

186 raise ValueError("""Style {0} exists already""".format(style.name)) 

187 style._style.xfId = (len(self)) 

188 super().append(style) 

189 

190 

191class _NamedCellStyle(Serialisable): 

192 

193 """ 

194 Pointer-based representation of named styles in XML 

195 xfId refers to the corresponding CellStyleXfs 

196 

197 Not used in client code. 

198 """ 

199 

200 tagname = "cellStyle" 

201 

202 name = String() 

203 xfId = Integer() 

204 builtinId = Integer(allow_none=True) 

205 iLevel = Integer(allow_none=True) 

206 hidden = Bool(allow_none=True) 

207 customBuiltin = Bool(allow_none=True) 

208 extLst = Typed(expected_type=ExtensionList, allow_none=True) 

209 

210 __elements__ = () 

211 

212 

213 def __init__(self, 

214 name=None, 

215 xfId=None, 

216 builtinId=None, 

217 iLevel=None, 

218 hidden=None, 

219 customBuiltin=None, 

220 extLst=None, 

221 ): 

222 self.name = name 

223 self.xfId = xfId 

224 self.builtinId = builtinId 

225 self.iLevel = iLevel 

226 self.hidden = hidden 

227 self.customBuiltin = customBuiltin 

228 

229 

230class _NamedCellStyleList(Serialisable): 

231 """ 

232 Container for named cell style objects 

233 

234 Not used in client code 

235 """ 

236 

237 tagname = "cellStyles" 

238 

239 count = Integer(allow_none=True) 

240 cellStyle = Sequence(expected_type=_NamedCellStyle) 

241 

242 __attrs__ = ("count",) 

243 

244 def __init__(self, 

245 count=None, 

246 cellStyle=(), 

247 ): 

248 self.cellStyle = cellStyle 

249 

250 

251 @property 

252 def count(self): 

253 return len(self.cellStyle) 

254 

255 

256 def remove_duplicates(self): 

257 """ 

258 Some applications contain duplicate definitions either by name or 

259 referenced style. 

260 

261 As the references are 0-based indices, styles are sorted by 

262 index. 

263 

264 Returns a list of style references with duplicates removed 

265 """ 

266 

267 def sort_fn(v): 

268 return v.xfId 

269 

270 styles = [] 

271 names = set() 

272 ids = set() 

273 

274 for ns in sorted(self.cellStyle, key=sort_fn): 

275 if ns.xfId in ids or ns.name in names: # skip duplicates 

276 continue 

277 ids.add(ns.xfId) 

278 names.add(ns.name) 

279 

280 styles.append(ns) 

281 

282 return styles