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
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
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#
9# Package imports.
10from . import xmlwriter
13class Styles(xmlwriter.XMLwriter):
14 """
15 A class for writing the Excel XLSX Styles file.
18 """
20 ###########################################################################
21 #
22 # Public API.
23 #
24 ###########################################################################
26 def __init__(self):
27 """
28 Constructor.
30 """
32 super(Styles, self).__init__()
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
46 ###########################################################################
47 #
48 # Private API.
49 #
50 ###########################################################################
52 def _assemble_xml_file(self):
53 # Assemble and write the XML file.
55 # Write the XML declaration.
56 self._xml_declaration()
58 # Add the style sheet.
59 self._write_style_sheet()
61 # Write the number formats.
62 self._write_num_fmts()
64 # Write the fonts.
65 self._write_fonts()
67 # Write the fills.
68 self._write_fills()
70 # Write the borders element.
71 self._write_borders()
73 # Write the cellStyleXfs element.
74 self._write_cell_style_xfs()
76 # Write the cellXfs element.
77 self._write_cell_xfs()
79 # Write the cellStyles element.
80 self._write_cell_styles()
82 # Write the dxfs element.
83 self._write_dxfs()
85 # Write the tableStyles element.
86 self._write_table_styles()
88 # Write the colors element.
89 self._write_colors()
91 # Close the style sheet tag.
92 self._xml_end_tag("styleSheet")
94 # Close the file.
95 self._xml_close()
97 def _set_style_properties(self, properties):
98 # Pass in the Format objects and other properties used in the styles.
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]
110 def _get_palette_color(self, color):
111 # Special handling for automatic color.
112 if color == "Automatic":
113 return color
115 # Convert the RGB color.
116 if color[0] == "#":
117 color = color[1:]
119 return "FF" + color.upper()
121 ###########################################################################
122 #
123 # XML methods.
124 #
125 ###########################################################################
127 def _write_style_sheet(self):
128 # Write the <styleSheet> element.
129 xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
131 attributes = [("xmlns", xmlns)]
132 self._xml_start_tag("styleSheet", attributes)
134 def _write_num_fmts(self):
135 # Write the <numFmts> element.
136 if not self.num_formats:
137 return
139 attributes = [("count", len(self.num_formats))]
140 self._xml_start_tag("numFmts", attributes)
142 # Write the numFmts elements.
143 for index, num_format in enumerate(self.num_formats, 164):
144 self._write_num_fmt(index, num_format)
146 self._xml_end_tag("numFmts")
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 }
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"
196 attributes = [
197 ("numFmtId", num_fmt_id),
198 ("formatCode", format_code),
199 ]
201 self._xml_empty_tag("numFmt", attributes)
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)]
211 self._xml_start_tag("fonts", attributes)
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)
218 if self.has_comments:
219 self._write_comment_font()
221 self._xml_end_tag("fonts")
223 def _write_font(self, xf_format, is_dxf_format=False):
224 # Write the <font> element.
225 self._xml_start_tag("font")
227 # The condense and extend elements are mainly used in dxf formats.
228 if xf_format.font_condense:
229 self._write_condense()
231 if xf_format.font_extend:
232 self._write_extend()
234 if xf_format.bold:
235 self._xml_empty_tag("b")
237 if xf_format.italic:
238 self._xml_empty_tag("i")
240 if xf_format.font_strikeout:
241 self._xml_empty_tag("strike")
243 if xf_format.font_outline:
244 self._xml_empty_tag("outline")
246 if xf_format.font_shadow:
247 self._xml_empty_tag("shadow")
249 # Handle the underline variants.
250 if xf_format.underline:
251 self._write_underline(xf_format.underline)
253 if xf_format.font_script == 1:
254 self._write_vert_align("superscript")
256 if xf_format.font_script == 2:
257 self._write_vert_align("subscript")
259 if not is_dxf_format:
260 self._xml_empty_tag("sz", [("val", xf_format.font_size)])
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)
276 if not is_dxf_format:
277 self._xml_empty_tag("name", [("val", xf_format.font_name)])
279 if xf_format.font_family:
280 self._xml_empty_tag("family", [("val", xf_format.font_family)])
282 if xf_format.font_charset:
283 self._xml_empty_tag("charset", [("val", xf_format.font_charset)])
285 if xf_format.font_name == "Calibri" and not xf_format.hyperlink:
286 self._xml_empty_tag("scheme", [("val", xf_format.font_scheme)])
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
293 self._xml_end_tag("font")
295 def _write_comment_font(self):
296 # Write the <font> element for comments.
297 self._xml_start_tag("font")
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)])
304 self._xml_end_tag("font")
306 def _write_underline(self, underline):
307 # Write the underline font element.
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 = []
319 self._xml_empty_tag("u", attributes)
321 def _write_vert_align(self, val):
322 # Write the <vertAlign> font sub-element.
323 attributes = [("val", val)]
325 self._xml_empty_tag("vertAlign", attributes)
327 def _write_color(self, name, value):
328 # Write the <color> element.
329 attributes = [(name, value)]
331 self._xml_empty_tag("color", attributes)
333 def _write_fills(self):
334 # Write the <fills> element.
335 attributes = [("count", self.fill_count)]
337 self._xml_start_tag("fills", attributes)
339 # Write the default fill element.
340 self._write_default_fill("none")
341 self._write_default_fill("gray125")
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)
348 self._xml_end_tag("fills")
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")
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
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
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 )
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
396 self._xml_start_tag("fill")
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])])
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)])
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)])
417 self._xml_end_tag("patternFill")
418 self._xml_end_tag("fill")
420 def _write_borders(self):
421 # Write the <borders> element.
422 attributes = [("count", self.border_count)]
424 self._xml_start_tag("borders", attributes)
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)
431 self._xml_end_tag("borders")
433 def _write_border(self, xf_format, is_dxf_format=False):
434 # Write the <border> element.
435 attributes = []
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))
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
450 # Write the start border tag.
451 self._xml_start_tag("border", attributes)
453 # Write the <border> sub elements.
454 self._write_sub_border("left", xf_format.left, xf_format.left_color)
456 self._write_sub_border("right", xf_format.right, xf_format.right_color)
458 self._write_sub_border("top", xf_format.top, xf_format.top_color)
460 self._write_sub_border("bottom", xf_format.bottom, xf_format.bottom_color)
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 )
468 if is_dxf_format:
469 self._write_sub_border("vertical", None, None)
470 self._write_sub_border("horizontal", None, None)
472 self._xml_end_tag("border")
474 def _write_sub_border(self, border_type, style, color):
475 # Write the <border> sub elements such as <right>, <top>, etc.
476 attributes = []
478 if not style:
479 self._xml_empty_tag(border_type)
480 return
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 )
499 attributes.append(("style", border_styles[style]))
501 self._xml_start_tag(border_type, attributes)
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)])
509 self._xml_end_tag(border_type)
511 def _write_cell_style_xfs(self):
512 # Write the <cellStyleXfs> element.
513 count = 1
515 if self.has_hyperlink:
516 count = 2
518 attributes = [("count", count)]
520 self._xml_start_tag("cellStyleXfs", attributes)
521 self._write_style_xf()
523 if self.has_hyperlink:
524 self._write_style_xf(True, self.hyperlink_font_id)
526 self._xml_end_tag("cellStyleXfs")
528 def _write_cell_xfs(self):
529 # Write the <cellXfs> element.
530 formats = self.xf_formats
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()
538 attributes = [("count", len(formats))]
539 self._xml_start_tag("cellXfs", attributes)
541 # Write the xf elements.
542 for xf_format in formats:
543 self._write_xf(xf_format)
545 self._xml_end_tag("cellXfs")
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
553 attributes = [
554 ("numFmtId", num_fmt_id),
555 ("fontId", font_id),
556 ("fillId", fill_id),
557 ("borderId", border_id),
558 ]
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))
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")
572 else:
573 self._xml_empty_tag("xf", attributes)
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
585 attributes = [
586 ("numFmtId", num_fmt_id),
587 ("fontId", font_id),
588 ("fillId", fill_id),
589 ("borderId", border_id),
590 ("xfId", xf_id),
591 ]
593 if xf_format.quote_prefix:
594 attributes.append(("quotePrefix", 1))
596 if xf_format.num_format_index > 0:
597 attributes.append(("applyNumberFormat", 1))
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))
603 # Add applyFill attribute if XF format uses a fill element.
604 if xf_format.fill_index > 0:
605 attributes.append(("applyFill", 1))
607 # Add applyBorder attribute if XF format uses a border element.
608 if xf_format.border_index > 0:
609 attributes.append(("applyBorder", 1))
611 # Check if XF format has alignment properties set.
612 (apply_align, align) = xf_format._get_align_properties()
614 # Check if an alignment sub-element should be written.
615 if apply_align and align:
616 has_align = 1
618 # We can also have applyAlignment without a sub-element.
619 if apply_align or xf_format.hyperlink:
620 attributes.append(("applyAlignment", 1))
622 # Check for cell protection properties.
623 protection = xf_format._get_protection_properties()
625 if protection or xf_format.hyperlink:
626 attributes.append(("applyProtection", 1))
628 if not xf_format.hyperlink:
629 has_protect = 1
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)
642 def _write_cell_styles(self):
643 # Write the <cellStyles> element.
644 count = 1
646 if self.has_hyperlink:
647 count = 2
649 attributes = [("count", count)]
651 self._xml_start_tag("cellStyles", attributes)
653 if self.has_hyperlink:
654 self._write_cell_style("Hyperlink", 1, 8)
656 self._write_cell_style()
658 self._xml_end_tag("cellStyles")
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 ]
668 self._xml_empty_tag("cellStyle", attributes)
670 def _write_dxfs(self):
671 # Write the <dxfs> element.
672 formats = self.dxf_formats
673 count = len(formats)
675 attributes = [("count", len(formats))]
677 if count:
678 self._xml_start_tag("dxfs", attributes)
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)
686 if xf_format.num_format_index:
687 self._write_num_fmt(
688 xf_format.num_format_index, xf_format.num_format
689 )
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")
697 self._xml_end_tag("dxfs")
698 else:
699 self._xml_empty_tag("dxfs", attributes)
701 def _write_table_styles(self):
702 # Write the <tableStyles> element.
703 count = 0
704 default_table_style = "TableStyleMedium9"
705 default_pivot_style = "PivotStyleLight16"
707 attributes = [
708 ("count", count),
709 ("defaultTableStyle", default_table_style),
710 ("defaultPivotStyle", default_pivot_style),
711 ]
713 self._xml_empty_tag("tableStyles", attributes)
715 def _write_colors(self):
716 # Write the <colors> element.
717 custom_colors = self.custom_colors
719 if not custom_colors:
720 return
722 self._xml_start_tag("colors")
723 self._write_mru_colors(custom_colors)
724 self._xml_end_tag("colors")
726 def _write_mru_colors(self, custom_colors):
727 # Write the <mruColors> element for the most recently used colors.
729 # Write the custom custom_colors in reverse order.
730 custom_colors.reverse()
732 # Limit the mruColors to the last 10.
733 if len(custom_colors) > 10:
734 custom_colors = custom_colors[0:10]
736 self._xml_start_tag("mruColors")
738 # Write the custom custom_colors in reverse order.
739 for color in custom_colors:
740 self._write_color("rgb", color)
742 self._xml_end_tag("mruColors")
744 def _write_condense(self):
745 # Write the <condense> element.
746 attributes = [("val", 0)]
748 self._xml_empty_tag("condense", attributes)
750 def _write_extend(self):
751 # Write the <extend> element.
752 attributes = [("val", 0)]
754 self._xml_empty_tag("extend", attributes)