diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/xlsxwriter/styles.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/xlsxwriter/styles.py | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/xlsxwriter/styles.py b/.venv/lib/python3.12/site-packages/xlsxwriter/styles.py new file mode 100644 index 00000000..a14035a9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/xlsxwriter/styles.py @@ -0,0 +1,803 @@ +############################################################################### +# +# Styles - A class for writing the Excel XLSX Worksheet file. +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org +# + +# Package imports. +from . import xmlwriter + + +class Styles(xmlwriter.XMLwriter): + """ + A class for writing the Excel XLSX Styles file. + + + """ + + ########################################################################### + # + # Public API. + # + ########################################################################### + + def __init__(self): + """ + Constructor. + + """ + + super().__init__() + + self.xf_formats = [] + self.palette = [] + self.font_count = 0 + self.num_formats = [] + self.border_count = 0 + self.fill_count = 0 + self.custom_colors = [] + self.dxf_formats = [] + self.has_hyperlink = False + self.hyperlink_font_id = 0 + self.has_comments = False + + ########################################################################### + # + # Private API. + # + ########################################################################### + + def _assemble_xml_file(self): + # Assemble and write the XML file. + + # Write the XML declaration. + self._xml_declaration() + + # Add the style sheet. + self._write_style_sheet() + + # Write the number formats. + self._write_num_fmts() + + # Write the fonts. + self._write_fonts() + + # Write the fills. + self._write_fills() + + # Write the borders element. + self._write_borders() + + # Write the cellStyleXfs element. + self._write_cell_style_xfs() + + # Write the cellXfs element. + self._write_cell_xfs() + + # Write the cellStyles element. + self._write_cell_styles() + + # Write the dxfs element. + self._write_dxfs() + + # Write the tableStyles element. + self._write_table_styles() + + # Write the colors element. + self._write_colors() + + # Close the style sheet tag. + self._xml_end_tag("styleSheet") + + # Close the file. + self._xml_close() + + def _set_style_properties(self, properties): + # Pass in the Format objects and other properties used in the styles. + + self.xf_formats = properties[0] + self.palette = properties[1] + self.font_count = properties[2] + self.num_formats = properties[3] + self.border_count = properties[4] + self.fill_count = properties[5] + self.custom_colors = properties[6] + self.dxf_formats = properties[7] + self.has_comments = properties[8] + + def _get_palette_color(self, color): + # Special handling for automatic color. + if color == "Automatic": + return color + + # Convert the RGB color. + if color[0] == "#": + color = color[1:] + + return "FF" + color.upper() + + ########################################################################### + # + # XML methods. + # + ########################################################################### + + def _write_style_sheet(self): + # Write the <styleSheet> element. + xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" + + attributes = [("xmlns", xmlns)] + self._xml_start_tag("styleSheet", attributes) + + def _write_num_fmts(self): + # Write the <numFmts> element. + if not self.num_formats: + return + + attributes = [("count", len(self.num_formats))] + self._xml_start_tag("numFmts", attributes) + + # Write the numFmts elements. + for index, num_format in enumerate(self.num_formats, 164): + self._write_num_fmt(index, num_format) + + self._xml_end_tag("numFmts") + + def _write_num_fmt(self, num_fmt_id, format_code): + # Write the <numFmt> element. + format_codes = { + 0: "General", + 1: "0", + 2: "0.00", + 3: "#,##0", + 4: "#,##0.00", + 5: "($#,##0_);($#,##0)", + 6: "($#,##0_);[Red]($#,##0)", + 7: "($#,##0.00_);($#,##0.00)", + 8: "($#,##0.00_);[Red]($#,##0.00)", + 9: "0%", + 10: "0.00%", + 11: "0.00E+00", + 12: "# ?/?", + 13: "# ??/??", + 14: "m/d/yy", + 15: "d-mmm-yy", + 16: "d-mmm", + 17: "mmm-yy", + 18: "h:mm AM/PM", + 19: "h:mm:ss AM/PM", + 20: "h:mm", + 21: "h:mm:ss", + 22: "m/d/yy h:mm", + 37: "(#,##0_);(#,##0)", + 38: "(#,##0_);[Red](#,##0)", + 39: "(#,##0.00_);(#,##0.00)", + 40: "(#,##0.00_);[Red](#,##0.00)", + 41: '_(* #,##0_);_(* (#,##0);_(* "-"_);_(_)', + 42: '_($* #,##0_);_($* (#,##0);_($* "-"_);_(_)', + 43: '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(_)', + 44: '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(_)', + 45: "mm:ss", + 46: "[h]:mm:ss", + 47: "mm:ss.0", + 48: "##0.0E+0", + 49: "@", + } + + # Set the format code for built-in number formats. + if num_fmt_id < 164: + format_code = format_codes.get(num_fmt_id, "General") + + attributes = [ + ("numFmtId", num_fmt_id), + ("formatCode", format_code), + ] + + self._xml_empty_tag("numFmt", attributes) + + def _write_fonts(self): + # Write the <fonts> element. + if self.has_comments: + # Add extra font for comments. + attributes = [("count", self.font_count + 1)] + else: + attributes = [("count", self.font_count)] + + self._xml_start_tag("fonts", attributes) + + # Write the font elements for xf_format objects that have them. + for xf_format in self.xf_formats: + if xf_format.has_font: + self._write_font(xf_format) + + if self.has_comments: + self._write_comment_font() + + self._xml_end_tag("fonts") + + def _write_font(self, xf_format, is_dxf_format=False): + # Write the <font> element. + self._xml_start_tag("font") + + # The condense and extend elements are mainly used in dxf formats. + if xf_format.font_condense: + self._write_condense() + + if xf_format.font_extend: + self._write_extend() + + if xf_format.bold: + self._xml_empty_tag("b") + + if xf_format.italic: + self._xml_empty_tag("i") + + if xf_format.font_strikeout: + self._xml_empty_tag("strike") + + if xf_format.font_outline: + self._xml_empty_tag("outline") + + if xf_format.font_shadow: + self._xml_empty_tag("shadow") + + # Handle the underline variants. + if xf_format.underline: + self._write_underline(xf_format.underline) + + if xf_format.font_script == 1: + self._write_vert_align("superscript") + + if xf_format.font_script == 2: + self._write_vert_align("subscript") + + if not is_dxf_format: + self._xml_empty_tag("sz", [("val", xf_format.font_size)]) + + if xf_format.theme == -1: + # Ignore for excel2003_style. + pass + elif xf_format.theme: + self._write_color("theme", xf_format.theme) + elif xf_format.color_indexed: + self._write_color("indexed", xf_format.color_indexed) + elif xf_format.font_color: + color = self._get_palette_color(xf_format.font_color) + if color != "Automatic": + self._write_color("rgb", color) + elif not is_dxf_format: + self._write_color("theme", 1) + + if not is_dxf_format: + self._xml_empty_tag("name", [("val", xf_format.font_name)]) + + if xf_format.font_family: + self._xml_empty_tag("family", [("val", xf_format.font_family)]) + + if xf_format.font_charset: + self._xml_empty_tag("charset", [("val", xf_format.font_charset)]) + + if xf_format.font_name == "Calibri" and not xf_format.hyperlink: + self._xml_empty_tag("scheme", [("val", xf_format.font_scheme)]) + + if xf_format.hyperlink: + self.has_hyperlink = True + if self.hyperlink_font_id == 0: + self.hyperlink_font_id = xf_format.font_index + + self._xml_end_tag("font") + + def _write_comment_font(self): + # Write the <font> element for comments. + self._xml_start_tag("font") + + self._xml_empty_tag("sz", [("val", 8)]) + self._write_color("indexed", 81) + self._xml_empty_tag("name", [("val", "Tahoma")]) + self._xml_empty_tag("family", [("val", 2)]) + + self._xml_end_tag("font") + + def _write_underline(self, underline): + # Write the underline font element. + + if underline == 2: + attributes = [("val", "double")] + elif underline == 33: + attributes = [("val", "singleAccounting")] + elif underline == 34: + attributes = [("val", "doubleAccounting")] + else: + # Default to single underline. + attributes = [] + + self._xml_empty_tag("u", attributes) + + def _write_vert_align(self, val): + # Write the <vertAlign> font sub-element. + attributes = [("val", val)] + + self._xml_empty_tag("vertAlign", attributes) + + def _write_color(self, name, value): + # Write the <color> element. + attributes = [(name, value)] + + self._xml_empty_tag("color", attributes) + + def _write_fills(self): + # Write the <fills> element. + attributes = [("count", self.fill_count)] + + self._xml_start_tag("fills", attributes) + + # Write the default fill element. + self._write_default_fill("none") + self._write_default_fill("gray125") + + # Write the fill elements for xf_format objects that have them. + for xf_format in self.xf_formats: + if xf_format.has_fill: + self._write_fill(xf_format) + + self._xml_end_tag("fills") + + def _write_default_fill(self, pattern_type): + # Write the <fill> element for the default fills. + self._xml_start_tag("fill") + self._xml_empty_tag("patternFill", [("patternType", pattern_type)]) + self._xml_end_tag("fill") + + def _write_fill(self, xf_format, is_dxf_format=False): + # Write the <fill> element. + pattern = xf_format.pattern + bg_color = xf_format.bg_color + fg_color = xf_format.fg_color + + # Colors for dxf formats are handled differently from normal formats + # since the normal xf_format reverses the meaning of BG and FG for + # solid fills. + if is_dxf_format: + bg_color = xf_format.dxf_bg_color + fg_color = xf_format.dxf_fg_color + + patterns = ( + "none", + "solid", + "mediumGray", + "darkGray", + "lightGray", + "darkHorizontal", + "darkVertical", + "darkDown", + "darkUp", + "darkGrid", + "darkTrellis", + "lightHorizontal", + "lightVertical", + "lightDown", + "lightUp", + "lightGrid", + "lightTrellis", + "gray125", + "gray0625", + ) + + # Special handling for pattern only case. + if not fg_color and not bg_color and patterns[pattern]: + self._write_default_fill(patterns[pattern]) + return + + self._xml_start_tag("fill") + + # The "none" pattern is handled differently for dxf formats. + if is_dxf_format and pattern <= 1: + self._xml_start_tag("patternFill") + else: + self._xml_start_tag("patternFill", [("patternType", patterns[pattern])]) + + if fg_color: + fg_color = self._get_palette_color(fg_color) + if fg_color != "Automatic": + self._xml_empty_tag("fgColor", [("rgb", fg_color)]) + + if bg_color: + bg_color = self._get_palette_color(bg_color) + if bg_color != "Automatic": + self._xml_empty_tag("bgColor", [("rgb", bg_color)]) + else: + if not is_dxf_format and pattern <= 1: + self._xml_empty_tag("bgColor", [("indexed", 64)]) + + self._xml_end_tag("patternFill") + self._xml_end_tag("fill") + + def _write_borders(self): + # Write the <borders> element. + attributes = [("count", self.border_count)] + + self._xml_start_tag("borders", attributes) + + # Write the border elements for xf_format objects that have them. + for xf_format in self.xf_formats: + if xf_format.has_border: + self._write_border(xf_format) + + self._xml_end_tag("borders") + + def _write_border(self, xf_format, is_dxf_format=False): + # Write the <border> element. + attributes = [] + + # Diagonal borders add attributes to the <border> element. + if xf_format.diag_type == 1: + attributes.append(("diagonalUp", 1)) + elif xf_format.diag_type == 2: + attributes.append(("diagonalDown", 1)) + elif xf_format.diag_type == 3: + attributes.append(("diagonalUp", 1)) + attributes.append(("diagonalDown", 1)) + + # Ensure that a default diag border is set if the diag type is set. + if xf_format.diag_type and not xf_format.diag_border: + xf_format.diag_border = 1 + + # Write the start border tag. + self._xml_start_tag("border", attributes) + + # Write the <border> sub elements. + self._write_sub_border("left", xf_format.left, xf_format.left_color) + + self._write_sub_border("right", xf_format.right, xf_format.right_color) + + self._write_sub_border("top", xf_format.top, xf_format.top_color) + + self._write_sub_border("bottom", xf_format.bottom, xf_format.bottom_color) + + # Condition DXF formats don't allow diagonal borders. + if not is_dxf_format: + self._write_sub_border( + "diagonal", xf_format.diag_border, xf_format.diag_color + ) + + if is_dxf_format: + self._write_sub_border("vertical", None, None) + self._write_sub_border("horizontal", None, None) + + self._xml_end_tag("border") + + def _write_sub_border(self, border_type, style, color): + # Write the <border> sub elements such as <right>, <top>, etc. + attributes = [] + + if not style: + self._xml_empty_tag(border_type) + return + + border_styles = ( + "none", + "thin", + "medium", + "dashed", + "dotted", + "thick", + "double", + "hair", + "mediumDashed", + "dashDot", + "mediumDashDot", + "dashDotDot", + "mediumDashDotDot", + "slantDashDot", + ) + + attributes.append(("style", border_styles[style])) + + self._xml_start_tag(border_type, attributes) + + if color and color != "Automatic": + color = self._get_palette_color(color) + self._xml_empty_tag("color", [("rgb", color)]) + else: + self._xml_empty_tag("color", [("auto", 1)]) + + self._xml_end_tag(border_type) + + def _write_cell_style_xfs(self): + # Write the <cellStyleXfs> element. + count = 1 + + if self.has_hyperlink: + count = 2 + + attributes = [("count", count)] + + self._xml_start_tag("cellStyleXfs", attributes) + self._write_style_xf() + + if self.has_hyperlink: + self._write_style_xf(True, self.hyperlink_font_id) + + self._xml_end_tag("cellStyleXfs") + + def _write_cell_xfs(self): + # Write the <cellXfs> element. + formats = self.xf_formats + + # Workaround for when the last xf_format is used for the comment font + # and shouldn't be used for cellXfs. + last_format = formats[-1] + if last_format.font_only: + formats.pop() + + attributes = [("count", len(formats))] + self._xml_start_tag("cellXfs", attributes) + + # Write the xf elements. + for xf_format in formats: + self._write_xf(xf_format) + + self._xml_end_tag("cellXfs") + + def _write_style_xf(self, has_hyperlink=False, font_id=0): + # Write the style <xf> element. + num_fmt_id = 0 + fill_id = 0 + border_id = 0 + + attributes = [ + ("numFmtId", num_fmt_id), + ("fontId", font_id), + ("fillId", fill_id), + ("borderId", border_id), + ] + + if has_hyperlink: + attributes.append(("applyNumberFormat", 0)) + attributes.append(("applyFill", 0)) + attributes.append(("applyBorder", 0)) + attributes.append(("applyAlignment", 0)) + attributes.append(("applyProtection", 0)) + + self._xml_start_tag("xf", attributes) + self._xml_empty_tag("alignment", [("vertical", "top")]) + self._xml_empty_tag("protection", [("locked", 0)]) + self._xml_end_tag("xf") + + else: + self._xml_empty_tag("xf", attributes) + + def _write_xf(self, xf_format): + # Write the <xf> element. + xf_id = xf_format.xf_id + font_id = xf_format.font_index + fill_id = xf_format.fill_index + border_id = xf_format.border_index + num_fmt_id = xf_format.num_format_index + + has_checkbox = xf_format.checkbox + has_alignment = False + has_protection = False + + attributes = [ + ("numFmtId", num_fmt_id), + ("fontId", font_id), + ("fillId", fill_id), + ("borderId", border_id), + ("xfId", xf_id), + ] + + if xf_format.quote_prefix: + attributes.append(("quotePrefix", 1)) + + if xf_format.num_format_index > 0: + attributes.append(("applyNumberFormat", 1)) + + # Add applyFont attribute if XF format uses a font element. + if xf_format.font_index > 0 and not xf_format.hyperlink: + attributes.append(("applyFont", 1)) + + # Add applyFill attribute if XF format uses a fill element. + if xf_format.fill_index > 0: + attributes.append(("applyFill", 1)) + + # Add applyBorder attribute if XF format uses a border element. + if xf_format.border_index > 0: + attributes.append(("applyBorder", 1)) + + # Check if XF format has alignment properties set. + (apply_align, align) = xf_format._get_align_properties() + + # Check if an alignment sub-element should be written. + if apply_align and align: + has_alignment = True + + # We can also have applyAlignment without a sub-element. + if apply_align or xf_format.hyperlink: + attributes.append(("applyAlignment", 1)) + + # Check for cell protection properties. + protection = xf_format._get_protection_properties() + + if protection or xf_format.hyperlink: + attributes.append(("applyProtection", 1)) + + if not xf_format.hyperlink: + has_protection = True + + # Write XF with sub-elements if required. + if has_alignment or has_protection or has_checkbox: + self._xml_start_tag("xf", attributes) + + if has_alignment: + self._xml_empty_tag("alignment", align) + + if has_protection: + self._xml_empty_tag("protection", protection) + + if has_checkbox: + self._write_xf_format_extensions() + + self._xml_end_tag("xf") + else: + self._xml_empty_tag("xf", attributes) + + def _write_cell_styles(self): + # Write the <cellStyles> element. + count = 1 + + if self.has_hyperlink: + count = 2 + + attributes = [("count", count)] + + self._xml_start_tag("cellStyles", attributes) + + if self.has_hyperlink: + self._write_cell_style("Hyperlink", 1, 8) + + self._write_cell_style() + + self._xml_end_tag("cellStyles") + + def _write_cell_style(self, name="Normal", xf_id=0, builtin_id=0): + # Write the <cellStyle> element. + attributes = [ + ("name", name), + ("xfId", xf_id), + ("builtinId", builtin_id), + ] + + self._xml_empty_tag("cellStyle", attributes) + + def _write_dxfs(self): + # Write the <dxfs> element. + formats = self.dxf_formats + count = len(formats) + + attributes = [("count", len(formats))] + + if count: + self._xml_start_tag("dxfs", attributes) + + # Write the font elements for xf_format objects that have them. + for dxf_format in self.dxf_formats: + self._xml_start_tag("dxf") + if dxf_format.has_dxf_font: + self._write_font(dxf_format, True) + + if dxf_format.num_format_index: + self._write_num_fmt( + dxf_format.num_format_index, dxf_format.num_format + ) + + if dxf_format.has_dxf_fill: + self._write_fill(dxf_format, True) + + if dxf_format.has_dxf_border: + self._write_border(dxf_format, True) + + if dxf_format.checkbox: + self._write_dxf_format_extensions() + + self._xml_end_tag("dxf") + + self._xml_end_tag("dxfs") + else: + self._xml_empty_tag("dxfs", attributes) + + def _write_table_styles(self): + # Write the <tableStyles> element. + count = 0 + default_table_style = "TableStyleMedium9" + default_pivot_style = "PivotStyleLight16" + + attributes = [ + ("count", count), + ("defaultTableStyle", default_table_style), + ("defaultPivotStyle", default_pivot_style), + ] + + self._xml_empty_tag("tableStyles", attributes) + + def _write_colors(self): + # Write the <colors> element. + custom_colors = self.custom_colors + + if not custom_colors: + return + + self._xml_start_tag("colors") + self._write_mru_colors(custom_colors) + self._xml_end_tag("colors") + + def _write_mru_colors(self, custom_colors): + # Write the <mruColors> element for the most recently used colors. + + # Write the custom custom_colors in reverse order. + custom_colors.reverse() + + # Limit the mruColors to the last 10. + if len(custom_colors) > 10: + custom_colors = custom_colors[0:10] + + self._xml_start_tag("mruColors") + + # Write the custom custom_colors in reverse order. + for color in custom_colors: + self._write_color("rgb", color) + + self._xml_end_tag("mruColors") + + def _write_condense(self): + # Write the <condense> element. + attributes = [("val", 0)] + + self._xml_empty_tag("condense", attributes) + + def _write_extend(self): + # Write the <extend> element. + attributes = [("val", 0)] + + self._xml_empty_tag("extend", attributes) + + def _write_xf_format_extensions(self): + # Write the xfComplement <extLst> elements. + schema = "http://schemas.microsoft.com/office/spreadsheetml" + attributes = [ + ("uri", "{C7286773-470A-42A8-94C5-96B5CB345126}"), + ( + "xmlns:xfpb", + schema + "/2022/featurepropertybag", + ), + ] + + self._xml_start_tag("extLst") + self._xml_start_tag("ext", attributes) + + self._xml_empty_tag("xfpb:xfComplement", [("i", "0")]) + + self._xml_end_tag("ext") + self._xml_end_tag("extLst") + + def _write_dxf_format_extensions(self): + # Write the DXFComplement <extLst> elements. + schema = "http://schemas.microsoft.com/office/spreadsheetml" + attributes = [ + ("uri", "{0417FA29-78FA-4A13-93AC-8FF0FAFDF519}"), + ( + "xmlns:xfpb", + schema + "/2022/featurepropertybag", + ), + ] + + self._xml_start_tag("extLst") + self._xml_start_tag("ext", attributes) + + self._xml_empty_tag("xfpb:DXFComplement", [("i", "0")]) + + self._xml_end_tag("ext") + self._xml_end_tag("extLst") |