###############################################################################
#
# Format - 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 typing import Literal, Union
from warnings import warn

from xlsxwriter.color import Color

from . import xmlwriter


class Format(xmlwriter.XMLwriter):
    """
    A class for writing the Excel XLSX Format file.


    """

    ###########################################################################
    #
    # Public API.
    #
    ###########################################################################

    def __init__(self, properties=None, xf_indices=None, dxf_indices=None) -> None:
        """
        Constructor.

        """
        if properties is None:
            properties = {}

        super().__init__()

        self.xf_format_indices = xf_indices
        self.dxf_format_indices = dxf_indices
        self.xf_index = None
        self.dxf_index = None

        self.num_format = "General"
        self.num_format_index = 0
        self.font_index = 0
        self.has_font = False
        self.has_dxf_font = False

        self.bold = 0
        self.underline = 0
        self.italic = 0
        self.font_name = "Calibri"
        self.font_size = 11
        self.font_color = None
        self.font_strikeout = 0
        self.font_outline = 0
        self.font_shadow = 0
        self.font_script = 0
        self.font_family = 2
        self.font_charset = 0
        self.font_scheme = "minor"
        self.font_condense = 0
        self.font_extend = 0
        self.theme = 0
        self.hyperlink = False
        self.xf_id = 0

        self.hidden = 0
        self.locked = 1

        self.text_h_align = 0
        self.text_wrap = 0
        self.text_v_align = 0
        self.text_justlast = 0
        self.rotation = 0

        self.fg_color = None
        self.bg_color = None
        self.pattern = 0
        self.has_fill = False
        self.has_dxf_fill = False
        self.fill_index = 0
        self.fill_count = 0

        self.border_index = 0
        self.has_border = False
        self.has_dxf_border = False
        self.border_count = 0

        self.bottom = 0
        self.bottom_color = None
        self.diag_border = 0
        self.diag_color = None
        self.diag_type = 0
        self.left = 0
        self.left_color = None
        self.right = 0
        self.right_color = None
        self.top = 0
        self.top_color = None

        self.indent = 0
        self.shrink = 0
        self.merge_range = 0
        self.reading_order = 0
        self.just_distrib = 0
        self.color_indexed = 0
        self.font_only = 0

        self.quote_prefix = False
        self.checkbox = False

        # Convert properties in the constructor to method calls.
        for key, value in properties.items():
            getattr(self, "set_" + key)(value)

        self._format_key = None

    def __repr__(self) -> str:
        """
        Return a string representation of the Format instance.
        """
        return (
            f"Format("
            f"font_name={self.font_name!r}, "
            f"font_size={self.font_size}, "
            f"bold={self.bold}, "
            f"italic={self.italic}, "
            f"underline={self.underline}, "
            f"font_color={self.font_color}, "
            f"num_format={self.num_format!r}, "
            f"text_h_align={self.text_h_align}, "
            f"text_v_align={self.text_v_align}, "
            f"fg_color={self.fg_color}, "
            f"bg_color={self.bg_color}, "
            f"pattern={self.pattern}, "
            f"locked={self.locked}, "
            f"hidden={self.hidden})"
        )

    ###########################################################################
    #
    # Format properties.
    #
    ###########################################################################

    def set_font_name(self, font_name) -> None:
        """
        Set the Format font_name property such as 'Time New Roman'. The
        default Excel font is 'Calibri'.

        Args:
            font_name: String with the font name. No default.

        Returns:
            Nothing.

        """
        self.font_name = font_name

    def set_font_size(self, font_size: int = 11) -> None:
        """
        Set the Format font_size property. The default Excel font size is 11.

        Args:
            font_size: Int with font size. No default.

        Returns:
            Nothing.

        """
        self.font_size = font_size

    def set_font_color(self, font_color: Union[str, Color]) -> None:
        """
        Set the Format font_color property. The Excel default is black.

        Args:
            font_color: String with the font color. No default.

        Returns:
            Nothing.

        """
        self.font_color = Color._from_value(font_color)

    def set_bold(self, bold: bool = True) -> None:
        """
        Set the Format bold property.

        Args:
            bold: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.bold = bold

    def set_italic(self, italic: bool = True) -> None:
        """
        Set the Format italic property.

        Args:
            italic: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.italic = italic

    def set_underline(self, underline: Literal[1, 2, 33, 34] = 1) -> None:
        """
        Set the Format underline property.

        Args:
            underline: Default is 1, single underline.

        Returns:
            Nothing.

        """
        self.underline = underline

    def set_font_strikeout(self, font_strikeout: bool = True) -> None:
        """
        Set the Format font_strikeout property.

        Args:
            font_strikeout: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_strikeout = font_strikeout

    def set_font_script(self, font_script: Literal[1, 2] = 1) -> None:
        """
        Set the Format font_script property.

        Args:
            font_script: Default is 1, superscript.

        Returns:
            Nothing.

        """
        self.font_script = font_script

    def set_font_outline(self, font_outline: bool = True) -> None:
        """
        Set the Format font_outline property.

        Args:
            font_outline: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_outline = font_outline

    def set_font_shadow(self, font_shadow: bool = True) -> None:
        """
        Set the Format font_shadow property.

        Args:
            font_shadow: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_shadow = font_shadow

    def set_num_format(self, num_format: str) -> None:
        """
        Set the Format num_format property such as '#,##0'.

        Args:
            num_format: String representing the number format. No default.

        Returns:
            Nothing.

        """
        self.num_format = num_format

    def set_locked(self, locked: bool = True) -> None:
        """
        Set the Format locked property.

        Args:
            locked: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.locked = locked

    def set_hidden(self, hidden: bool = True) -> None:
        """
        Set the Format hidden property.

        Args:
            hidden: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.hidden = hidden

    def set_align(
        self,
        alignment: Literal[
            "left",
            "centre",
            "center",
            "right",
            "fill",
            "justify",
            "center_across",
            "centre_across",
            "distributed",
            "justify_distributed",
            "justify_distributed",
            "top",
            "vcentre",
            "vcenter",
            "bottom",
            "vjustify",
            "vdistributed",
        ],
    ) -> None:
        """
        Set the Format cell alignment.

        Args:
            alignment: String representing alignment. No default.

        Returns:
            Nothing.
        """
        alignment = alignment.lower()

        # Set horizontal alignment properties.
        if alignment == "left":
            self.set_text_h_align(1)
        if alignment == "centre":
            self.set_text_h_align(2)
        if alignment == "center":
            self.set_text_h_align(2)
        if alignment == "right":
            self.set_text_h_align(3)
        if alignment == "fill":
            self.set_text_h_align(4)
        if alignment == "justify":
            self.set_text_h_align(5)
        if alignment == "center_across":
            self.set_text_h_align(6)
        if alignment == "centre_across":
            self.set_text_h_align(6)
        if alignment == "distributed":
            self.set_text_h_align(7)
        if alignment == "justify_distributed":
            self.set_text_h_align(7)

        if alignment == "justify_distributed":
            self.just_distrib = 1

        # Set vertical alignment properties.
        if alignment == "top":
            self.set_text_v_align(1)
        if alignment == "vcentre":
            self.set_text_v_align(2)
        if alignment == "vcenter":
            self.set_text_v_align(2)
        if alignment == "bottom":
            self.set_text_v_align(3)
        if alignment == "vjustify":
            self.set_text_v_align(4)
        if alignment == "vdistributed":
            self.set_text_v_align(5)

    def set_center_across(self, align_type: None = None) -> None:
        # pylint: disable=unused-argument
        """
        Set the Format center_across property.

        Returns:
            Nothing.

        """
        self.set_text_h_align(6)

    def set_text_wrap(self, text_wrap: bool = True) -> None:
        """
        Set the Format text_wrap property.

        Args:
            text_wrap: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.text_wrap = text_wrap

    def set_rotation(self, rotation: int) -> None:
        """
        Set the Format rotation property.

        Args:
            rotation: Rotation angle. No default.

        Returns:
            Nothing.

        """
        rotation = int(rotation)

        # Map user angle to Excel angle.
        if rotation == 270:
            rotation = 255
        elif -90 <= rotation <= 90:
            if rotation < 0:
                rotation = -rotation + 90
        else:
            warn("Rotation rotation outside range: -90 <= angle <= 90")
            return

        self.rotation = rotation

    def set_indent(self, indent: int = 1) -> None:
        """
        Set the Format indent property.

        Args:
            indent: Default is 1, first indentation level.

        Returns:
            Nothing.

        """
        self.indent = indent

    def set_shrink(self, shrink: bool = True) -> None:
        """
        Set the Format shrink property.

        Args:
            shrink: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.shrink = shrink

    def set_text_justlast(self, text_justlast: bool = True) -> None:
        """
        Set the Format text_justlast property.

        Args:
            text_justlast: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.text_justlast = text_justlast

    def set_pattern(self, pattern: int = 1) -> None:
        """
        Set the Format pattern property.

        Args:
            pattern: Default is 1, solid fill.

        Returns:
            Nothing.

        """
        self.pattern = pattern

    def set_bg_color(self, bg_color: Union[str, Color]) -> None:
        """
        Set the Format bg_color property.

        Args:
            bg_color: Background color. No default.

        Returns:
            Nothing.

        """
        self.bg_color = Color._from_value(bg_color)

    def set_fg_color(self, fg_color: Union[str, Color]) -> None:
        """
        Set the Format fg_color property.

        Args:
            fg_color: Foreground color. No default.

        Returns:
            Nothing.

        """
        self.fg_color = Color._from_value(fg_color)

    # set_border(style) Set cells borders to the same style
    def set_border(self, style: int = 1) -> None:
        """
        Set the Format bottom property.

        Args:
            bottom: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.set_bottom(style)
        self.set_top(style)
        self.set_left(style)
        self.set_right(style)

    # set_border_color(color) Set cells border to the same color
    def set_border_color(self, color: Union[str, Color]) -> None:
        """
        Set the Format bottom property.

        Args:
            color: Color string. No default.

        Returns:
            Nothing.

        """
        self.set_bottom_color(color)
        self.set_top_color(color)
        self.set_left_color(color)
        self.set_right_color(color)

    def set_bottom(self, bottom: int = 1) -> None:
        """
        Set the Format bottom property.

        Args:
            bottom: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.bottom = bottom

    def set_bottom_color(self, bottom_color: Union[str, Color]) -> None:
        """
        Set the Format bottom_color property.

        Args:
            bottom_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.bottom_color = Color._from_value(bottom_color)

    def set_diag_type(self, diag_type: Literal[1, 2, 3] = 1) -> None:
        """
        Set the Format diag_type property.

        Args:
            diag_type: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.diag_type = diag_type

    def set_left(self, left: int = 1) -> None:
        """
        Set the Format left property.

        Args:
            left: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.left = left

    def set_left_color(self, left_color: Union[str, Color]) -> None:
        """
        Set the Format left_color property.

        Args:
            left_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.left_color = Color._from_value(left_color)

    def set_right(self, right: int = 1) -> None:
        """
        Set the Format right property.

        Args:
            right: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.right = right

    def set_right_color(self, right_color: Union[str, Color]) -> None:
        """
        Set the Format right_color property.

        Args:
            right_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.right_color = Color._from_value(right_color)

    def set_top(self, top: int = 1) -> None:
        """
        Set the Format top property.

        Args:
            top: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.top = top

    def set_top_color(self, top_color: Union[str, Color]) -> None:
        """
        Set the Format top_color property.

        Args:
            top_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.top_color = Color._from_value(top_color)

    def set_diag_color(self, diag_color: Union[str, Color]) -> None:
        """
        Set the Format diag_color property.

        Args:
            diag_color: Color string. No default.

        Returns:
            Nothing.

        """
        self.diag_color = Color._from_value(diag_color)

    def set_diag_border(self, diag_border: int = 1) -> None:
        """
        Set the Format diag_border property.

        Args:
            diag_border: Default is 1, border type 1.

        Returns:
            Nothing.

        """
        self.diag_border = diag_border

    def set_quote_prefix(self, quote_prefix: bool = True) -> None:
        """
        Set the Format quote prefix property.

        Args:
            quote_prefix: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.quote_prefix = quote_prefix

    def set_checkbox(self, checkbox: bool = True) -> None:
        """
        Set the Format property to show a checkbox in a cell.

        This format property can be used with a cell that contains a boolean
        value to display it as a checkbox. This property isn't required very
        often and it is generally easier to create a checkbox using the
        ``worksheet.insert_checkbox()`` method.

        Args:
            checkbox: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.checkbox = checkbox

    ###########################################################################
    #
    # Internal Format properties. These aren't documented since they are
    # either only used internally or else are unlikely to be set by the user.
    #
    ###########################################################################

    def set_has_font(self, has_font: bool = True) -> None:
        """
        Set the property to indicate the format has a font.

        Args:
            has_font: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.has_font = has_font

    def set_has_fill(self, has_fill: bool = True) -> None:
        """
        Set the property to indicate the format has a fill.

        Args:
            has_fill: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.has_fill = has_fill

    def set_font_index(self, font_index: int) -> None:
        """
        Set the unique font index property.

        Args:
            font_index: The unique font index.

        Returns:
            Nothing.

        """
        self.font_index = font_index

    def set_xf_index(self, xf_index: int) -> None:
        """
        Set the unique format index property.

        Args:
            xf_index: The unique Excel format index.

        Returns:
            Nothing.

        """
        self.xf_index = xf_index

    def set_dxf_index(self, dxf_index: int) -> None:
        """
        Set the unique conditional format index property.

        Args:
            dxf_index: The unique Excel conditional format index.

        Returns:
            Nothing.

        """
        self.dxf_index = dxf_index

    def set_num_format_index(self, num_format_index: int) -> None:
        """
        Set the number format_index property.

        Args:
            num_format_index: The unique number format index.

        Returns:
            Nothing.

        """
        self.num_format_index = num_format_index

    def set_text_h_align(self, text_h_align: int) -> None:
        """
        Set the horizontal text alignment property.

        Args:
            text_h_align: Horizontal text alignment.

        Returns:
            Nothing.

        """
        self.text_h_align = text_h_align

    def set_text_v_align(self, text_v_align: int) -> None:
        """
        Set the vertical text alignment property.

        Args:
            text_h_align: Vertical text alignment.

        Returns:
            Nothing.

        """
        self.text_v_align = text_v_align

    def set_reading_order(self, direction: int = 0) -> None:
        # Set the reading_order property.
        """
        Set the reading order property.

        Args:
            direction: Default is 0, left to right.

        Returns:
            Nothing.

        """
        self.reading_order = direction

    def set_valign(
        self,
        align: Literal[
            "left",
            "centre",
            "center",
            "right",
            "fill",
            "justify",
            "center_across",
            "centre_across",
            "distributed",
            "justify_distributed",
            "justify_distributed",
            "top",
            "vcentre",
            "vcenter",
            "bottom",
            "vjustify",
            "vdistributed",
        ],
    ) -> None:
        # Set vertical cell alignment. This is required by the constructor
        # properties dict to differentiate between the vertical and horizontal
        # properties.
        """
        Set vertical cell alignment property.

        This is required by the constructor properties dict to differentiate
        between the vertical and horizontal properties.

        Args:
            align: Alignment property.

        Returns:
            Nothing.

        """
        self.set_align(align)

    def set_font_family(self, font_family: int) -> None:
        """
        Set the font family property.

        Args:
            font_family: Font family number.

        Returns:
            Nothing.

        """
        self.font_family = font_family

    def set_font_charset(self, font_charset: int) -> None:
        """
        Set the font character set property.

        Args:
            font_charset: The font character set number.

        Returns:
            Nothing.

        """
        self.font_charset = font_charset

    def set_font_scheme(self, font_scheme: int) -> None:
        """
        Set the font scheme property.

        Args:
            font_scheme: The font scheme.

        Returns:
            Nothing.

        """
        self.font_scheme = font_scheme

    def set_font_condense(self, font_condense: int) -> None:
        """
        Set the font condense property.

        Args:
            font_condense: The font condense property.

        Returns:
            Nothing.

        """
        self.font_condense = font_condense

    def set_font_extend(self, font_extend: int) -> None:
        """
        Set the font extend property.

        Args:
            font_extend: The font extend property.

        Returns:
            Nothing.

        """
        self.font_extend = font_extend

    def set_theme(self, theme: int) -> None:
        """
        Set the theme property.

        Args:
            theme: Format theme.

        Returns:
            Nothing.

        """
        self.theme = theme

    def set_hyperlink(self, hyperlink: bool = True) -> None:
        """
        Set the properties for the hyperlink style.

        Args:
            hyperlink: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.xf_id = 1
        self.set_underline(1)
        self.set_theme(10)
        self.hyperlink = hyperlink

    def set_color_indexed(self, color_index: Literal[0, 1]) -> None:
        """
        Set the color index property. Some fundamental format properties use an
        indexed color instead of a rbg or theme color.

        Args:
            color_index: Generally 0 or 1.

        Returns:
            Nothing.

        """
        self.color_indexed = color_index

    def set_font_only(self, font_only: bool = True) -> None:
        """
        Set property to indicate that the format is used for fonts only.

        Args:
            font_only: Default is True, turns property on.

        Returns:
            Nothing.

        """
        self.font_only = font_only

    # Compatibility methods. These versions of the method names were added in an
    # initial version for compatibility testing with Excel::Writer::XLSX and
    # leaked out into production code. They are deprecated and will be removed
    # in a future after a suitable deprecation period.
    def set_font(self, font_name: str) -> None:
        """Deprecated: Use set_font_name() instead."""
        self.font_name = font_name

    def set_size(self, font_size: int) -> None:
        """Deprecated: Use set_font_size() instead."""
        self.font_size = font_size

    def set_color(self, font_color: Union[Color, str]) -> None:
        """Deprecated: Use set_font_color() instead."""
        self.font_color = Color._from_value(font_color)

    ###########################################################################
    #
    # Private API.
    #
    ###########################################################################

    def _get_align_properties(self):
        # pylint: disable=too-many-boolean-expressions
        # Return properties for an Style xf <alignment> sub-element.
        changed = 0
        align = []

        # Check if any alignment options in the format have been changed.
        if (
            self.text_h_align
            or self.text_v_align
            or self.indent
            or self.rotation
            or self.text_wrap
            or self.shrink
            or self.reading_order
        ):
            changed = 1
        else:
            return changed, align

        # Indent is only allowed for some alignment properties. If it is
        # defined for any other alignment or no alignment has been set then
        # default to left alignment.
        if (
            self.indent
            and self.text_h_align != 1
            and self.text_h_align != 3
            and self.text_h_align != 7
            and self.text_v_align != 1
            and self.text_v_align != 3
            and self.text_v_align != 5
        ):
            self.text_h_align = 1

        # Check for properties that are mutually exclusive.
        if self.text_wrap:
            self.shrink = 0
        if self.text_h_align == 4:
            self.shrink = 0
        if self.text_h_align == 5:
            self.shrink = 0
        if self.text_h_align == 7:
            self.shrink = 0
        if self.text_h_align != 7:
            self.just_distrib = 0
        if self.indent:
            self.just_distrib = 0

        continuous = "centerContinuous"

        if self.text_h_align == 1:
            align.append(("horizontal", "left"))
        if self.text_h_align == 2:
            align.append(("horizontal", "center"))
        if self.text_h_align == 3:
            align.append(("horizontal", "right"))
        if self.text_h_align == 4:
            align.append(("horizontal", "fill"))
        if self.text_h_align == 5:
            align.append(("horizontal", "justify"))
        if self.text_h_align == 6:
            align.append(("horizontal", continuous))
        if self.text_h_align == 7:
            align.append(("horizontal", "distributed"))

        if self.just_distrib:
            align.append(("justifyLastLine", 1))

        # Property 'vertical' => 'bottom' is a default. It sets applyAlignment
        # without an alignment sub-element.
        if self.text_v_align == 1:
            align.append(("vertical", "top"))
        if self.text_v_align == 2:
            align.append(("vertical", "center"))
        if self.text_v_align == 4:
            align.append(("vertical", "justify"))
        if self.text_v_align == 5:
            align.append(("vertical", "distributed"))

        if self.rotation:
            align.append(("textRotation", self.rotation))
        if self.indent:
            align.append(("indent", self.indent))

        if self.text_wrap:
            align.append(("wrapText", 1))
        if self.shrink:
            align.append(("shrinkToFit", 1))

        if self.reading_order == 1:
            align.append(("readingOrder", 1))
        if self.reading_order == 2:
            align.append(("readingOrder", 2))

        return changed, align

    def _get_protection_properties(self):
        # Return properties for an Excel XML <Protection> element.
        attributes = []

        if not self.locked:
            attributes.append(("locked", 0))
        if self.hidden:
            attributes.append(("hidden", 1))

        return attributes

    def _get_format_key(self):
        # Returns a unique hash key for a format. Used by Workbook.
        if self._format_key is None:
            self._format_key = ":".join(
                str(x)
                for x in (
                    self._get_font_key(),
                    self._get_border_key(),
                    self._get_fill_key(),
                    self._get_alignment_key(),
                    self.num_format,
                    self.locked,
                    self.checkbox,
                    self.quote_prefix,
                    self.hidden,
                )
            )

        return self._format_key

    def _get_font_key(self):
        # Returns a unique hash key for a font. Used by Workbook.
        key = ":".join(
            str(x)
            for x in (
                self.bold,
                self.font_color,
                self.font_charset,
                self.font_family,
                self.font_outline,
                self.font_script,
                self.font_shadow,
                self.font_strikeout,
                self.font_name,
                self.italic,
                self.font_size,
                self.underline,
                self.theme,
            )
        )

        return key

    def _get_border_key(self):
        # Returns a unique hash key for a border style. Used by Workbook.
        key = ":".join(
            str(x)
            for x in (
                self.bottom,
                self.bottom_color,
                self.diag_border,
                self.diag_color,
                self.diag_type,
                self.left,
                self.left_color,
                self.right,
                self.right_color,
                self.top,
                self.top_color,
            )
        )

        return key

    def _get_fill_key(self):
        # Returns a unique hash key for a fill style. Used by Workbook.
        key = ":".join(str(x) for x in (self.pattern, self.bg_color, self.fg_color))

        return key

    def _get_alignment_key(self):
        # Returns a unique hash key for alignment formats.

        key = ":".join(
            str(x)
            for x in (
                self.text_h_align,
                self.text_v_align,
                self.indent,
                self.rotation,
                self.text_wrap,
                self.shrink,
                self.reading_order,
            )
        )

        return key

    def _get_xf_index(self):
        # Returns the XF index number used by Excel to identify a format.
        if self.xf_index is not None:
            # Format already has an index number so return it.
            return self.xf_index

        # Format doesn't have an index number so assign one.
        key = self._get_format_key()

        if key in self.xf_format_indices:
            # Format matches existing format with an index.
            return self.xf_format_indices[key]

        # New format requiring an index. Note. +1 since Excel
        # has an implicit "General" format at index 0.
        index = 1 + len(self.xf_format_indices)
        self.xf_format_indices[key] = index
        self.xf_index = index
        return index

    def _get_dxf_index(self):
        # Returns the DXF index number used by Excel to identify a format.
        if self.dxf_index is not None:
            # Format already has an index number so return it.
            return self.dxf_index

        # Format doesn't have an index number so assign one.
        key = self._get_format_key()

        if key in self.dxf_format_indices:
            # Format matches existing format with an index.
            return self.dxf_format_indices[key]

        # New format requiring an index.
        index = len(self.dxf_format_indices)
        self.dxf_format_indices[key] = index
        self.dxf_index = index
        return index

    ###########################################################################
    #
    # XML methods.
    #
    ###########################################################################
