Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/xlsxwriter/styles.py: 57%

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

362 statements  

1############################################################################### 

2# 

3# Styles - A class for writing the Excel XLSX Worksheet file. 

4# 

5# SPDX-License-Identifier: BSD-2-Clause 

6# Copyright 2013-2024, John McNamara, jmcnamara@cpan.org 

7# 

8 

9# Package imports. 

10from . import xmlwriter 

11 

12 

13class Styles(xmlwriter.XMLwriter): 

14 """ 

15 A class for writing the Excel XLSX Styles file. 

16 

17 

18 """ 

19 

20 ########################################################################### 

21 # 

22 # Public API. 

23 # 

24 ########################################################################### 

25 

26 def __init__(self): 

27 """ 

28 Constructor. 

29 

30 """ 

31 

32 super(Styles, self).__init__() 

33 

34 self.xf_formats = [] 

35 self.palette = [] 

36 self.font_count = 0 

37 self.num_formats = [] 

38 self.border_count = 0 

39 self.fill_count = 0 

40 self.custom_colors = [] 

41 self.dxf_formats = [] 

42 self.has_hyperlink = False 

43 self.hyperlink_font_id = 0 

44 self.has_comments = False 

45 

46 ########################################################################### 

47 # 

48 # Private API. 

49 # 

50 ########################################################################### 

51 

52 def _assemble_xml_file(self): 

53 # Assemble and write the XML file. 

54 

55 # Write the XML declaration. 

56 self._xml_declaration() 

57 

58 # Add the style sheet. 

59 self._write_style_sheet() 

60 

61 # Write the number formats. 

62 self._write_num_fmts() 

63 

64 # Write the fonts. 

65 self._write_fonts() 

66 

67 # Write the fills. 

68 self._write_fills() 

69 

70 # Write the borders element. 

71 self._write_borders() 

72 

73 # Write the cellStyleXfs element. 

74 self._write_cell_style_xfs() 

75 

76 # Write the cellXfs element. 

77 self._write_cell_xfs() 

78 

79 # Write the cellStyles element. 

80 self._write_cell_styles() 

81 

82 # Write the dxfs element. 

83 self._write_dxfs() 

84 

85 # Write the tableStyles element. 

86 self._write_table_styles() 

87 

88 # Write the colors element. 

89 self._write_colors() 

90 

91 # Close the style sheet tag. 

92 self._xml_end_tag("styleSheet") 

93 

94 # Close the file. 

95 self._xml_close() 

96 

97 def _set_style_properties(self, properties): 

98 # Pass in the Format objects and other properties used in the styles. 

99 

100 self.xf_formats = properties[0] 

101 self.palette = properties[1] 

102 self.font_count = properties[2] 

103 self.num_formats = properties[3] 

104 self.border_count = properties[4] 

105 self.fill_count = properties[5] 

106 self.custom_colors = properties[6] 

107 self.dxf_formats = properties[7] 

108 self.has_comments = properties[8] 

109 

110 def _get_palette_color(self, color): 

111 # Special handling for automatic color. 

112 if color == "Automatic": 

113 return color 

114 

115 # Convert the RGB color. 

116 if color[0] == "#": 

117 color = color[1:] 

118 

119 return "FF" + color.upper() 

120 

121 ########################################################################### 

122 # 

123 # XML methods. 

124 # 

125 ########################################################################### 

126 

127 def _write_style_sheet(self): 

128 # Write the <styleSheet> element. 

129 xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" 

130 

131 attributes = [("xmlns", xmlns)] 

132 self._xml_start_tag("styleSheet", attributes) 

133 

134 def _write_num_fmts(self): 

135 # Write the <numFmts> element. 

136 if not self.num_formats: 

137 return 

138 

139 attributes = [("count", len(self.num_formats))] 

140 self._xml_start_tag("numFmts", attributes) 

141 

142 # Write the numFmts elements. 

143 for index, num_format in enumerate(self.num_formats, 164): 

144 self._write_num_fmt(index, num_format) 

145 

146 self._xml_end_tag("numFmts") 

147 

148 def _write_num_fmt(self, num_fmt_id, format_code): 

149 # Write the <numFmt> element. 

150 format_codes = { 

151 0: "General", 

152 1: "0", 

153 2: "0.00", 

154 3: "#,##0", 

155 4: "#,##0.00", 

156 5: "($#,##0_);($#,##0)", 

157 6: "($#,##0_);[Red]($#,##0)", 

158 7: "($#,##0.00_);($#,##0.00)", 

159 8: "($#,##0.00_);[Red]($#,##0.00)", 

160 9: "0%", 

161 10: "0.00%", 

162 11: "0.00E+00", 

163 12: "# ?/?", 

164 13: "# ??/??", 

165 14: "m/d/yy", 

166 15: "d-mmm-yy", 

167 16: "d-mmm", 

168 17: "mmm-yy", 

169 18: "h:mm AM/PM", 

170 19: "h:mm:ss AM/PM", 

171 20: "h:mm", 

172 21: "h:mm:ss", 

173 22: "m/d/yy h:mm", 

174 37: "(#,##0_);(#,##0)", 

175 38: "(#,##0_);[Red](#,##0)", 

176 39: "(#,##0.00_);(#,##0.00)", 

177 40: "(#,##0.00_);[Red](#,##0.00)", 

178 41: '_(* #,##0_);_(* (#,##0);_(* "-"_);_(_)', 

179 42: '_($* #,##0_);_($* (#,##0);_($* "-"_);_(_)', 

180 43: '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(_)', 

181 44: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(_)', 

182 45: "mm:ss", 

183 46: "[h]:mm:ss", 

184 47: "mm:ss.0", 

185 48: "##0.0E+0", 

186 49: "@", 

187 } 

188 

189 # Set the format code for built-in number formats. 

190 if num_fmt_id < 164: 

191 if num_fmt_id in format_codes: 

192 format_code = format_codes[num_fmt_id] 

193 else: 

194 format_code = "General" 

195 

196 attributes = [ 

197 ("numFmtId", num_fmt_id), 

198 ("formatCode", format_code), 

199 ] 

200 

201 self._xml_empty_tag("numFmt", attributes) 

202 

203 def _write_fonts(self): 

204 # Write the <fonts> element. 

205 if self.has_comments: 

206 # Add extra font for comments. 

207 attributes = [("count", self.font_count + 1)] 

208 else: 

209 attributes = [("count", self.font_count)] 

210 

211 self._xml_start_tag("fonts", attributes) 

212 

213 # Write the font elements for xf_format objects that have them. 

214 for xf_format in self.xf_formats: 

215 if xf_format.has_font: 

216 self._write_font(xf_format) 

217 

218 if self.has_comments: 

219 self._write_comment_font() 

220 

221 self._xml_end_tag("fonts") 

222 

223 def _write_font(self, xf_format, is_dxf_format=False): 

224 # Write the <font> element. 

225 self._xml_start_tag("font") 

226 

227 # The condense and extend elements are mainly used in dxf formats. 

228 if xf_format.font_condense: 

229 self._write_condense() 

230 

231 if xf_format.font_extend: 

232 self._write_extend() 

233 

234 if xf_format.bold: 

235 self._xml_empty_tag("b") 

236 

237 if xf_format.italic: 

238 self._xml_empty_tag("i") 

239 

240 if xf_format.font_strikeout: 

241 self._xml_empty_tag("strike") 

242 

243 if xf_format.font_outline: 

244 self._xml_empty_tag("outline") 

245 

246 if xf_format.font_shadow: 

247 self._xml_empty_tag("shadow") 

248 

249 # Handle the underline variants. 

250 if xf_format.underline: 

251 self._write_underline(xf_format.underline) 

252 

253 if xf_format.font_script == 1: 

254 self._write_vert_align("superscript") 

255 

256 if xf_format.font_script == 2: 

257 self._write_vert_align("subscript") 

258 

259 if not is_dxf_format: 

260 self._xml_empty_tag("sz", [("val", xf_format.font_size)]) 

261 

262 if xf_format.theme == -1: 

263 # Ignore for excel2003_style. 

264 pass 

265 elif xf_format.theme: 

266 self._write_color("theme", xf_format.theme) 

267 elif xf_format.color_indexed: 

268 self._write_color("indexed", xf_format.color_indexed) 

269 elif xf_format.font_color: 

270 color = self._get_palette_color(xf_format.font_color) 

271 if color != "Automatic": 

272 self._write_color("rgb", color) 

273 elif not is_dxf_format: 

274 self._write_color("theme", 1) 

275 

276 if not is_dxf_format: 

277 self._xml_empty_tag("name", [("val", xf_format.font_name)]) 

278 

279 if xf_format.font_family: 

280 self._xml_empty_tag("family", [("val", xf_format.font_family)]) 

281 

282 if xf_format.font_charset: 

283 self._xml_empty_tag("charset", [("val", xf_format.font_charset)]) 

284 

285 if xf_format.font_name == "Calibri" and not xf_format.hyperlink: 

286 self._xml_empty_tag("scheme", [("val", xf_format.font_scheme)]) 

287 

288 if xf_format.hyperlink: 

289 self.has_hyperlink = True 

290 if self.hyperlink_font_id == 0: 

291 self.hyperlink_font_id = xf_format.font_index 

292 

293 self._xml_end_tag("font") 

294 

295 def _write_comment_font(self): 

296 # Write the <font> element for comments. 

297 self._xml_start_tag("font") 

298 

299 self._xml_empty_tag("sz", [("val", 8)]) 

300 self._write_color("indexed", 81) 

301 self._xml_empty_tag("name", [("val", "Tahoma")]) 

302 self._xml_empty_tag("family", [("val", 2)]) 

303 

304 self._xml_end_tag("font") 

305 

306 def _write_underline(self, underline): 

307 # Write the underline font element. 

308 

309 if underline == 2: 

310 attributes = [("val", "double")] 

311 elif underline == 33: 

312 attributes = [("val", "singleAccounting")] 

313 elif underline == 34: 

314 attributes = [("val", "doubleAccounting")] 

315 else: 

316 # Default to single underline. 

317 attributes = [] 

318 

319 self._xml_empty_tag("u", attributes) 

320 

321 def _write_vert_align(self, val): 

322 # Write the <vertAlign> font sub-element. 

323 attributes = [("val", val)] 

324 

325 self._xml_empty_tag("vertAlign", attributes) 

326 

327 def _write_color(self, name, value): 

328 # Write the <color> element. 

329 attributes = [(name, value)] 

330 

331 self._xml_empty_tag("color", attributes) 

332 

333 def _write_fills(self): 

334 # Write the <fills> element. 

335 attributes = [("count", self.fill_count)] 

336 

337 self._xml_start_tag("fills", attributes) 

338 

339 # Write the default fill element. 

340 self._write_default_fill("none") 

341 self._write_default_fill("gray125") 

342 

343 # Write the fill elements for xf_format objects that have them. 

344 for xf_format in self.xf_formats: 

345 if xf_format.has_fill: 

346 self._write_fill(xf_format) 

347 

348 self._xml_end_tag("fills") 

349 

350 def _write_default_fill(self, pattern_type): 

351 # Write the <fill> element for the default fills. 

352 self._xml_start_tag("fill") 

353 self._xml_empty_tag("patternFill", [("patternType", pattern_type)]) 

354 self._xml_end_tag("fill") 

355 

356 def _write_fill(self, xf_format, is_dxf_format=False): 

357 # Write the <fill> element. 

358 pattern = xf_format.pattern 

359 bg_color = xf_format.bg_color 

360 fg_color = xf_format.fg_color 

361 

362 # Colors for dxf formats are handled differently from normal formats 

363 # since the normal xf_format reverses the meaning of BG and FG for 

364 # solid fills. 

365 if is_dxf_format: 

366 bg_color = xf_format.dxf_bg_color 

367 fg_color = xf_format.dxf_fg_color 

368 

369 patterns = ( 

370 "none", 

371 "solid", 

372 "mediumGray", 

373 "darkGray", 

374 "lightGray", 

375 "darkHorizontal", 

376 "darkVertical", 

377 "darkDown", 

378 "darkUp", 

379 "darkGrid", 

380 "darkTrellis", 

381 "lightHorizontal", 

382 "lightVertical", 

383 "lightDown", 

384 "lightUp", 

385 "lightGrid", 

386 "lightTrellis", 

387 "gray125", 

388 "gray0625", 

389 ) 

390 

391 # Special handling for pattern only case. 

392 if not fg_color and not bg_color and patterns[pattern]: 

393 self._write_default_fill(patterns[pattern]) 

394 return 

395 

396 self._xml_start_tag("fill") 

397 

398 # The "none" pattern is handled differently for dxf formats. 

399 if is_dxf_format and pattern <= 1: 

400 self._xml_start_tag("patternFill") 

401 else: 

402 self._xml_start_tag("patternFill", [("patternType", patterns[pattern])]) 

403 

404 if fg_color: 

405 fg_color = self._get_palette_color(fg_color) 

406 if fg_color != "Automatic": 

407 self._xml_empty_tag("fgColor", [("rgb", fg_color)]) 

408 

409 if bg_color: 

410 bg_color = self._get_palette_color(bg_color) 

411 if bg_color != "Automatic": 

412 self._xml_empty_tag("bgColor", [("rgb", bg_color)]) 

413 else: 

414 if not is_dxf_format and pattern <= 1: 

415 self._xml_empty_tag("bgColor", [("indexed", 64)]) 

416 

417 self._xml_end_tag("patternFill") 

418 self._xml_end_tag("fill") 

419 

420 def _write_borders(self): 

421 # Write the <borders> element. 

422 attributes = [("count", self.border_count)] 

423 

424 self._xml_start_tag("borders", attributes) 

425 

426 # Write the border elements for xf_format objects that have them. 

427 for xf_format in self.xf_formats: 

428 if xf_format.has_border: 

429 self._write_border(xf_format) 

430 

431 self._xml_end_tag("borders") 

432 

433 def _write_border(self, xf_format, is_dxf_format=False): 

434 # Write the <border> element. 

435 attributes = [] 

436 

437 # Diagonal borders add attributes to the <border> element. 

438 if xf_format.diag_type == 1: 

439 attributes.append(("diagonalUp", 1)) 

440 elif xf_format.diag_type == 2: 

441 attributes.append(("diagonalDown", 1)) 

442 elif xf_format.diag_type == 3: 

443 attributes.append(("diagonalUp", 1)) 

444 attributes.append(("diagonalDown", 1)) 

445 

446 # Ensure that a default diag border is set if the diag type is set. 

447 if xf_format.diag_type and not xf_format.diag_border: 

448 xf_format.diag_border = 1 

449 

450 # Write the start border tag. 

451 self._xml_start_tag("border", attributes) 

452 

453 # Write the <border> sub elements. 

454 self._write_sub_border("left", xf_format.left, xf_format.left_color) 

455 

456 self._write_sub_border("right", xf_format.right, xf_format.right_color) 

457 

458 self._write_sub_border("top", xf_format.top, xf_format.top_color) 

459 

460 self._write_sub_border("bottom", xf_format.bottom, xf_format.bottom_color) 

461 

462 # Condition DXF formats don't allow diagonal borders. 

463 if not is_dxf_format: 

464 self._write_sub_border( 

465 "diagonal", xf_format.diag_border, xf_format.diag_color 

466 ) 

467 

468 if is_dxf_format: 

469 self._write_sub_border("vertical", None, None) 

470 self._write_sub_border("horizontal", None, None) 

471 

472 self._xml_end_tag("border") 

473 

474 def _write_sub_border(self, border_type, style, color): 

475 # Write the <border> sub elements such as <right>, <top>, etc. 

476 attributes = [] 

477 

478 if not style: 

479 self._xml_empty_tag(border_type) 

480 return 

481 

482 border_styles = ( 

483 "none", 

484 "thin", 

485 "medium", 

486 "dashed", 

487 "dotted", 

488 "thick", 

489 "double", 

490 "hair", 

491 "mediumDashed", 

492 "dashDot", 

493 "mediumDashDot", 

494 "dashDotDot", 

495 "mediumDashDotDot", 

496 "slantDashDot", 

497 ) 

498 

499 attributes.append(("style", border_styles[style])) 

500 

501 self._xml_start_tag(border_type, attributes) 

502 

503 if color and color != "Automatic": 

504 color = self._get_palette_color(color) 

505 self._xml_empty_tag("color", [("rgb", color)]) 

506 else: 

507 self._xml_empty_tag("color", [("auto", 1)]) 

508 

509 self._xml_end_tag(border_type) 

510 

511 def _write_cell_style_xfs(self): 

512 # Write the <cellStyleXfs> element. 

513 count = 1 

514 

515 if self.has_hyperlink: 

516 count = 2 

517 

518 attributes = [("count", count)] 

519 

520 self._xml_start_tag("cellStyleXfs", attributes) 

521 self._write_style_xf() 

522 

523 if self.has_hyperlink: 

524 self._write_style_xf(True, self.hyperlink_font_id) 

525 

526 self._xml_end_tag("cellStyleXfs") 

527 

528 def _write_cell_xfs(self): 

529 # Write the <cellXfs> element. 

530 formats = self.xf_formats 

531 

532 # Workaround for when the last xf_format is used for the comment font 

533 # and shouldn't be used for cellXfs. 

534 last_format = formats[-1] 

535 if last_format.font_only: 

536 formats.pop() 

537 

538 attributes = [("count", len(formats))] 

539 self._xml_start_tag("cellXfs", attributes) 

540 

541 # Write the xf elements. 

542 for xf_format in formats: 

543 self._write_xf(xf_format) 

544 

545 self._xml_end_tag("cellXfs") 

546 

547 def _write_style_xf(self, has_hyperlink=False, font_id=0): 

548 # Write the style <xf> element. 

549 num_fmt_id = 0 

550 fill_id = 0 

551 border_id = 0 

552 

553 attributes = [ 

554 ("numFmtId", num_fmt_id), 

555 ("fontId", font_id), 

556 ("fillId", fill_id), 

557 ("borderId", border_id), 

558 ] 

559 

560 if has_hyperlink: 

561 attributes.append(("applyNumberFormat", 0)) 

562 attributes.append(("applyFill", 0)) 

563 attributes.append(("applyBorder", 0)) 

564 attributes.append(("applyAlignment", 0)) 

565 attributes.append(("applyProtection", 0)) 

566 

567 self._xml_start_tag("xf", attributes) 

568 self._xml_empty_tag("alignment", [("vertical", "top")]) 

569 self._xml_empty_tag("protection", [("locked", 0)]) 

570 self._xml_end_tag("xf") 

571 

572 else: 

573 self._xml_empty_tag("xf", attributes) 

574 

575 def _write_xf(self, xf_format): 

576 # Write the <xf> element. 

577 num_fmt_id = xf_format.num_format_index 

578 font_id = xf_format.font_index 

579 fill_id = xf_format.fill_index 

580 border_id = xf_format.border_index 

581 xf_id = xf_format.xf_id 

582 has_align = 0 

583 has_protect = 0 

584 

585 attributes = [ 

586 ("numFmtId", num_fmt_id), 

587 ("fontId", font_id), 

588 ("fillId", fill_id), 

589 ("borderId", border_id), 

590 ("xfId", xf_id), 

591 ] 

592 

593 if xf_format.quote_prefix: 

594 attributes.append(("quotePrefix", 1)) 

595 

596 if xf_format.num_format_index > 0: 

597 attributes.append(("applyNumberFormat", 1)) 

598 

599 # Add applyFont attribute if XF format uses a font element. 

600 if xf_format.font_index > 0 and not xf_format.hyperlink: 

601 attributes.append(("applyFont", 1)) 

602 

603 # Add applyFill attribute if XF format uses a fill element. 

604 if xf_format.fill_index > 0: 

605 attributes.append(("applyFill", 1)) 

606 

607 # Add applyBorder attribute if XF format uses a border element. 

608 if xf_format.border_index > 0: 

609 attributes.append(("applyBorder", 1)) 

610 

611 # Check if XF format has alignment properties set. 

612 (apply_align, align) = xf_format._get_align_properties() 

613 

614 # Check if an alignment sub-element should be written. 

615 if apply_align and align: 

616 has_align = 1 

617 

618 # We can also have applyAlignment without a sub-element. 

619 if apply_align or xf_format.hyperlink: 

620 attributes.append(("applyAlignment", 1)) 

621 

622 # Check for cell protection properties. 

623 protection = xf_format._get_protection_properties() 

624 

625 if protection or xf_format.hyperlink: 

626 attributes.append(("applyProtection", 1)) 

627 

628 if not xf_format.hyperlink: 

629 has_protect = 1 

630 

631 # Write XF with sub-elements if required. 

632 if has_align or has_protect: 

633 self._xml_start_tag("xf", attributes) 

634 if has_align: 

635 self._xml_empty_tag("alignment", align) 

636 if has_protect: 

637 self._xml_empty_tag("protection", protection) 

638 self._xml_end_tag("xf") 

639 else: 

640 self._xml_empty_tag("xf", attributes) 

641 

642 def _write_cell_styles(self): 

643 # Write the <cellStyles> element. 

644 count = 1 

645 

646 if self.has_hyperlink: 

647 count = 2 

648 

649 attributes = [("count", count)] 

650 

651 self._xml_start_tag("cellStyles", attributes) 

652 

653 if self.has_hyperlink: 

654 self._write_cell_style("Hyperlink", 1, 8) 

655 

656 self._write_cell_style() 

657 

658 self._xml_end_tag("cellStyles") 

659 

660 def _write_cell_style(self, name="Normal", xf_id=0, builtin_id=0): 

661 # Write the <cellStyle> element. 

662 attributes = [ 

663 ("name", name), 

664 ("xfId", xf_id), 

665 ("builtinId", builtin_id), 

666 ] 

667 

668 self._xml_empty_tag("cellStyle", attributes) 

669 

670 def _write_dxfs(self): 

671 # Write the <dxfs> element. 

672 formats = self.dxf_formats 

673 count = len(formats) 

674 

675 attributes = [("count", len(formats))] 

676 

677 if count: 

678 self._xml_start_tag("dxfs", attributes) 

679 

680 # Write the font elements for xf_format objects that have them. 

681 for xf_format in self.dxf_formats: 

682 self._xml_start_tag("dxf") 

683 if xf_format.has_dxf_font: 

684 self._write_font(xf_format, True) 

685 

686 if xf_format.num_format_index: 

687 self._write_num_fmt( 

688 xf_format.num_format_index, xf_format.num_format 

689 ) 

690 

691 if xf_format.has_dxf_fill: 

692 self._write_fill(xf_format, True) 

693 if xf_format.has_dxf_border: 

694 self._write_border(xf_format, True) 

695 self._xml_end_tag("dxf") 

696 

697 self._xml_end_tag("dxfs") 

698 else: 

699 self._xml_empty_tag("dxfs", attributes) 

700 

701 def _write_table_styles(self): 

702 # Write the <tableStyles> element. 

703 count = 0 

704 default_table_style = "TableStyleMedium9" 

705 default_pivot_style = "PivotStyleLight16" 

706 

707 attributes = [ 

708 ("count", count), 

709 ("defaultTableStyle", default_table_style), 

710 ("defaultPivotStyle", default_pivot_style), 

711 ] 

712 

713 self._xml_empty_tag("tableStyles", attributes) 

714 

715 def _write_colors(self): 

716 # Write the <colors> element. 

717 custom_colors = self.custom_colors 

718 

719 if not custom_colors: 

720 return 

721 

722 self._xml_start_tag("colors") 

723 self._write_mru_colors(custom_colors) 

724 self._xml_end_tag("colors") 

725 

726 def _write_mru_colors(self, custom_colors): 

727 # Write the <mruColors> element for the most recently used colors. 

728 

729 # Write the custom custom_colors in reverse order. 

730 custom_colors.reverse() 

731 

732 # Limit the mruColors to the last 10. 

733 if len(custom_colors) > 10: 

734 custom_colors = custom_colors[0:10] 

735 

736 self._xml_start_tag("mruColors") 

737 

738 # Write the custom custom_colors in reverse order. 

739 for color in custom_colors: 

740 self._write_color("rgb", color) 

741 

742 self._xml_end_tag("mruColors") 

743 

744 def _write_condense(self): 

745 # Write the <condense> element. 

746 attributes = [("val", 0)] 

747 

748 self._xml_empty_tag("condense", attributes) 

749 

750 def _write_extend(self): 

751 # Write the <extend> element. 

752 attributes = [("val", 0)] 

753 

754 self._xml_empty_tag("extend", attributes)