Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/openpyxl/styles/stylesheet.py: 21%

165 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# Copyright (c) 2010-2022 openpyxl 

2 

3from warnings import warn 

4 

5from openpyxl.descriptors.serialisable import Serialisable 

6from openpyxl.descriptors import ( 

7 Typed, 

8) 

9from openpyxl.descriptors.sequence import NestedSequence 

10from openpyxl.descriptors.excel import ExtensionList 

11from openpyxl.utils.indexed_list import IndexedList 

12from openpyxl.xml.constants import ARC_STYLE, SHEET_MAIN_NS 

13from openpyxl.xml.functions import fromstring 

14 

15from .builtins import styles 

16from .colors import ColorList, COLOR_INDEX 

17from .differential import DifferentialStyle 

18from .table import TableStyleList 

19from .borders import Border 

20from .fills import Fill 

21from .fonts import Font 

22from .numbers import ( 

23 NumberFormatList, 

24 BUILTIN_FORMATS, 

25 BUILTIN_FORMATS_MAX_SIZE, 

26 BUILTIN_FORMATS_REVERSE, 

27 is_date_format, 

28 is_timedelta_format, 

29 builtin_format_code 

30) 

31from .named_styles import ( 

32 _NamedCellStyleList 

33) 

34from .cell_style import CellStyle, CellStyleList 

35 

36 

37class Stylesheet(Serialisable): 

38 

39 tagname = "styleSheet" 

40 

41 numFmts = Typed(expected_type=NumberFormatList) 

42 fonts = NestedSequence(expected_type=Font, count=True) 

43 fills = NestedSequence(expected_type=Fill, count=True) 

44 borders = NestedSequence(expected_type=Border, count=True) 

45 cellStyleXfs = Typed(expected_type=CellStyleList) 

46 cellXfs = Typed(expected_type=CellStyleList) 

47 cellStyles = Typed(expected_type=_NamedCellStyleList) 

48 dxfs = NestedSequence(expected_type=DifferentialStyle, count=True) 

49 tableStyles = Typed(expected_type=TableStyleList, allow_none=True) 

50 colors = Typed(expected_type=ColorList, allow_none=True) 

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

52 

53 __elements__ = ('numFmts', 'fonts', 'fills', 'borders', 'cellStyleXfs', 

54 'cellXfs', 'cellStyles', 'dxfs', 'tableStyles', 'colors') 

55 

56 def __init__(self, 

57 numFmts=None, 

58 fonts=(), 

59 fills=(), 

60 borders=(), 

61 cellStyleXfs=None, 

62 cellXfs=None, 

63 cellStyles=None, 

64 dxfs=(), 

65 tableStyles=None, 

66 colors=None, 

67 extLst=None, 

68 ): 

69 if numFmts is None: 

70 numFmts = NumberFormatList() 

71 self.numFmts = numFmts 

72 self.number_formats = IndexedList() 

73 self.fonts = fonts 

74 self.fills = fills 

75 self.borders = borders 

76 if cellStyleXfs is None: 

77 cellStyleXfs = CellStyleList() 

78 self.cellStyleXfs = cellStyleXfs 

79 if cellXfs is None: 

80 cellXfs = CellStyleList() 

81 self.cellXfs = cellXfs 

82 if cellStyles is None: 

83 cellStyles = _NamedCellStyleList() 

84 self.cellStyles = cellStyles 

85 

86 self.dxfs = dxfs 

87 self.tableStyles = tableStyles 

88 self.colors = colors 

89 

90 self.cell_styles = self.cellXfs._to_array() 

91 self.alignments = self.cellXfs.alignments 

92 self.protections = self.cellXfs.prots 

93 self._normalise_numbers() 

94 self.named_styles = self._merge_named_styles() 

95 

96 

97 @classmethod 

98 def from_tree(cls, node): 

99 # strip all attribs 

100 attrs = dict(node.attrib) 

101 for k in attrs: 

102 del node.attrib[k] 

103 return super(Stylesheet, cls).from_tree(node) 

104 

105 

106 def _merge_named_styles(self): 

107 """ 

108 Merge named style names "cellStyles" with their associated styles 

109 "cellStyleXfs" 

110 """ 

111 named_styles = self.cellStyles.names 

112 

113 for style in named_styles: 

114 self._expand_named_style(style) 

115 

116 return named_styles 

117 

118 

119 def _expand_named_style(self, named_style): 

120 """ 

121 Bind format definitions for a named style from the associated style 

122 record 

123 """ 

124 xf = self.cellStyleXfs[named_style.xfId] 

125 named_style.font = self.fonts[xf.fontId] 

126 named_style.fill = self.fills[xf.fillId] 

127 named_style.border = self.borders[xf.borderId] 

128 if xf.numFmtId < BUILTIN_FORMATS_MAX_SIZE: 

129 formats = BUILTIN_FORMATS 

130 else: 

131 formats = self.custom_formats 

132 if xf.numFmtId in formats: 

133 named_style.number_format = formats[xf.numFmtId] 

134 if xf.alignment: 

135 named_style.alignment = xf.alignment 

136 if xf.protection: 

137 named_style.protection = xf.protection 

138 

139 

140 def _split_named_styles(self, wb): 

141 """ 

142 Convert NamedStyle into separate CellStyle and Xf objects 

143 """ 

144 for style in wb._named_styles: 

145 self.cellStyles.cellStyle.append(style.as_name()) 

146 self.cellStyleXfs.xf.append(style.as_xf()) 

147 

148 

149 @property 

150 def custom_formats(self): 

151 return dict([(n.numFmtId, n.formatCode) for n in self.numFmts.numFmt]) 

152 

153 

154 def _normalise_numbers(self): 

155 """ 

156 Rebase custom numFmtIds with a floor of 164 when reading stylesheet 

157 And index datetime formats 

158 """ 

159 date_formats = set() 

160 timedelta_formats = set() 

161 custom = self.custom_formats 

162 formats = self.number_formats 

163 for idx, style in enumerate(self.cell_styles): 

164 if style.numFmtId in custom: 

165 fmt = custom[style.numFmtId] 

166 if fmt in BUILTIN_FORMATS_REVERSE: # remove builtins 

167 style.numFmtId = BUILTIN_FORMATS_REVERSE[fmt] 

168 else: 

169 style.numFmtId = formats.add(fmt) + BUILTIN_FORMATS_MAX_SIZE 

170 else: 

171 fmt = builtin_format_code(style.numFmtId) 

172 if is_date_format(fmt): 

173 # Create an index of which styles refer to datetimes 

174 date_formats.add(idx) 

175 if is_timedelta_format(fmt): 

176 # Create an index of which styles refer to timedeltas 

177 timedelta_formats.add(idx) 

178 self.date_formats = date_formats 

179 self.timedelta_formats = timedelta_formats 

180 

181 

182 def to_tree(self, tagname=None, idx=None, namespace=None): 

183 tree = super(Stylesheet, self).to_tree(tagname, idx, namespace) 

184 tree.set("xmlns", SHEET_MAIN_NS) 

185 return tree 

186 

187 

188def apply_stylesheet(archive, wb): 

189 """ 

190 Add styles to workbook if present 

191 """ 

192 try: 

193 src = archive.read(ARC_STYLE) 

194 except KeyError: 

195 return wb 

196 

197 node = fromstring(src) 

198 stylesheet = Stylesheet.from_tree(node) 

199 

200 if stylesheet.cell_styles: 

201 

202 wb._borders = IndexedList(stylesheet.borders) 

203 wb._fonts = IndexedList(stylesheet.fonts) 

204 wb._fills = IndexedList(stylesheet.fills) 

205 wb._differential_styles.styles = stylesheet.dxfs 

206 wb._number_formats = stylesheet.number_formats 

207 wb._protections = stylesheet.protections 

208 wb._alignments = stylesheet.alignments 

209 wb._table_styles = stylesheet.tableStyles 

210 

211 # need to overwrite openpyxl defaults in case workbook has different ones 

212 wb._cell_styles = stylesheet.cell_styles 

213 wb._named_styles = stylesheet.named_styles 

214 wb._date_formats = stylesheet.date_formats 

215 wb._timedelta_formats = stylesheet.timedelta_formats 

216 

217 for ns in wb._named_styles: 

218 ns.bind(wb) 

219 

220 else: 

221 warn("Workbook contains no stylesheet, using openpyxl's defaults") 

222 

223 if not wb._named_styles: 

224 normal = styles['Normal'] 

225 wb.add_named_style(normal) 

226 warn("Workbook contains no default style, apply openpyxl's default") 

227 

228 if stylesheet.colors is not None: 

229 wb._colors = stylesheet.colors.index 

230 

231 

232def write_stylesheet(wb): 

233 stylesheet = Stylesheet() 

234 stylesheet.fonts = wb._fonts 

235 stylesheet.fills = wb._fills 

236 stylesheet.borders = wb._borders 

237 stylesheet.dxfs = wb._differential_styles.styles 

238 stylesheet.colors = ColorList(indexedColors=wb._colors) 

239 

240 from .numbers import NumberFormat 

241 fmts = [] 

242 for idx, code in enumerate(wb._number_formats, BUILTIN_FORMATS_MAX_SIZE): 

243 fmt = NumberFormat(idx, code) 

244 fmts.append(fmt) 

245 

246 stylesheet.numFmts.numFmt = fmts 

247 

248 xfs = [] 

249 for style in wb._cell_styles: 

250 xf = CellStyle.from_array(style) 

251 

252 if style.alignmentId: 

253 xf.alignment = wb._alignments[style.alignmentId] 

254 

255 if style.protectionId: 

256 xf.protection = wb._protections[style.protectionId] 

257 xfs.append(xf) 

258 stylesheet.cellXfs = CellStyleList(xf=xfs) 

259 

260 stylesheet._split_named_styles(wb) 

261 stylesheet.tableStyles = wb._table_styles 

262 

263 return stylesheet.to_tree()