diff --git a/excelizestyle/README.md b/excelizestyle/README.md new file mode 100644 index 0000000..c3b31cd --- /dev/null +++ b/excelizestyle/README.md @@ -0,0 +1,79 @@ +# Typed Excelize styles + +`StyleDefinition` and its nested definitions represent every public field of the +selected Excelize `Style`, `Border`, `Fill`, `Font`, `Alignment`, and `Protection` +structs. Categorical fields use named enum types. Numeric, text, boolean, and +pointer values retain their original meaning and can express custom values. + +```go +style := excelizestyle.StyleDefinition{ + Fill: excelizestyle.FillDefinition{ + Type: excelizestyle.FillTypePattern, + Pattern: excelizestyle.FillPatternSolid, + Color: []string{"#123456"}, + }, + Alignment: &excelizestyle.AlignmentDefinition{ + Horizontal: excelizestyle.AlignmentHorizontalCenter, + Vertical: excelizestyle.AlignmentVerticalBottom, + ReadingOrder: excelizestyle.AlignmentReadingOrderLeftToRight, + }, + NumFmt: excelizestyle.NumberFormatID14, +} +styleID, err := workbook.NewStyle(style.ToExcelize()) +``` + +Use `StyleDefinitionFromExcelize` to convert existing styles, or +`excelizeutil.GetStyleDefinition` to read a cell into this representation. +Conversion between a definition and an `excelize.Style` preserves all struct +fields, including nil pointers, custom strings, unknown numeric enum values, +and the distinction between nil and empty border slices. This does not expand +what Excelize can serialize or recover from a workbook. In particular, cell fill +transparency, arbitrary gradient shapes, both pattern colors, and the original +`Lang`/`DecimalPlaces`/`NegRed` inputs cannot always be recovered. + +## Generation and dependency updates + +After updating Excelize, run from the repository root: + +```sh +go generate ./excelizeutil +``` + +The generator reads the selected dependency through Go AST: + +- Fill pattern, border style and position, and fill type tables. +- Gradient preset IDs, geometry, and stop positions (legacy switches and modern + preset literals). The extractor uses these generated geometries. +- Documented alignment choices, underline and font vertical-alignment choices. +- Built-in, currency, and language-specific number format IDs/codes and language + tags. `NumberFormat.CodeForLanguage` resolves the corresponding source table; + these codes describe Excelize inputs, not arbitrary workbook custom formats + that may reuse the same ID. +- Every public field name and the complete typed definitions/conversions. + +Reading order, theme slots, character sets, and alignment defaults missing from +older documentation use stable OOXML definitions. Indexed palette values are +read from Excelize's palette when available; older releases use the 64-slot +OOXML palette. These schema definitions are explicit: AST cannot discover value +domains that Excelize does not define in code. Arbitrary RGB, font family/size, +indentation, rotation, tint, custom format, and transparency values remain plain +values rather than exhaustive enum lists. + +The test suite also compares the generated files with the selected dependency, +so adding an upstream enum value or field without regenerating fails tests. + +Check generation without changing files: + +```sh +(cd excelizeutil && go run ./cmd/gen-style-enums -check) +``` + +Changes to unsupported source shapes cause generation to fail. Regeneration +updates definitions and enum tables; newly added fields still require a review +of the workbook extractor and its coverage. Tests check field inventories, +conversion of every field, upstream additions, source-shape failures, and +round-trips for all generated patterns, borders, and gradient presets. + +The existing `Border`, `BorderAround`, `FindBorder`, `Alignment`, and `Fill` +helpers remain available, along with the original `BorderStyleContinuous*`, +`BorderStyleDash*`, and related aliases. diff --git a/excelizestyle/style.go b/excelizestyle/style.go index a75c3f0..1890d5c 100644 --- a/excelizestyle/style.go +++ b/excelizestyle/style.go @@ -4,39 +4,57 @@ import ( "github.com/xuri/excelize/v2" ) -type BorderPosition string - -const ( - BorderPositionTop BorderPosition = "top" - BorderPositionLeft BorderPosition = "left" - BorderPositionRight BorderPosition = "right" - BorderPositionBottom BorderPosition = "bottom" -) - -// BorderStyle https://xuri.me/excelize/ja/style.html#border -type BorderStyle int - +// Preserve the existing border style names as aliases of the generated enums. const ( - BorderStyleNone BorderStyle = 0 // Weight: 0, Style: - BorderStyleContinuous0 BorderStyle = 7 // Weight: 0, Style: ----------- - BorderStyleContinuous1 BorderStyle = 1 // Weight: 1, Style: ----------- - BorderStyleContinuous2 BorderStyle = 2 // Weight: 2, Style: ----------- - BorderStyleContinuous3 BorderStyle = 5 // Weight: 3, Style: ----------- - BorderStyleDash1 BorderStyle = 3 // Weight: 1, Style: - - - - - - - BorderStyleDash2 BorderStyle = 8 // Weight: 2, Style: - - - - - - - BorderStyleDot BorderStyle = 4 // Weight: 1, Style: . . . . . . - BorderStyleDouble BorderStyle = 6 // Weight: 3, Style: =========== - BorderStyleDashDot1 BorderStyle = 9 // Weight: 1, Style: - . - . - . - BorderStyleDashDot2 BorderStyle = 10 // Weight: 2, Style: - . - . - . - BorderStyleDashDotDot1 BorderStyle = 11 // Weight: 1, Style: - . . - . . - BorderStyleDashDotDot2 BorderStyle = 12 // Weight: 2, Style: - . . - . . - BorderStyleSlantDash BorderStyle = 13 // Weight: 2, Style: / - . / - . + // BorderStyleContinuous0 is the compatibility alias for BorderStyleHair. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleContinuous0 = BorderStyleHair + // BorderStyleContinuous1 is the compatibility alias for BorderStyleThin. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleContinuous1 = BorderStyleThin + // BorderStyleContinuous2 is the compatibility alias for BorderStyleMedium. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleContinuous2 = BorderStyleMedium + // BorderStyleContinuous3 is the compatibility alias for BorderStyleThick. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleContinuous3 = BorderStyleThick + // BorderStyleDash1 is the compatibility alias for BorderStyleDashed. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDash1 = BorderStyleDashed + // BorderStyleDash2 is the compatibility alias for BorderStyleMediumDashed. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDash2 = BorderStyleMediumDashed + // BorderStyleDot is the compatibility alias for BorderStyleDotted. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDot = BorderStyleDotted + // BorderStyleDashDot1 is the compatibility alias for BorderStyleDashDot. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDashDot1 = BorderStyleDashDot + // BorderStyleDashDot2 is the compatibility alias for BorderStyleMediumDashDot. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDashDot2 = BorderStyleMediumDashDot + // BorderStyleDashDotDot1 is the compatibility alias for BorderStyleDashDotDot. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDashDotDot1 = BorderStyleDashDotDot + // BorderStyleDashDotDot2 is the compatibility alias for BorderStyleMediumDashDotDot. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleDashDotDot2 = BorderStyleMediumDashDotDot + // BorderStyleSlantDash is the compatibility alias for BorderStyleSlantDashDot. + // See https://xuri.me/excelize/en/style.html#border + BorderStyleSlantDash = BorderStyleSlantDashDot ) +// BorderColor specifies an RGB border color, including custom hexadecimal values. +// +// See https://xuri.me/excelize/en/style.html#border type BorderColor string const ( + // BorderColorBlack selects the RGB color black (#000000). + // See https://xuri.me/excelize/en/style.html#border BorderColorBlack BorderColor = "#000000" + // BorderColorWhite selects the RGB color white (#FFFFFF). + // See https://xuri.me/excelize/en/style.html#border BorderColorWhite BorderColor = "#FFFFFF" ) @@ -91,29 +109,6 @@ func ExistsBorder(borders []excelize.Border, position BorderPosition) bool { return false } -// AlignmentHorizontal https://xuri.me/excelize/ja/style.html#align -type AlignmentHorizontal string - -const ( - AlignmentHorizontalLeft AlignmentHorizontal = "left" // Left (indented) - AlignmentHorizontalCenter AlignmentHorizontal = "center" // Centered - AlignmentHorizontalRight AlignmentHorizontal = "right" // Right (indented) - AlignmentHorizontalFill AlignmentHorizontal = "fill" // Filling - AlignmentHorizontalJustify AlignmentHorizontal = "justify" // Justified - AlignmentHorizontalCenterContinuous AlignmentHorizontal = "centerContinuous" // Cross-column centered - AlignmentHorizontalDistributed AlignmentHorizontal = "distributed" // Decentralized alignment (indented) -) - -// AlignmentVertical https://xuri.me/excelize/ja/style.html#align -type AlignmentVertical string - -const ( - AlignmentVerticalTop AlignmentVertical = "top" // Top alignment - AlignmentVerticalCenter AlignmentVertical = "center" // Centered - AlignmentVerticalJustify AlignmentVertical = "justify" // Justified - AlignmentVerticalDistributed AlignmentVertical = "distributed" // Decentralized alignment -) - func Alignment(horizontal AlignmentHorizontal, vertical AlignmentVertical, wrapText bool) *excelize.Alignment { return &excelize.Alignment{ Horizontal: string(horizontal), @@ -122,34 +117,9 @@ func Alignment(horizontal AlignmentHorizontal, vertical AlignmentVertical, wrapT } } -// FillPattern https://xuri.me/excelize/ja/style.html#pattern -type FillPattern int - -const ( - FillPatternNone FillPattern = 0 - FillPatternSolid FillPattern = 1 - FillPatternMediumGray FillPattern = 2 - FillPatternDarkGray FillPattern = 3 - FillPatternLightGray FillPattern = 4 - FillPatternDarkHorizontal FillPattern = 5 - FillPatternDarkVertical FillPattern = 6 - FillPatternDarkDown FillPattern = 7 - FillPatternDarkUp FillPattern = 8 - FillPatternDarkGrid FillPattern = 9 - FillPatternDarkTrellis FillPattern = 10 - FillPatternLightHorizontal FillPattern = 11 - FillPatternLightVertical FillPattern = 12 - FillPatternLightDown FillPattern = 13 - FillPatternLightUp FillPattern = 14 - FillPatternLightGrid FillPattern = 15 - FillPatternLightTrellis FillPattern = 16 - FillPatternGray125 FillPattern = 17 - FillPatternGray0625 FillPattern = 18 -) - func Fill(pattern FillPattern, color string) excelize.Fill { return excelize.Fill{ - Type: "pattern", + Type: string(FillTypePattern), Pattern: int(pattern), Color: []string{color}, } diff --git a/excelizestyle/style_enums_gen.go b/excelizestyle/style_enums_gen.go new file mode 100644 index 0000000..a9fb852 --- /dev/null +++ b/excelizestyle/style_enums_gen.go @@ -0,0 +1,4923 @@ +// Code generated by go generate ./excelizeutil; DO NOT EDIT. + +package excelizestyle + +import "github.com/xuri/excelize/v2" + +// FillPattern selects the pattern index used by excelize.Fill.Pattern. +// +// See https://xuri.me/excelize/en/style.html +type FillPattern int + +const ( + // FillPatternNone selects the "none" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternNone FillPattern = iota + // FillPatternSolid selects the "solid" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternSolid + // FillPatternMediumGray selects the "mediumGray" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternMediumGray + // FillPatternDarkGray selects the "darkGray" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkGray + // FillPatternLightGray selects the "lightGray" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightGray + // FillPatternDarkHorizontal selects the "darkHorizontal" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkHorizontal + // FillPatternDarkVertical selects the "darkVertical" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkVertical + // FillPatternDarkDown selects the "darkDown" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkDown + // FillPatternDarkUp selects the "darkUp" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkUp + // FillPatternDarkGrid selects the "darkGrid" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkGrid + // FillPatternDarkTrellis selects the "darkTrellis" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternDarkTrellis + // FillPatternLightHorizontal selects the "lightHorizontal" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightHorizontal + // FillPatternLightVertical selects the "lightVertical" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightVertical + // FillPatternLightDown selects the "lightDown" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightDown + // FillPatternLightUp selects the "lightUp" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightUp + // FillPatternLightGrid selects the "lightGrid" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightGrid + // FillPatternLightTrellis selects the "lightTrellis" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternLightTrellis + // FillPatternGray125 selects the "gray125" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternGray125 + // FillPatternGray0625 selects the "gray0625" cell-fill pattern. + // + // See https://xuri.me/excelize/en/style.html + FillPatternGray0625 + // FillPatternCount is the number of generated choices; it is not a valid formatting value. + // + // See https://xuri.me/excelize/en/style.html + FillPatternCount +) + +func (value FillPattern) String() string { + switch value { + case FillPatternNone: + return "none" + case FillPatternSolid: + return "solid" + case FillPatternMediumGray: + return "mediumGray" + case FillPatternDarkGray: + return "darkGray" + case FillPatternLightGray: + return "lightGray" + case FillPatternDarkHorizontal: + return "darkHorizontal" + case FillPatternDarkVertical: + return "darkVertical" + case FillPatternDarkDown: + return "darkDown" + case FillPatternDarkUp: + return "darkUp" + case FillPatternDarkGrid: + return "darkGrid" + case FillPatternDarkTrellis: + return "darkTrellis" + case FillPatternLightHorizontal: + return "lightHorizontal" + case FillPatternLightVertical: + return "lightVertical" + case FillPatternLightDown: + return "lightDown" + case FillPatternLightUp: + return "lightUp" + case FillPatternLightGrid: + return "lightGrid" + case FillPatternLightTrellis: + return "lightTrellis" + case FillPatternGray125: + return "gray125" + case FillPatternGray0625: + return "gray0625" + default: + return "" + } +} + +func ParseFillPattern(value string) (FillPattern, bool) { + switch value { + case "none": + return FillPatternNone, true + case "solid": + return FillPatternSolid, true + case "mediumGray": + return FillPatternMediumGray, true + case "darkGray": + return FillPatternDarkGray, true + case "lightGray": + return FillPatternLightGray, true + case "darkHorizontal": + return FillPatternDarkHorizontal, true + case "darkVertical": + return FillPatternDarkVertical, true + case "darkDown": + return FillPatternDarkDown, true + case "darkUp": + return FillPatternDarkUp, true + case "darkGrid": + return FillPatternDarkGrid, true + case "darkTrellis": + return FillPatternDarkTrellis, true + case "lightHorizontal": + return FillPatternLightHorizontal, true + case "lightVertical": + return FillPatternLightVertical, true + case "lightDown": + return FillPatternLightDown, true + case "lightUp": + return FillPatternLightUp, true + case "lightGrid": + return FillPatternLightGrid, true + case "lightTrellis": + return FillPatternLightTrellis, true + case "gray125": + return FillPatternGray125, true + case "gray0625": + return FillPatternGray0625, true + default: + return 0, false + } +} + +// BorderStyle selects the line style index used by excelize.Border.Style. +// +// See https://xuri.me/excelize/en/style.html +type BorderStyle int + +const ( + // BorderStyleNone selects no border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleNone BorderStyle = iota + // BorderStyleThin selects a thin solid border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleThin + // BorderStyleMedium selects a medium solid border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleMedium + // BorderStyleDashed selects a dashed border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleDashed + // BorderStyleDotted selects a dotted border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleDotted + // BorderStyleThick selects a thick solid border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleThick + // BorderStyleDouble selects a double-line border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleDouble + // BorderStyleHair selects a hairline border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleHair + // BorderStyleMediumDashed selects a medium dashed border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleMediumDashed + // BorderStyleDashDot selects a dash-dot border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleDashDot + // BorderStyleMediumDashDot selects a medium dash-dot border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleMediumDashDot + // BorderStyleDashDotDot selects a dash-dot-dot border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleDashDotDot + // BorderStyleMediumDashDotDot selects a medium dash-dot-dot border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleMediumDashDotDot + // BorderStyleSlantDashDot selects a slanted dash-dot border. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleSlantDashDot + // BorderStyleCount is the number of generated choices; it is not a valid formatting value. + // + // See https://xuri.me/excelize/en/style.html + BorderStyleCount +) + +func (value BorderStyle) String() string { + switch value { + case BorderStyleNone: + return "none" + case BorderStyleThin: + return "thin" + case BorderStyleMedium: + return "medium" + case BorderStyleDashed: + return "dashed" + case BorderStyleDotted: + return "dotted" + case BorderStyleThick: + return "thick" + case BorderStyleDouble: + return "double" + case BorderStyleHair: + return "hair" + case BorderStyleMediumDashed: + return "mediumDashed" + case BorderStyleDashDot: + return "dashDot" + case BorderStyleMediumDashDot: + return "mediumDashDot" + case BorderStyleDashDotDot: + return "dashDotDot" + case BorderStyleMediumDashDotDot: + return "mediumDashDotDot" + case BorderStyleSlantDashDot: + return "slantDashDot" + default: + return "" + } +} + +func ParseBorderStyle(value string) (BorderStyle, bool) { + switch value { + case "none": + return BorderStyleNone, true + case "thin": + return BorderStyleThin, true + case "medium": + return BorderStyleMedium, true + case "dashed": + return BorderStyleDashed, true + case "dotted": + return BorderStyleDotted, true + case "thick": + return BorderStyleThick, true + case "double": + return BorderStyleDouble, true + case "hair": + return BorderStyleHair, true + case "mediumDashed": + return BorderStyleMediumDashed, true + case "dashDot": + return BorderStyleDashDot, true + case "mediumDashDot": + return BorderStyleMediumDashDot, true + case "dashDotDot": + return BorderStyleDashDotDot, true + case "mediumDashDotDot": + return BorderStyleMediumDashDotDot, true + case "slantDashDot": + return BorderStyleSlantDashDot, true + default: + return 0, false + } +} + +// BorderPosition selects the edge or diagonal used by excelize.Border.Type. +// +// See https://xuri.me/excelize/en/style.html +type BorderPosition string + +const ( + // BorderPositionLeft selects the left cell edge. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionLeft BorderPosition = "left" + // BorderPositionRight selects the right cell edge. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionRight BorderPosition = "right" + // BorderPositionTop selects the top cell edge. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionTop BorderPosition = "top" + // BorderPositionBottom selects the bottom cell edge. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionBottom BorderPosition = "bottom" + // BorderPositionDiagonalUp selects the diagonal from bottom-left to top-right. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionDiagonalUp BorderPosition = "diagonalUp" + // BorderPositionDiagonalDown selects the diagonal from top-left to bottom-right. + // + // See https://xuri.me/excelize/en/style.html + BorderPositionDiagonalDown BorderPosition = "diagonalDown" +) + +func BorderPositionValues() []BorderPosition { + return []BorderPosition{"left", "right", "top", "bottom", "diagonalUp", "diagonalDown"} +} + +// FillType selects pattern or gradient filling for excelize.Fill.Type; the empty value leaves the fill unset. +// +// See https://xuri.me/excelize/en/style.html +type FillType string + +const ( + // FillTypeGradient uses the selected gradient preset with two endpoint colors. + // + // See https://xuri.me/excelize/en/style.html + FillTypeGradient FillType = "gradient" + // FillTypePattern uses the selected fill pattern and its foreground color. + // + // See https://xuri.me/excelize/en/style.html + FillTypePattern FillType = "pattern" + // FillTypeUnset leaves the formatting selector unset. + // + // See https://xuri.me/excelize/en/style.html + FillTypeUnset FillType = "" +) + +func FillTypeValues() []FillType { return []FillType{"gradient", "pattern", ""} } + +// AlignmentHorizontal selects horizontal cell alignment, including general alignment. +// +// See https://xuri.me/excelize/en/style.html +type AlignmentHorizontal string + +const ( + // AlignmentHorizontalLeft aligns text to the left edge. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalLeft AlignmentHorizontal = "left" + // AlignmentHorizontalCenter centers text horizontally. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalCenter AlignmentHorizontal = "center" + // AlignmentHorizontalRight aligns text to the right edge. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalRight AlignmentHorizontal = "right" + // AlignmentHorizontalFill repeats the cell text across the available width. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalFill AlignmentHorizontal = "fill" + // AlignmentHorizontalJustify justifies text to both horizontal edges. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalJustify AlignmentHorizontal = "justify" + // AlignmentHorizontalCenterContinuous centers text across adjacent cells. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalCenterContinuous AlignmentHorizontal = "centerContinuous" + // AlignmentHorizontalDistributed distributes text evenly across the available width. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalDistributed AlignmentHorizontal = "distributed" + // AlignmentHorizontalGeneral aligns according to the cell value's data type. + // + // See https://xuri.me/excelize/en/style.html + AlignmentHorizontalGeneral AlignmentHorizontal = "general" +) + +func AlignmentHorizontalValues() []AlignmentHorizontal { + return []AlignmentHorizontal{"left", "center", "right", "fill", "justify", "centerContinuous", "distributed", "general"} +} + +// AlignmentVertical selects vertical cell alignment, including bottom alignment. +// +// See https://xuri.me/excelize/en/style.html +type AlignmentVertical string + +const ( + // AlignmentVerticalTop aligns text to the top edge. + // + // See https://xuri.me/excelize/en/style.html + AlignmentVerticalTop AlignmentVertical = "top" + // AlignmentVerticalCenter centers text vertically. + // + // See https://xuri.me/excelize/en/style.html + AlignmentVerticalCenter AlignmentVertical = "center" + // AlignmentVerticalJustify justifies text vertically. + // + // See https://xuri.me/excelize/en/style.html + AlignmentVerticalJustify AlignmentVertical = "justify" + // AlignmentVerticalDistributed distributes text evenly across the available height. + // + // See https://xuri.me/excelize/en/style.html + AlignmentVerticalDistributed AlignmentVertical = "distributed" + // AlignmentVerticalBottom aligns text to the bottom edge. + // + // See https://xuri.me/excelize/en/style.html + AlignmentVerticalBottom AlignmentVertical = "bottom" +) + +func AlignmentVerticalValues() []AlignmentVertical { + return []AlignmentVertical{"top", "center", "justify", "distributed", "bottom"} +} + +// FontUnderline selects the underline mode accepted by the selected Excelize dependency; the empty value leaves it unset. +// +// See https://xuri.me/excelize/en/style.html +type FontUnderline string + +const ( + // FontUnderlineNone explicitly disables underlining. + // + // See https://xuri.me/excelize/en/style.html + FontUnderlineNone FontUnderline = "none" + // FontUnderlineSingle uses a single underline. + // + // See https://xuri.me/excelize/en/style.html + FontUnderlineSingle FontUnderline = "single" + // FontUnderlineDouble uses a double underline. + // + // See https://xuri.me/excelize/en/style.html + FontUnderlineDouble FontUnderline = "double" + // FontUnderlineUnset leaves the formatting selector unset. + // + // See https://xuri.me/excelize/en/style.html + FontUnderlineUnset FontUnderline = "" +) + +func FontUnderlineValues() []FontUnderline { return []FontUnderline{"none", "single", "double", ""} } + +// FontVerticalAlign selects baseline, superscript, or subscript placement of font text. +// +// See https://xuri.me/excelize/en/style.html +type FontVerticalAlign string + +const ( + // FontVerticalAlignBaseline uses normal baseline text. + // + // See https://xuri.me/excelize/en/style.html + FontVerticalAlignBaseline FontVerticalAlign = "baseline" + // FontVerticalAlignSuperscript raises text above the baseline. + // + // See https://xuri.me/excelize/en/style.html + FontVerticalAlignSuperscript FontVerticalAlign = "superscript" + // FontVerticalAlignSubscript lowers text below the baseline. + // + // See https://xuri.me/excelize/en/style.html + FontVerticalAlignSubscript FontVerticalAlign = "subscript" +) + +func FontVerticalAlignValues() []FontVerticalAlign { + return []FontVerticalAlign{"baseline", "superscript", "subscript"} +} + +// AlignmentReadingOrder selects context-dependent, left-to-right, or right-to-left cell text direction. +// +// See https://xuri.me/excelize/en/style.html +type AlignmentReadingOrder uint64 + +const ( + // AlignmentReadingOrderContext derives text direction from the first strong directional character. + // + // See https://xuri.me/excelize/en/style.html + AlignmentReadingOrderContext AlignmentReadingOrder = iota + // AlignmentReadingOrderLeftToRight reads cell text from left to right. + // + // See https://xuri.me/excelize/en/style.html + AlignmentReadingOrderLeftToRight + // AlignmentReadingOrderRightToLeft reads cell text from right to left. + // + // See https://xuri.me/excelize/en/style.html + AlignmentReadingOrderRightToLeft +) + +// NumberFormat selects an Excelize number-format ID; Code and CodeForLanguage resolve the dependency's format tables, not custom workbook definitions. +// +// See https://xuri.me/excelize/en/style.html +type NumberFormat int + +const ( + // NumberFormatID0 selects Excelize number-format ID 0 with format code "general". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID0 NumberFormat = 0 + // NumberFormatID1 selects Excelize number-format ID 1 with format code "0". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID1 NumberFormat = 1 + // NumberFormatID2 selects Excelize number-format ID 2 with format code "0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID2 NumberFormat = 2 + // NumberFormatID3 selects Excelize number-format ID 3 with format code "#,##0". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID3 NumberFormat = 3 + // NumberFormatID4 selects Excelize number-format ID 4 with format code "#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID4 NumberFormat = 4 + // NumberFormatID9 selects Excelize number-format ID 9 with format code "0%". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID9 NumberFormat = 9 + // NumberFormatID10 selects Excelize number-format ID 10 with format code "0.00%". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID10 NumberFormat = 10 + // NumberFormatID11 selects Excelize number-format ID 11 with format code "0.00E+00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID11 NumberFormat = 11 + // NumberFormatID12 selects Excelize number-format ID 12 with format code "# ?/?". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID12 NumberFormat = 12 + // NumberFormatID13 selects Excelize number-format ID 13 with format code "# ??/??". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID13 NumberFormat = 13 + // NumberFormatID14 selects Excelize number-format ID 14 with format code "mm-dd-yy". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID14 NumberFormat = 14 + // NumberFormatID15 selects Excelize number-format ID 15 with format code "d-mmm-yy". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID15 NumberFormat = 15 + // NumberFormatID16 selects Excelize number-format ID 16 with format code "d-mmm". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID16 NumberFormat = 16 + // NumberFormatID17 selects Excelize number-format ID 17 with format code "mmm-yy". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID17 NumberFormat = 17 + // NumberFormatID18 selects Excelize number-format ID 18 with format code "h:mm AM/PM". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID18 NumberFormat = 18 + // NumberFormatID19 selects Excelize number-format ID 19 with format code "h:mm:ss AM/PM". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID19 NumberFormat = 19 + // NumberFormatID20 selects Excelize number-format ID 20 with format code "hh:mm". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID20 NumberFormat = 20 + // NumberFormatID21 selects Excelize number-format ID 21 with format code "hh:mm:ss". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID21 NumberFormat = 21 + // NumberFormatID22 selects Excelize number-format ID 22 with format code "m/d/yy hh:mm". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID22 NumberFormat = 22 + // NumberFormatID27 selects Excelize number-format ID 27; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID27 NumberFormat = 27 + // NumberFormatID28 selects Excelize number-format ID 28; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID28 NumberFormat = 28 + // NumberFormatID29 selects Excelize number-format ID 29; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID29 NumberFormat = 29 + // NumberFormatID30 selects Excelize number-format ID 30; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID30 NumberFormat = 30 + // NumberFormatID31 selects Excelize number-format ID 31; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID31 NumberFormat = 31 + // NumberFormatID32 selects Excelize number-format ID 32; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID32 NumberFormat = 32 + // NumberFormatID33 selects Excelize number-format ID 33; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID33 NumberFormat = 33 + // NumberFormatID34 selects Excelize number-format ID 34; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID34 NumberFormat = 34 + // NumberFormatID35 selects Excelize number-format ID 35; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID35 NumberFormat = 35 + // NumberFormatID36 selects Excelize number-format ID 36; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID36 NumberFormat = 36 + // NumberFormatID37 selects Excelize number-format ID 37 with format code "#,##0 ;(#,##0)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID37 NumberFormat = 37 + // NumberFormatID38 selects Excelize number-format ID 38 with format code "#,##0 ;[red](#,##0)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID38 NumberFormat = 38 + // NumberFormatID39 selects Excelize number-format ID 39 with format code "#,##0.00 ;(#,##0.00)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID39 NumberFormat = 39 + // NumberFormatID40 selects Excelize number-format ID 40 with format code "#,##0.00 ;[red](#,##0.00)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID40 NumberFormat = 40 + // NumberFormatID41 selects Excelize number-format ID 41 with format code "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID41 NumberFormat = 41 + // NumberFormatID42 selects Excelize number-format ID 42 with format code "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID42 NumberFormat = 42 + // NumberFormatID43 selects Excelize number-format ID 43 with format code "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID43 NumberFormat = 43 + // NumberFormatID44 selects Excelize number-format ID 44 with format code "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID44 NumberFormat = 44 + // NumberFormatID45 selects Excelize number-format ID 45 with format code "mm:ss". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID45 NumberFormat = 45 + // NumberFormatID46 selects Excelize number-format ID 46 with format code "[h]:mm:ss". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID46 NumberFormat = 46 + // NumberFormatID47 selects Excelize number-format ID 47 with format code "mm:ss.0". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID47 NumberFormat = 47 + // NumberFormatID48 selects Excelize number-format ID 48 with format code "##0.0E+0". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID48 NumberFormat = 48 + // NumberFormatID49 selects Excelize number-format ID 49 with format code "@". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID49 NumberFormat = 49 + // NumberFormatID50 selects Excelize number-format ID 50; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID50 NumberFormat = 50 + // NumberFormatID51 selects Excelize number-format ID 51; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID51 NumberFormat = 51 + // NumberFormatID52 selects Excelize number-format ID 52; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID52 NumberFormat = 52 + // NumberFormatID53 selects Excelize number-format ID 53; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID53 NumberFormat = 53 + // NumberFormatID54 selects Excelize number-format ID 54; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID54 NumberFormat = 54 + // NumberFormatID55 selects Excelize number-format ID 55; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID55 NumberFormat = 55 + // NumberFormatID56 selects Excelize number-format ID 56; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID56 NumberFormat = 56 + // NumberFormatID57 selects Excelize number-format ID 57; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID57 NumberFormat = 57 + // NumberFormatID58 selects Excelize number-format ID 58; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID58 NumberFormat = 58 + // NumberFormatID59 selects Excelize number-format ID 59; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID59 NumberFormat = 59 + // NumberFormatID60 selects Excelize number-format ID 60; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID60 NumberFormat = 60 + // NumberFormatID61 selects Excelize number-format ID 61; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID61 NumberFormat = 61 + // NumberFormatID62 selects Excelize number-format ID 62; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID62 NumberFormat = 62 + // NumberFormatID67 selects Excelize number-format ID 67; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID67 NumberFormat = 67 + // NumberFormatID68 selects Excelize number-format ID 68; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID68 NumberFormat = 68 + // NumberFormatID69 selects Excelize number-format ID 69; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID69 NumberFormat = 69 + // NumberFormatID70 selects Excelize number-format ID 70; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID70 NumberFormat = 70 + // NumberFormatID71 selects Excelize number-format ID 71; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID71 NumberFormat = 71 + // NumberFormatID72 selects Excelize number-format ID 72; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID72 NumberFormat = 72 + // NumberFormatID73 selects Excelize number-format ID 73; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID73 NumberFormat = 73 + // NumberFormatID74 selects Excelize number-format ID 74; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID74 NumberFormat = 74 + // NumberFormatID75 selects Excelize number-format ID 75; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID75 NumberFormat = 75 + // NumberFormatID76 selects Excelize number-format ID 76; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID76 NumberFormat = 76 + // NumberFormatID77 selects Excelize number-format ID 77; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID77 NumberFormat = 77 + // NumberFormatID78 selects Excelize number-format ID 78; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID78 NumberFormat = 78 + // NumberFormatID79 selects Excelize number-format ID 79; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID79 NumberFormat = 79 + // NumberFormatID80 selects Excelize number-format ID 80; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID80 NumberFormat = 80 + // NumberFormatID81 selects Excelize number-format ID 81; its format depends on the language selected through CodeForLanguage. + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID81 NumberFormat = 81 + // NumberFormatID164 selects Excelize number-format ID 164 with format code "\"¥\"#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID164 NumberFormat = 164 + // NumberFormatID165 selects Excelize number-format ID 165 with format code "[$$-409]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID165 NumberFormat = 165 + // NumberFormatID166 selects Excelize number-format ID 166 with format code "[$$-45C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID166 NumberFormat = 166 + // NumberFormatID167 selects Excelize number-format ID 167 with format code "[$$-1004]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID167 NumberFormat = 167 + // NumberFormatID168 selects Excelize number-format ID 168 with format code "[$$-404]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID168 NumberFormat = 168 + // NumberFormatID169 selects Excelize number-format ID 169 with format code "[$$-C09]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID169 NumberFormat = 169 + // NumberFormatID170 selects Excelize number-format ID 170 with format code "[$$-2809]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID170 NumberFormat = 170 + // NumberFormatID171 selects Excelize number-format ID 171 with format code "[$$-1009]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID171 NumberFormat = 171 + // NumberFormatID172 selects Excelize number-format ID 172 with format code "[$$-2009]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID172 NumberFormat = 172 + // NumberFormatID173 selects Excelize number-format ID 173 with format code "[$$-1409]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID173 NumberFormat = 173 + // NumberFormatID174 selects Excelize number-format ID 174 with format code "[$$-4809]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID174 NumberFormat = 174 + // NumberFormatID175 selects Excelize number-format ID 175 with format code "[$$-2C09]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID175 NumberFormat = 175 + // NumberFormatID176 selects Excelize number-format ID 176 with format code "[$$-2409]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID176 NumberFormat = 176 + // NumberFormatID177 selects Excelize number-format ID 177 with format code "[$$-1000]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID177 NumberFormat = 177 + // NumberFormatID178 selects Excelize number-format ID 178 with format code "#,##0.00\\ [$$-C0C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID178 NumberFormat = 178 + // NumberFormatID179 selects Excelize number-format ID 179 with format code "[$$-475]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID179 NumberFormat = 179 + // NumberFormatID180 selects Excelize number-format ID 180 with format code "[$$-83E]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID180 NumberFormat = 180 + // NumberFormatID181 selects Excelize number-format ID 181 with format code "[$$-86B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID181 NumberFormat = 181 + // NumberFormatID182 selects Excelize number-format ID 182 with format code "[$$-340A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID182 NumberFormat = 182 + // NumberFormatID183 selects Excelize number-format ID 183 with format code "[$$-240A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID183 NumberFormat = 183 + // NumberFormatID184 selects Excelize number-format ID 184 with format code "[$$-300A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID184 NumberFormat = 184 + // NumberFormatID185 selects Excelize number-format ID 185 with format code "[$$-440A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID185 NumberFormat = 185 + // NumberFormatID186 selects Excelize number-format ID 186 with format code "[$$-80A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID186 NumberFormat = 186 + // NumberFormatID187 selects Excelize number-format ID 187 with format code "[$$-500A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID187 NumberFormat = 187 + // NumberFormatID188 selects Excelize number-format ID 188 with format code "[$$-540A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID188 NumberFormat = 188 + // NumberFormatID189 selects Excelize number-format ID 189 with format code "[$$-380A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID189 NumberFormat = 189 + // NumberFormatID190 selects Excelize number-format ID 190 with format code "[$£-809]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID190 NumberFormat = 190 + // NumberFormatID191 selects Excelize number-format ID 191 with format code "[$£-491]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID191 NumberFormat = 191 + // NumberFormatID192 selects Excelize number-format ID 192 with format code "[$£-452]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID192 NumberFormat = 192 + // NumberFormatID193 selects Excelize number-format ID 193 with format code "[$¥-804]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID193 NumberFormat = 193 + // NumberFormatID194 selects Excelize number-format ID 194 with format code "[$¥-411]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID194 NumberFormat = 194 + // NumberFormatID195 selects Excelize number-format ID 195 with format code "[$¥-478]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID195 NumberFormat = 195 + // NumberFormatID196 selects Excelize number-format ID 196 with format code "[$¥-451]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID196 NumberFormat = 196 + // NumberFormatID197 selects Excelize number-format ID 197 with format code "[$¥-480]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID197 NumberFormat = 197 + // NumberFormatID198 selects Excelize number-format ID 198 with format code "#,##0.00\\ [$֏-42B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID198 NumberFormat = 198 + // NumberFormatID199 selects Excelize number-format ID 199 with format code "[$؋-463]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID199 NumberFormat = 199 + // NumberFormatID200 selects Excelize number-format ID 200 with format code "[$؋-48C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID200 NumberFormat = 200 + // NumberFormatID201 selects Excelize number-format ID 201 with format code "[$৳-845]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID201 NumberFormat = 201 + // NumberFormatID202 selects Excelize number-format ID 202 with format code "#,##0.00[$៛-453]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID202 NumberFormat = 202 + // NumberFormatID203 selects Excelize number-format ID 203 with format code "[$₡-140A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID203 NumberFormat = 203 + // NumberFormatID204 selects Excelize number-format ID 204 with format code "[$₦-468]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID204 NumberFormat = 204 + // NumberFormatID205 selects Excelize number-format ID 205 with format code "[$₦-470]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID205 NumberFormat = 205 + // NumberFormatID206 selects Excelize number-format ID 206 with format code "[$₩-412]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID206 NumberFormat = 206 + // NumberFormatID207 selects Excelize number-format ID 207 with format code "[$₪-40D]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID207 NumberFormat = 207 + // NumberFormatID208 selects Excelize number-format ID 208 with format code "#,##0.00\\ [$₫-42A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID208 NumberFormat = 208 + // NumberFormatID209 selects Excelize number-format ID 209 with format code "#,##0.00\\ [$€-42D]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID209 NumberFormat = 209 + // NumberFormatID210 selects Excelize number-format ID 210 with format code "#,##0.00\\ [$€-47E]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID210 NumberFormat = 210 + // NumberFormatID211 selects Excelize number-format ID 211 with format code "#,##0.00\\ [$€-403]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID211 NumberFormat = 211 + // NumberFormatID212 selects Excelize number-format ID 212 with format code "#,##0.00\\ [$€-483]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID212 NumberFormat = 212 + // NumberFormatID213 selects Excelize number-format ID 213 with format code "[$€-813]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID213 NumberFormat = 213 + // NumberFormatID214 selects Excelize number-format ID 214 with format code "[$€-413]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID214 NumberFormat = 214 + // NumberFormatID215 selects Excelize number-format ID 215 with format code "[$€-1809]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID215 NumberFormat = 215 + // NumberFormatID216 selects Excelize number-format ID 216 with format code "#,##0.00\\ [$€-425]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID216 NumberFormat = 216 + // NumberFormatID217 selects Excelize number-format ID 217 with format code "[$€-2]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID217 NumberFormat = 217 + // NumberFormatID218 selects Excelize number-format ID 218 with format code "#,##0.00\\ [$€-1]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID218 NumberFormat = 218 + // NumberFormatID219 selects Excelize number-format ID 219 with format code "#,##0.00\\ [$€-40B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID219 NumberFormat = 219 + // NumberFormatID220 selects Excelize number-format ID 220 with format code "#,##0.00\\ [$€-80C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID220 NumberFormat = 220 + // NumberFormatID221 selects Excelize number-format ID 221 with format code "#,##0.00\\ [$€-40C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID221 NumberFormat = 221 + // NumberFormatID222 selects Excelize number-format ID 222 with format code "#,##0.00\\ [$€-140C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID222 NumberFormat = 222 + // NumberFormatID223 selects Excelize number-format ID 223 with format code "#,##0.00\\ [$€-180C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID223 NumberFormat = 223 + // NumberFormatID224 selects Excelize number-format ID 224 with format code "[$€-200C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID224 NumberFormat = 224 + // NumberFormatID225 selects Excelize number-format ID 225 with format code "#,##0.00\\ [$€-456]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID225 NumberFormat = 225 + // NumberFormatID226 selects Excelize number-format ID 226 with format code "#,##0.00\\ [$€-C07]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID226 NumberFormat = 226 + // NumberFormatID227 selects Excelize number-format ID 227 with format code "#,##0.00\\ [$€-407]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID227 NumberFormat = 227 + // NumberFormatID228 selects Excelize number-format ID 228 with format code "#,##0.00\\ [$€-1007]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID228 NumberFormat = 228 + // NumberFormatID229 selects Excelize number-format ID 229 with format code "#,##0.00\\ [$€-408]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID229 NumberFormat = 229 + // NumberFormatID230 selects Excelize number-format ID 230 with format code "#,##0.00\\ [$€-243B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID230 NumberFormat = 230 + // NumberFormatID231 selects Excelize number-format ID 231 with format code "[$€-83C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID231 NumberFormat = 231 + // NumberFormatID232 selects Excelize number-format ID 232 with format code "[$€-410]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID232 NumberFormat = 232 + // NumberFormatID233 selects Excelize number-format ID 233 with format code "[$€-476]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID233 NumberFormat = 233 + // NumberFormatID234 selects Excelize number-format ID 234 with format code "#,##0.00\\ [$€-2C1A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID234 NumberFormat = 234 + // NumberFormatID235 selects Excelize number-format ID 235 with format code "[$€-426]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID235 NumberFormat = 235 + // NumberFormatID236 selects Excelize number-format ID 236 with format code "#,##0.00\\ [$€-427]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID236 NumberFormat = 236 + // NumberFormatID237 selects Excelize number-format ID 237 with format code "#,##0.00\\ [$€-82E]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID237 NumberFormat = 237 + // NumberFormatID238 selects Excelize number-format ID 238 with format code "#,##0.00\\ [$€-46E]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID238 NumberFormat = 238 + // NumberFormatID239 selects Excelize number-format ID 239 with format code "[$€-43A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID239 NumberFormat = 239 + // NumberFormatID240 selects Excelize number-format ID 240 with format code "#,##0.00\\ [$€-C3B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID240 NumberFormat = 240 + // NumberFormatID241 selects Excelize number-format ID 241 with format code "#,##0.00\\ [$€-482]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID241 NumberFormat = 241 + // NumberFormatID242 selects Excelize number-format ID 242 with format code "#,##0.00\\ [$€-816]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID242 NumberFormat = 242 + // NumberFormatID243 selects Excelize number-format ID 243 with format code "#,##0.00\\ [$€-301A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID243 NumberFormat = 243 + // NumberFormatID244 selects Excelize number-format ID 244 with format code "#,##0.00\\ [$€-203B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID244 NumberFormat = 244 + // NumberFormatID245 selects Excelize number-format ID 245 with format code "#,##0.00\\ [$€-41B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID245 NumberFormat = 245 + // NumberFormatID246 selects Excelize number-format ID 246 with format code "#,##0.00\\ [$€-424]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID246 NumberFormat = 246 + // NumberFormatID247 selects Excelize number-format ID 247 with format code "#,##0.00\\ [$€-C0A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID247 NumberFormat = 247 + // NumberFormatID248 selects Excelize number-format ID 248 with format code "#,##0.00\\ [$€-81D]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID248 NumberFormat = 248 + // NumberFormatID249 selects Excelize number-format ID 249 with format code "#,##0.00\\ [$€-484]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID249 NumberFormat = 249 + // NumberFormatID250 selects Excelize number-format ID 250 with format code "#,##0.00\\ [$€-42E]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID250 NumberFormat = 250 + // NumberFormatID251 selects Excelize number-format ID 251 with format code "[$€-462]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID251 NumberFormat = 251 + // NumberFormatID252 selects Excelize number-format ID 252 with format code "#,##0.00\\ [$₭-454]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID252 NumberFormat = 252 + // NumberFormatID253 selects Excelize number-format ID 253 with format code "#,##0.00\\ [$₮-450]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID253 NumberFormat = 253 + // NumberFormatID254 selects Excelize number-format ID 254 with format code "[$₮-C50]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID254 NumberFormat = 254 + // NumberFormatID255 selects Excelize number-format ID 255 with format code "[$₱-3409]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID255 NumberFormat = 255 + // NumberFormatID256 selects Excelize number-format ID 256 with format code "[$₱-464]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID256 NumberFormat = 256 + // NumberFormatID257 selects Excelize number-format ID 257 with format code "#,##0.00[$₴-422]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID257 NumberFormat = 257 + // NumberFormatID258 selects Excelize number-format ID 258 with format code "[$₸-43F]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID258 NumberFormat = 258 + // NumberFormatID259 selects Excelize number-format ID 259 with format code "[$₹-460]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID259 NumberFormat = 259 + // NumberFormatID260 selects Excelize number-format ID 260 with format code "[$₹-4009]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID260 NumberFormat = 260 + // NumberFormatID261 selects Excelize number-format ID 261 with format code "[$₹-447]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID261 NumberFormat = 261 + // NumberFormatID262 selects Excelize number-format ID 262 with format code "[$₹-439]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID262 NumberFormat = 262 + // NumberFormatID263 selects Excelize number-format ID 263 with format code "[$₹-44B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID263 NumberFormat = 263 + // NumberFormatID264 selects Excelize number-format ID 264 with format code "[$₹-860]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID264 NumberFormat = 264 + // NumberFormatID265 selects Excelize number-format ID 265 with format code "[$₹-457]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID265 NumberFormat = 265 + // NumberFormatID266 selects Excelize number-format ID 266 with format code "[$₹-458]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID266 NumberFormat = 266 + // NumberFormatID267 selects Excelize number-format ID 267 with format code "[$₹-44E]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID267 NumberFormat = 267 + // NumberFormatID268 selects Excelize number-format ID 268 with format code "[$₹-861]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID268 NumberFormat = 268 + // NumberFormatID269 selects Excelize number-format ID 269 with format code "[$₹-448]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID269 NumberFormat = 269 + // NumberFormatID270 selects Excelize number-format ID 270 with format code "[$₹-446]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID270 NumberFormat = 270 + // NumberFormatID271 selects Excelize number-format ID 271 with format code "[$₹-44F]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID271 NumberFormat = 271 + // NumberFormatID272 selects Excelize number-format ID 272 with format code "[$₹-459]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID272 NumberFormat = 272 + // NumberFormatID273 selects Excelize number-format ID 273 with format code "[$₹-449]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID273 NumberFormat = 273 + // NumberFormatID274 selects Excelize number-format ID 274 with format code "[$₹-820]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID274 NumberFormat = 274 + // NumberFormatID275 selects Excelize number-format ID 275 with format code "#,##0.00\\ [$₺-41F]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID275 NumberFormat = 275 + // NumberFormatID276 selects Excelize number-format ID 276 with format code "#,##0.00\\ [$₼-42C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID276 NumberFormat = 276 + // NumberFormatID277 selects Excelize number-format ID 277 with format code "#,##0.00\\ [$₼-82C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID277 NumberFormat = 277 + // NumberFormatID278 selects Excelize number-format ID 278 with format code "#,##0.00\\ [$₽-419]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID278 NumberFormat = 278 + // NumberFormatID279 selects Excelize number-format ID 279 with format code "#,##0.00[$₽-485]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID279 NumberFormat = 279 + // NumberFormatID280 selects Excelize number-format ID 280 with format code "#,##0.00\\ [$₾-437]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID280 NumberFormat = 280 + // NumberFormatID281 selects Excelize number-format ID 281 with format code "[$B/.-180A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID281 NumberFormat = 281 + // NumberFormatID282 selects Excelize number-format ID 282 with format code "[$Br-472]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID282 NumberFormat = 282 + // NumberFormatID283 selects Excelize number-format ID 283 with format code "[$Br-477]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID283 NumberFormat = 283 + // NumberFormatID284 selects Excelize number-format ID 284 with format code "#,##0.00[$Br-473]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID284 NumberFormat = 284 + // NumberFormatID285 selects Excelize number-format ID 285 with format code "[$Bs-46B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID285 NumberFormat = 285 + // NumberFormatID286 selects Excelize number-format ID 286 with format code "[$Bs-400A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID286 NumberFormat = 286 + // NumberFormatID287 selects Excelize number-format ID 287 with format code "[$Bs.-200A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID287 NumberFormat = 287 + // NumberFormatID288 selects Excelize number-format ID 288 with format code "[$BWP-832]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID288 NumberFormat = 288 + // NumberFormatID289 selects Excelize number-format ID 289 with format code "[$C$-4C0A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID289 NumberFormat = 289 + // NumberFormatID290 selects Excelize number-format ID 290 with format code "[$CA$-85D]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID290 NumberFormat = 290 + // NumberFormatID291 selects Excelize number-format ID 291 with format code "[$CA$-47C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID291 NumberFormat = 291 + // NumberFormatID292 selects Excelize number-format ID 292 with format code "[$CA$-45D]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID292 NumberFormat = 292 + // NumberFormatID293 selects Excelize number-format ID 293 with format code "[$CFA-340C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID293 NumberFormat = 293 + // NumberFormatID294 selects Excelize number-format ID 294 with format code "[$CFA-280C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID294 NumberFormat = 294 + // NumberFormatID295 selects Excelize number-format ID 295 with format code "#,##0.00\\ [$CFA-867]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID295 NumberFormat = 295 + // NumberFormatID296 selects Excelize number-format ID 296 with format code "#,##0.00\\ [$CFA-488]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID296 NumberFormat = 296 + // NumberFormatID297 selects Excelize number-format ID 297 with format code "#,##0.00\\ [$CHF-100C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID297 NumberFormat = 297 + // NumberFormatID298 selects Excelize number-format ID 298 with format code "[$CHF-1407]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID298 NumberFormat = 298 + // NumberFormatID299 selects Excelize number-format ID 299 with format code "[$CHF-807]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID299 NumberFormat = 299 + // NumberFormatID300 selects Excelize number-format ID 300 with format code "[$CHF-810]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID300 NumberFormat = 300 + // NumberFormatID301 selects Excelize number-format ID 301 with format code "[$CHF-417]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID301 NumberFormat = 301 + // NumberFormatID302 selects Excelize number-format ID 302 with format code "[$CLP-47A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID302 NumberFormat = 302 + // NumberFormatID303 selects Excelize number-format ID 303 with format code "[$CN¥-850]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID303 NumberFormat = 303 + // NumberFormatID304 selects Excelize number-format ID 304 with format code "#,##0.00\\ [$DZD-85F]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID304 NumberFormat = 304 + // NumberFormatID305 selects Excelize number-format ID 305 with format code "[$FCFA-2C0C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID305 NumberFormat = 305 + // NumberFormatID306 selects Excelize number-format ID 306 with format code "#,##0.00\\ [$Ft-40E]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID306 NumberFormat = 306 + // NumberFormatID307 selects Excelize number-format ID 307 with format code "[$G-3C0C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID307 NumberFormat = 307 + // NumberFormatID308 selects Excelize number-format ID 308 with format code "[$Gs.-3C0A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID308 NumberFormat = 308 + // NumberFormatID309 selects Excelize number-format ID 309 with format code "[$GTQ-486]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID309 NumberFormat = 309 + // NumberFormatID310 selects Excelize number-format ID 310 with format code "[$HK$-C04]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID310 NumberFormat = 310 + // NumberFormatID311 selects Excelize number-format ID 311 with format code "[$HK$-3C09]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID311 NumberFormat = 311 + // NumberFormatID312 selects Excelize number-format ID 312 with format code "#,##0.00\\ [$HRK-41A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID312 NumberFormat = 312 + // NumberFormatID313 selects Excelize number-format ID 313 with format code "[$IDR-3809]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID313 NumberFormat = 313 + // NumberFormatID314 selects Excelize number-format ID 314 with format code "[$IQD-492]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID314 NumberFormat = 314 + // NumberFormatID315 selects Excelize number-format ID 315 with format code "#,##0.00\\ [$ISK-40F]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID315 NumberFormat = 315 + // NumberFormatID316 selects Excelize number-format ID 316 with format code "[$K-455]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID316 NumberFormat = 316 + // NumberFormatID317 selects Excelize number-format ID 317 with format code "#,##0.00\\ [$Kč-405]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID317 NumberFormat = 317 + // NumberFormatID318 selects Excelize number-format ID 318 with format code "#,##0.00\\ [$KM-141A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID318 NumberFormat = 318 + // NumberFormatID319 selects Excelize number-format ID 319 with format code "#,##0.00\\ [$KM-101A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID319 NumberFormat = 319 + // NumberFormatID320 selects Excelize number-format ID 320 with format code "#,##0.00\\ [$KM-181A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID320 NumberFormat = 320 + // NumberFormatID321 selects Excelize number-format ID 321 with format code "[$kr-438]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID321 NumberFormat = 321 + // NumberFormatID322 selects Excelize number-format ID 322 with format code "[$kr-43B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID322 NumberFormat = 322 + // NumberFormatID323 selects Excelize number-format ID 323 with format code "#,##0.00\\ [$kr-83B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID323 NumberFormat = 323 + // NumberFormatID324 selects Excelize number-format ID 324 with format code "[$kr-414]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID324 NumberFormat = 324 + // NumberFormatID325 selects Excelize number-format ID 325 with format code "[$kr-814]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID325 NumberFormat = 325 + // NumberFormatID326 selects Excelize number-format ID 326 with format code "#,##0.00\\ [$kr-41D]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID326 NumberFormat = 326 + // NumberFormatID327 selects Excelize number-format ID 327 with format code "[$kr.-406]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID327 NumberFormat = 327 + // NumberFormatID328 selects Excelize number-format ID 328 with format code "[$kr.-46F]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID328 NumberFormat = 328 + // NumberFormatID329 selects Excelize number-format ID 329 with format code "[$Ksh-441]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID329 NumberFormat = 329 + // NumberFormatID330 selects Excelize number-format ID 330 with format code "[$L-818]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID330 NumberFormat = 330 + // NumberFormatID331 selects Excelize number-format ID 331 with format code "[$L-819]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID331 NumberFormat = 331 + // NumberFormatID332 selects Excelize number-format ID 332 with format code "[$L-480A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID332 NumberFormat = 332 + // NumberFormatID333 selects Excelize number-format ID 333 with format code "#,##0.00\\ [$Lekë-41C]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID333 NumberFormat = 333 + // NumberFormatID334 selects Excelize number-format ID 334 with format code "[$MAD-45F]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID334 NumberFormat = 334 + // NumberFormatID335 selects Excelize number-format ID 335 with format code "[$MAD-380C]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID335 NumberFormat = 335 + // NumberFormatID336 selects Excelize number-format ID 336 with format code "#,##0.00\\ [$MAD-105F]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID336 NumberFormat = 336 + // NumberFormatID337 selects Excelize number-format ID 337 with format code "[$MOP$-1404]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID337 NumberFormat = 337 + // NumberFormatID338 selects Excelize number-format ID 338 with format code "#,##0.00\\ [$MVR-465]_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID338 NumberFormat = 338 + // NumberFormatID339 selects Excelize number-format ID 339 with format code "#,##0.00[$Nfk-873]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID339 NumberFormat = 339 + // NumberFormatID340 selects Excelize number-format ID 340 with format code "[$NGN-466]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID340 NumberFormat = 340 + // NumberFormatID341 selects Excelize number-format ID 341 with format code "[$NGN-467]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID341 NumberFormat = 341 + // NumberFormatID342 selects Excelize number-format ID 342 with format code "[$NGN-469]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID342 NumberFormat = 342 + // NumberFormatID343 selects Excelize number-format ID 343 with format code "[$NGN-471]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID343 NumberFormat = 343 + // NumberFormatID344 selects Excelize number-format ID 344 with format code "[$NOK-103B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID344 NumberFormat = 344 + // NumberFormatID345 selects Excelize number-format ID 345 with format code "[$NOK-183B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID345 NumberFormat = 345 + // NumberFormatID346 selects Excelize number-format ID 346 with format code "[$NZ$-481]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID346 NumberFormat = 346 + // NumberFormatID347 selects Excelize number-format ID 347 with format code "[$PKR-859]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID347 NumberFormat = 347 + // NumberFormatID348 selects Excelize number-format ID 348 with format code "[$PYG-474]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID348 NumberFormat = 348 + // NumberFormatID349 selects Excelize number-format ID 349 with format code "[$Q-100A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID349 NumberFormat = 349 + // NumberFormatID350 selects Excelize number-format ID 350 with format code "[$R-436]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID350 NumberFormat = 350 + // NumberFormatID351 selects Excelize number-format ID 351 with format code "[$R-1C09]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID351 NumberFormat = 351 + // NumberFormatID352 selects Excelize number-format ID 352 with format code "[$R-435]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID352 NumberFormat = 352 + // NumberFormatID353 selects Excelize number-format ID 353 with format code "[$R$-416]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID353 NumberFormat = 353 + // NumberFormatID354 selects Excelize number-format ID 354 with format code "[$RD$-1C0A]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID354 NumberFormat = 354 + // NumberFormatID355 selects Excelize number-format ID 355 with format code "#,##0.00\\ [$RF-487]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID355 NumberFormat = 355 + // NumberFormatID356 selects Excelize number-format ID 356 with format code "[$RM-4409]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID356 NumberFormat = 356 + // NumberFormatID357 selects Excelize number-format ID 357 with format code "[$RM-43E]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID357 NumberFormat = 357 + // NumberFormatID358 selects Excelize number-format ID 358 with format code "#,##0.00\\ [$RON-418]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID358 NumberFormat = 358 + // NumberFormatID359 selects Excelize number-format ID 359 with format code "[$Rp-421]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID359 NumberFormat = 359 + // NumberFormatID360 selects Excelize number-format ID 360 with format code "[$Rs-420]#,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID360 NumberFormat = 360 + // NumberFormatID361 selects Excelize number-format ID 361 with format code "[$Rs.-849]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID361 NumberFormat = 361 + // NumberFormatID362 selects Excelize number-format ID 362 with format code "#,##0.00\\ [$RSD-81A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID362 NumberFormat = 362 + // NumberFormatID363 selects Excelize number-format ID 363 with format code "#,##0.00\\ [$RSD-C1A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID363 NumberFormat = 363 + // NumberFormatID364 selects Excelize number-format ID 364 with format code "#,##0.00\\ [$RUB-46D]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID364 NumberFormat = 364 + // NumberFormatID365 selects Excelize number-format ID 365 with format code "#,##0.00\\ [$RUB-444]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID365 NumberFormat = 365 + // NumberFormatID366 selects Excelize number-format ID 366 with format code "[$S/.-C6B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID366 NumberFormat = 366 + // NumberFormatID367 selects Excelize number-format ID 367 with format code "[$S/.-280A]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID367 NumberFormat = 367 + // NumberFormatID368 selects Excelize number-format ID 368 with format code "#,##0.00\\ [$SEK-143B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID368 NumberFormat = 368 + // NumberFormatID369 selects Excelize number-format ID 369 with format code "#,##0.00\\ [$SEK-1C3B]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID369 NumberFormat = 369 + // NumberFormatID370 selects Excelize number-format ID 370 with format code "#,##0.00\\ [$soʻm-443]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID370 NumberFormat = 370 + // NumberFormatID371 selects Excelize number-format ID 371 with format code "#,##0.00\\ [$soʻm-843]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID371 NumberFormat = 371 + // NumberFormatID372 selects Excelize number-format ID 372 with format code "#,##0.00\\ [$SYP-45A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID372 NumberFormat = 372 + // NumberFormatID373 selects Excelize number-format ID 373 with format code "[$THB-41E]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID373 NumberFormat = 373 + // NumberFormatID374 selects Excelize number-format ID 374 with format code "#,##0.00[$TMT-442]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID374 NumberFormat = 374 + // NumberFormatID375 selects Excelize number-format ID 375 with format code "[$US$-3009]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID375 NumberFormat = 375 + // NumberFormatID376 selects Excelize number-format ID 376 with format code "[$ZAR-46C]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID376 NumberFormat = 376 + // NumberFormatID377 selects Excelize number-format ID 377 with format code "[$ZAR-430]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID377 NumberFormat = 377 + // NumberFormatID378 selects Excelize number-format ID 378 with format code "[$ZAR-431]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID378 NumberFormat = 378 + // NumberFormatID379 selects Excelize number-format ID 379 with format code "[$ZAR-432]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID379 NumberFormat = 379 + // NumberFormatID380 selects Excelize number-format ID 380 with format code "[$ZAR-433]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID380 NumberFormat = 380 + // NumberFormatID381 selects Excelize number-format ID 381 with format code "[$ZAR-434]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID381 NumberFormat = 381 + // NumberFormatID382 selects Excelize number-format ID 382 with format code "#,##0.00\\ [$zł-415]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID382 NumberFormat = 382 + // NumberFormatID383 selects Excelize number-format ID 383 with format code "#,##0.00\\ [$ден-42F]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID383 NumberFormat = 383 + // NumberFormatID384 selects Excelize number-format ID 384 with format code "#,##0.00\\ [$КМ-201A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID384 NumberFormat = 384 + // NumberFormatID385 selects Excelize number-format ID 385 with format code "#,##0.00\\ [$КМ-1C1A]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID385 NumberFormat = 385 + // NumberFormatID386 selects Excelize number-format ID 386 with format code "#,##0.00\\ [$лв.-402]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID386 NumberFormat = 386 + // NumberFormatID387 selects Excelize number-format ID 387 with format code "#,##0.00\\ [$р.-423]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID387 NumberFormat = 387 + // NumberFormatID388 selects Excelize number-format ID 388 with format code "#,##0.00\\ [$сом-440]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID388 NumberFormat = 388 + // NumberFormatID389 selects Excelize number-format ID 389 with format code "#,##0.00\\ [$сом-428]". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID389 NumberFormat = 389 + // NumberFormatID390 selects Excelize number-format ID 390 with format code "[$ج.م.-C01]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID390 NumberFormat = 390 + // NumberFormatID391 selects Excelize number-format ID 391 with format code "[$د.أ.-2C01]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID391 NumberFormat = 391 + // NumberFormatID392 selects Excelize number-format ID 392 with format code "[$د.إ.-3801]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID392 NumberFormat = 392 + // NumberFormatID393 selects Excelize number-format ID 393 with format code "[$د.ب.-3C01]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID393 NumberFormat = 393 + // NumberFormatID394 selects Excelize number-format ID 394 with format code "[$د.ت.-1C01]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID394 NumberFormat = 394 + // NumberFormatID395 selects Excelize number-format ID 395 with format code "[$د.ج.-1401]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID395 NumberFormat = 395 + // NumberFormatID396 selects Excelize number-format ID 396 with format code "[$د.ع.-801]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID396 NumberFormat = 396 + // NumberFormatID397 selects Excelize number-format ID 397 with format code "[$د.ك.-3401]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID397 NumberFormat = 397 + // NumberFormatID398 selects Excelize number-format ID 398 with format code "[$د.ل.-1001]#,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID398 NumberFormat = 398 + // NumberFormatID399 selects Excelize number-format ID 399 with format code "[$د.م.-1801]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID399 NumberFormat = 399 + // NumberFormatID400 selects Excelize number-format ID 400 with format code "[$ر-846]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID400 NumberFormat = 400 + // NumberFormatID401 selects Excelize number-format ID 401 with format code "[$ر.س.-401]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID401 NumberFormat = 401 + // NumberFormatID402 selects Excelize number-format ID 402 with format code "[$ر.ع.-2001]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID402 NumberFormat = 402 + // NumberFormatID403 selects Excelize number-format ID 403 with format code "[$ر.ق.-4001]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID403 NumberFormat = 403 + // NumberFormatID404 selects Excelize number-format ID 404 with format code "[$ر.ي.-2401]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID404 NumberFormat = 404 + // NumberFormatID405 selects Excelize number-format ID 405 with format code "[$ریال-429]#,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID405 NumberFormat = 405 + // NumberFormatID406 selects Excelize number-format ID 406 with format code "[$ل.س.-2801]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID406 NumberFormat = 406 + // NumberFormatID407 selects Excelize number-format ID 407 with format code "[$ل.ل.-3001]\\ #,##0.00_-". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID407 NumberFormat = 407 + // NumberFormatID408 selects Excelize number-format ID 408 with format code "[$ብር-45E]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID408 NumberFormat = 408 + // NumberFormatID409 selects Excelize number-format ID 409 with format code "[$रू-461]#,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID409 NumberFormat = 409 + // NumberFormatID410 selects Excelize number-format ID 410 with format code "[$රු.-45B]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID410 NumberFormat = 410 + // NumberFormatID411 selects Excelize number-format ID 411 with format code "[$ADP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID411 NumberFormat = 411 + // NumberFormatID412 selects Excelize number-format ID 412 with format code "[$AED]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID412 NumberFormat = 412 + // NumberFormatID413 selects Excelize number-format ID 413 with format code "[$AFA]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID413 NumberFormat = 413 + // NumberFormatID414 selects Excelize number-format ID 414 with format code "[$AFN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID414 NumberFormat = 414 + // NumberFormatID415 selects Excelize number-format ID 415 with format code "[$ALL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID415 NumberFormat = 415 + // NumberFormatID416 selects Excelize number-format ID 416 with format code "[$AMD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID416 NumberFormat = 416 + // NumberFormatID417 selects Excelize number-format ID 417 with format code "[$ANG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID417 NumberFormat = 417 + // NumberFormatID418 selects Excelize number-format ID 418 with format code "[$AOA]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID418 NumberFormat = 418 + // NumberFormatID419 selects Excelize number-format ID 419 with format code "[$ARS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID419 NumberFormat = 419 + // NumberFormatID420 selects Excelize number-format ID 420 with format code "[$ATS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID420 NumberFormat = 420 + // NumberFormatID421 selects Excelize number-format ID 421 with format code "[$AUD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID421 NumberFormat = 421 + // NumberFormatID422 selects Excelize number-format ID 422 with format code "[$AWG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID422 NumberFormat = 422 + // NumberFormatID423 selects Excelize number-format ID 423 with format code "[$AZM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID423 NumberFormat = 423 + // NumberFormatID424 selects Excelize number-format ID 424 with format code "[$AZN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID424 NumberFormat = 424 + // NumberFormatID425 selects Excelize number-format ID 425 with format code "[$BAM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID425 NumberFormat = 425 + // NumberFormatID426 selects Excelize number-format ID 426 with format code "[$BBD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID426 NumberFormat = 426 + // NumberFormatID427 selects Excelize number-format ID 427 with format code "[$BDT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID427 NumberFormat = 427 + // NumberFormatID428 selects Excelize number-format ID 428 with format code "[$BEF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID428 NumberFormat = 428 + // NumberFormatID429 selects Excelize number-format ID 429 with format code "[$BGL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID429 NumberFormat = 429 + // NumberFormatID430 selects Excelize number-format ID 430 with format code "[$BGN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID430 NumberFormat = 430 + // NumberFormatID431 selects Excelize number-format ID 431 with format code "[$BHD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID431 NumberFormat = 431 + // NumberFormatID432 selects Excelize number-format ID 432 with format code "[$BIF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID432 NumberFormat = 432 + // NumberFormatID433 selects Excelize number-format ID 433 with format code "[$BMD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID433 NumberFormat = 433 + // NumberFormatID434 selects Excelize number-format ID 434 with format code "[$BND]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID434 NumberFormat = 434 + // NumberFormatID435 selects Excelize number-format ID 435 with format code "[$BOB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID435 NumberFormat = 435 + // NumberFormatID436 selects Excelize number-format ID 436 with format code "[$BOV]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID436 NumberFormat = 436 + // NumberFormatID437 selects Excelize number-format ID 437 with format code "[$BRL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID437 NumberFormat = 437 + // NumberFormatID438 selects Excelize number-format ID 438 with format code "[$BSD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID438 NumberFormat = 438 + // NumberFormatID439 selects Excelize number-format ID 439 with format code "[$BTN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID439 NumberFormat = 439 + // NumberFormatID440 selects Excelize number-format ID 440 with format code "[$BWP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID440 NumberFormat = 440 + // NumberFormatID441 selects Excelize number-format ID 441 with format code "[$BYR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID441 NumberFormat = 441 + // NumberFormatID442 selects Excelize number-format ID 442 with format code "[$BZD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID442 NumberFormat = 442 + // NumberFormatID443 selects Excelize number-format ID 443 with format code "[$CAD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID443 NumberFormat = 443 + // NumberFormatID444 selects Excelize number-format ID 444 with format code "[$CDF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID444 NumberFormat = 444 + // NumberFormatID445 selects Excelize number-format ID 445 with format code "[$CHE]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID445 NumberFormat = 445 + // NumberFormatID446 selects Excelize number-format ID 446 with format code "[$CHF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID446 NumberFormat = 446 + // NumberFormatID447 selects Excelize number-format ID 447 with format code "[$CHW]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID447 NumberFormat = 447 + // NumberFormatID448 selects Excelize number-format ID 448 with format code "[$CLF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID448 NumberFormat = 448 + // NumberFormatID449 selects Excelize number-format ID 449 with format code "[$CLP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID449 NumberFormat = 449 + // NumberFormatID450 selects Excelize number-format ID 450 with format code "[$CNY]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID450 NumberFormat = 450 + // NumberFormatID451 selects Excelize number-format ID 451 with format code "[$COP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID451 NumberFormat = 451 + // NumberFormatID452 selects Excelize number-format ID 452 with format code "[$COU]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID452 NumberFormat = 452 + // NumberFormatID453 selects Excelize number-format ID 453 with format code "[$CRC]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID453 NumberFormat = 453 + // NumberFormatID454 selects Excelize number-format ID 454 with format code "[$CSD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID454 NumberFormat = 454 + // NumberFormatID455 selects Excelize number-format ID 455 with format code "[$CUC]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID455 NumberFormat = 455 + // NumberFormatID456 selects Excelize number-format ID 456 with format code "[$CVE]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID456 NumberFormat = 456 + // NumberFormatID457 selects Excelize number-format ID 457 with format code "[$CYP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID457 NumberFormat = 457 + // NumberFormatID458 selects Excelize number-format ID 458 with format code "[$CZK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID458 NumberFormat = 458 + // NumberFormatID459 selects Excelize number-format ID 459 with format code "[$DEM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID459 NumberFormat = 459 + // NumberFormatID460 selects Excelize number-format ID 460 with format code "[$DJF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID460 NumberFormat = 460 + // NumberFormatID461 selects Excelize number-format ID 461 with format code "[$DKK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID461 NumberFormat = 461 + // NumberFormatID462 selects Excelize number-format ID 462 with format code "[$DOP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID462 NumberFormat = 462 + // NumberFormatID463 selects Excelize number-format ID 463 with format code "[$DZD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID463 NumberFormat = 463 + // NumberFormatID464 selects Excelize number-format ID 464 with format code "[$ECS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID464 NumberFormat = 464 + // NumberFormatID465 selects Excelize number-format ID 465 with format code "[$ECV]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID465 NumberFormat = 465 + // NumberFormatID466 selects Excelize number-format ID 466 with format code "[$EEK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID466 NumberFormat = 466 + // NumberFormatID467 selects Excelize number-format ID 467 with format code "[$EGP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID467 NumberFormat = 467 + // NumberFormatID468 selects Excelize number-format ID 468 with format code "[$ERN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID468 NumberFormat = 468 + // NumberFormatID469 selects Excelize number-format ID 469 with format code "[$ESP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID469 NumberFormat = 469 + // NumberFormatID470 selects Excelize number-format ID 470 with format code "[$ETB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID470 NumberFormat = 470 + // NumberFormatID471 selects Excelize number-format ID 471 with format code "[$EUR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID471 NumberFormat = 471 + // NumberFormatID472 selects Excelize number-format ID 472 with format code "[$FIM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID472 NumberFormat = 472 + // NumberFormatID473 selects Excelize number-format ID 473 with format code "[$FJD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID473 NumberFormat = 473 + // NumberFormatID474 selects Excelize number-format ID 474 with format code "[$FKP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID474 NumberFormat = 474 + // NumberFormatID475 selects Excelize number-format ID 475 with format code "[$FRF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID475 NumberFormat = 475 + // NumberFormatID476 selects Excelize number-format ID 476 with format code "[$GBP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID476 NumberFormat = 476 + // NumberFormatID477 selects Excelize number-format ID 477 with format code "[$GEL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID477 NumberFormat = 477 + // NumberFormatID478 selects Excelize number-format ID 478 with format code "[$GHC]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID478 NumberFormat = 478 + // NumberFormatID479 selects Excelize number-format ID 479 with format code "[$GHS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID479 NumberFormat = 479 + // NumberFormatID480 selects Excelize number-format ID 480 with format code "[$GIP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID480 NumberFormat = 480 + // NumberFormatID481 selects Excelize number-format ID 481 with format code "[$GMD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID481 NumberFormat = 481 + // NumberFormatID482 selects Excelize number-format ID 482 with format code "[$GNF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID482 NumberFormat = 482 + // NumberFormatID483 selects Excelize number-format ID 483 with format code "[$GRD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID483 NumberFormat = 483 + // NumberFormatID484 selects Excelize number-format ID 484 with format code "[$GTQ]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID484 NumberFormat = 484 + // NumberFormatID485 selects Excelize number-format ID 485 with format code "[$GYD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID485 NumberFormat = 485 + // NumberFormatID486 selects Excelize number-format ID 486 with format code "[$HKD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID486 NumberFormat = 486 + // NumberFormatID487 selects Excelize number-format ID 487 with format code "[$HNL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID487 NumberFormat = 487 + // NumberFormatID488 selects Excelize number-format ID 488 with format code "[$HRK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID488 NumberFormat = 488 + // NumberFormatID489 selects Excelize number-format ID 489 with format code "[$HTG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID489 NumberFormat = 489 + // NumberFormatID490 selects Excelize number-format ID 490 with format code "[$HUF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID490 NumberFormat = 490 + // NumberFormatID491 selects Excelize number-format ID 491 with format code "[$IDR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID491 NumberFormat = 491 + // NumberFormatID492 selects Excelize number-format ID 492 with format code "[$IEP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID492 NumberFormat = 492 + // NumberFormatID493 selects Excelize number-format ID 493 with format code "[$ILS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID493 NumberFormat = 493 + // NumberFormatID494 selects Excelize number-format ID 494 with format code "[$INR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID494 NumberFormat = 494 + // NumberFormatID495 selects Excelize number-format ID 495 with format code "[$IQD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID495 NumberFormat = 495 + // NumberFormatID496 selects Excelize number-format ID 496 with format code "[$IRR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID496 NumberFormat = 496 + // NumberFormatID497 selects Excelize number-format ID 497 with format code "[$ISK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID497 NumberFormat = 497 + // NumberFormatID498 selects Excelize number-format ID 498 with format code "[$ITL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID498 NumberFormat = 498 + // NumberFormatID499 selects Excelize number-format ID 499 with format code "[$JMD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID499 NumberFormat = 499 + // NumberFormatID500 selects Excelize number-format ID 500 with format code "[$JOD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID500 NumberFormat = 500 + // NumberFormatID501 selects Excelize number-format ID 501 with format code "[$JPY]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID501 NumberFormat = 501 + // NumberFormatID502 selects Excelize number-format ID 502 with format code "[$KAF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID502 NumberFormat = 502 + // NumberFormatID503 selects Excelize number-format ID 503 with format code "[$KES]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID503 NumberFormat = 503 + // NumberFormatID504 selects Excelize number-format ID 504 with format code "[$KGS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID504 NumberFormat = 504 + // NumberFormatID505 selects Excelize number-format ID 505 with format code "[$KHR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID505 NumberFormat = 505 + // NumberFormatID506 selects Excelize number-format ID 506 with format code "[$KMF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID506 NumberFormat = 506 + // NumberFormatID507 selects Excelize number-format ID 507 with format code "[$KPW]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID507 NumberFormat = 507 + // NumberFormatID508 selects Excelize number-format ID 508 with format code "[$KRW]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID508 NumberFormat = 508 + // NumberFormatID509 selects Excelize number-format ID 509 with format code "[$KWD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID509 NumberFormat = 509 + // NumberFormatID510 selects Excelize number-format ID 510 with format code "[$KYD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID510 NumberFormat = 510 + // NumberFormatID511 selects Excelize number-format ID 511 with format code "[$KZT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID511 NumberFormat = 511 + // NumberFormatID512 selects Excelize number-format ID 512 with format code "[$LAK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID512 NumberFormat = 512 + // NumberFormatID513 selects Excelize number-format ID 513 with format code "[$LBP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID513 NumberFormat = 513 + // NumberFormatID514 selects Excelize number-format ID 514 with format code "[$LKR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID514 NumberFormat = 514 + // NumberFormatID515 selects Excelize number-format ID 515 with format code "[$LRD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID515 NumberFormat = 515 + // NumberFormatID516 selects Excelize number-format ID 516 with format code "[$LSL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID516 NumberFormat = 516 + // NumberFormatID517 selects Excelize number-format ID 517 with format code "[$LTL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID517 NumberFormat = 517 + // NumberFormatID518 selects Excelize number-format ID 518 with format code "[$LUF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID518 NumberFormat = 518 + // NumberFormatID519 selects Excelize number-format ID 519 with format code "[$LVL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID519 NumberFormat = 519 + // NumberFormatID520 selects Excelize number-format ID 520 with format code "[$LYD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID520 NumberFormat = 520 + // NumberFormatID521 selects Excelize number-format ID 521 with format code "[$MAD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID521 NumberFormat = 521 + // NumberFormatID522 selects Excelize number-format ID 522 with format code "[$MDL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID522 NumberFormat = 522 + // NumberFormatID523 selects Excelize number-format ID 523 with format code "[$MGA]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID523 NumberFormat = 523 + // NumberFormatID524 selects Excelize number-format ID 524 with format code "[$MGF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID524 NumberFormat = 524 + // NumberFormatID525 selects Excelize number-format ID 525 with format code "[$MKD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID525 NumberFormat = 525 + // NumberFormatID526 selects Excelize number-format ID 526 with format code "[$MMK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID526 NumberFormat = 526 + // NumberFormatID527 selects Excelize number-format ID 527 with format code "[$MNT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID527 NumberFormat = 527 + // NumberFormatID528 selects Excelize number-format ID 528 with format code "[$MOP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID528 NumberFormat = 528 + // NumberFormatID529 selects Excelize number-format ID 529 with format code "[$MRO]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID529 NumberFormat = 529 + // NumberFormatID530 selects Excelize number-format ID 530 with format code "[$MTL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID530 NumberFormat = 530 + // NumberFormatID531 selects Excelize number-format ID 531 with format code "[$MUR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID531 NumberFormat = 531 + // NumberFormatID532 selects Excelize number-format ID 532 with format code "[$MVR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID532 NumberFormat = 532 + // NumberFormatID533 selects Excelize number-format ID 533 with format code "[$MWK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID533 NumberFormat = 533 + // NumberFormatID534 selects Excelize number-format ID 534 with format code "[$MXN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID534 NumberFormat = 534 + // NumberFormatID535 selects Excelize number-format ID 535 with format code "[$MXV]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID535 NumberFormat = 535 + // NumberFormatID536 selects Excelize number-format ID 536 with format code "[$MYR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID536 NumberFormat = 536 + // NumberFormatID537 selects Excelize number-format ID 537 with format code "[$MZM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID537 NumberFormat = 537 + // NumberFormatID538 selects Excelize number-format ID 538 with format code "[$MZN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID538 NumberFormat = 538 + // NumberFormatID539 selects Excelize number-format ID 539 with format code "[$NAD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID539 NumberFormat = 539 + // NumberFormatID540 selects Excelize number-format ID 540 with format code "[$NGN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID540 NumberFormat = 540 + // NumberFormatID541 selects Excelize number-format ID 541 with format code "[$NIO]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID541 NumberFormat = 541 + // NumberFormatID542 selects Excelize number-format ID 542 with format code "[$NLG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID542 NumberFormat = 542 + // NumberFormatID543 selects Excelize number-format ID 543 with format code "[$NOK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID543 NumberFormat = 543 + // NumberFormatID544 selects Excelize number-format ID 544 with format code "[$NPR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID544 NumberFormat = 544 + // NumberFormatID545 selects Excelize number-format ID 545 with format code "[$NTD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID545 NumberFormat = 545 + // NumberFormatID546 selects Excelize number-format ID 546 with format code "[$NZD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID546 NumberFormat = 546 + // NumberFormatID547 selects Excelize number-format ID 547 with format code "[$OMR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID547 NumberFormat = 547 + // NumberFormatID548 selects Excelize number-format ID 548 with format code "[$PAB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID548 NumberFormat = 548 + // NumberFormatID549 selects Excelize number-format ID 549 with format code "[$PEN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID549 NumberFormat = 549 + // NumberFormatID550 selects Excelize number-format ID 550 with format code "[$PGK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID550 NumberFormat = 550 + // NumberFormatID551 selects Excelize number-format ID 551 with format code "[$PHP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID551 NumberFormat = 551 + // NumberFormatID552 selects Excelize number-format ID 552 with format code "[$PKR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID552 NumberFormat = 552 + // NumberFormatID553 selects Excelize number-format ID 553 with format code "[$PLN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID553 NumberFormat = 553 + // NumberFormatID554 selects Excelize number-format ID 554 with format code "[$PTE]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID554 NumberFormat = 554 + // NumberFormatID555 selects Excelize number-format ID 555 with format code "[$PYG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID555 NumberFormat = 555 + // NumberFormatID556 selects Excelize number-format ID 556 with format code "[$QAR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID556 NumberFormat = 556 + // NumberFormatID557 selects Excelize number-format ID 557 with format code "[$ROL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID557 NumberFormat = 557 + // NumberFormatID558 selects Excelize number-format ID 558 with format code "[$RON]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID558 NumberFormat = 558 + // NumberFormatID559 selects Excelize number-format ID 559 with format code "[$RSD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID559 NumberFormat = 559 + // NumberFormatID560 selects Excelize number-format ID 560 with format code "[$RUB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID560 NumberFormat = 560 + // NumberFormatID561 selects Excelize number-format ID 561 with format code "[$RUR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID561 NumberFormat = 561 + // NumberFormatID562 selects Excelize number-format ID 562 with format code "[$RWF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID562 NumberFormat = 562 + // NumberFormatID563 selects Excelize number-format ID 563 with format code "[$SAR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID563 NumberFormat = 563 + // NumberFormatID564 selects Excelize number-format ID 564 with format code "[$SBD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID564 NumberFormat = 564 + // NumberFormatID565 selects Excelize number-format ID 565 with format code "[$SCR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID565 NumberFormat = 565 + // NumberFormatID566 selects Excelize number-format ID 566 with format code "[$SDD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID566 NumberFormat = 566 + // NumberFormatID567 selects Excelize number-format ID 567 with format code "[$SDG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID567 NumberFormat = 567 + // NumberFormatID568 selects Excelize number-format ID 568 with format code "[$SDP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID568 NumberFormat = 568 + // NumberFormatID569 selects Excelize number-format ID 569 with format code "[$SEK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID569 NumberFormat = 569 + // NumberFormatID570 selects Excelize number-format ID 570 with format code "[$SGD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID570 NumberFormat = 570 + // NumberFormatID571 selects Excelize number-format ID 571 with format code "[$SHP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID571 NumberFormat = 571 + // NumberFormatID572 selects Excelize number-format ID 572 with format code "[$SIT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID572 NumberFormat = 572 + // NumberFormatID573 selects Excelize number-format ID 573 with format code "[$SKK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID573 NumberFormat = 573 + // NumberFormatID574 selects Excelize number-format ID 574 with format code "[$SLL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID574 NumberFormat = 574 + // NumberFormatID575 selects Excelize number-format ID 575 with format code "[$SOS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID575 NumberFormat = 575 + // NumberFormatID576 selects Excelize number-format ID 576 with format code "[$SPL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID576 NumberFormat = 576 + // NumberFormatID577 selects Excelize number-format ID 577 with format code "[$SRD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID577 NumberFormat = 577 + // NumberFormatID578 selects Excelize number-format ID 578 with format code "[$SRG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID578 NumberFormat = 578 + // NumberFormatID579 selects Excelize number-format ID 579 with format code "[$STD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID579 NumberFormat = 579 + // NumberFormatID580 selects Excelize number-format ID 580 with format code "[$SVC]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID580 NumberFormat = 580 + // NumberFormatID581 selects Excelize number-format ID 581 with format code "[$SYP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID581 NumberFormat = 581 + // NumberFormatID582 selects Excelize number-format ID 582 with format code "[$SZL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID582 NumberFormat = 582 + // NumberFormatID583 selects Excelize number-format ID 583 with format code "[$THB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID583 NumberFormat = 583 + // NumberFormatID584 selects Excelize number-format ID 584 with format code "[$TJR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID584 NumberFormat = 584 + // NumberFormatID585 selects Excelize number-format ID 585 with format code "[$TJS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID585 NumberFormat = 585 + // NumberFormatID586 selects Excelize number-format ID 586 with format code "[$TMM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID586 NumberFormat = 586 + // NumberFormatID587 selects Excelize number-format ID 587 with format code "[$TMT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID587 NumberFormat = 587 + // NumberFormatID588 selects Excelize number-format ID 588 with format code "[$TND]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID588 NumberFormat = 588 + // NumberFormatID589 selects Excelize number-format ID 589 with format code "[$TOP]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID589 NumberFormat = 589 + // NumberFormatID590 selects Excelize number-format ID 590 with format code "[$TRL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID590 NumberFormat = 590 + // NumberFormatID591 selects Excelize number-format ID 591 with format code "[$TRY]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID591 NumberFormat = 591 + // NumberFormatID592 selects Excelize number-format ID 592 with format code "[$TTD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID592 NumberFormat = 592 + // NumberFormatID593 selects Excelize number-format ID 593 with format code "[$TWD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID593 NumberFormat = 593 + // NumberFormatID594 selects Excelize number-format ID 594 with format code "[$TZS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID594 NumberFormat = 594 + // NumberFormatID595 selects Excelize number-format ID 595 with format code "[$UAH]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID595 NumberFormat = 595 + // NumberFormatID596 selects Excelize number-format ID 596 with format code "[$UGX]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID596 NumberFormat = 596 + // NumberFormatID597 selects Excelize number-format ID 597 with format code "[$USD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID597 NumberFormat = 597 + // NumberFormatID598 selects Excelize number-format ID 598 with format code "[$USN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID598 NumberFormat = 598 + // NumberFormatID599 selects Excelize number-format ID 599 with format code "[$USS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID599 NumberFormat = 599 + // NumberFormatID600 selects Excelize number-format ID 600 with format code "[$UYI]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID600 NumberFormat = 600 + // NumberFormatID601 selects Excelize number-format ID 601 with format code "[$UYU]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID601 NumberFormat = 601 + // NumberFormatID602 selects Excelize number-format ID 602 with format code "[$UZS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID602 NumberFormat = 602 + // NumberFormatID603 selects Excelize number-format ID 603 with format code "[$VEB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID603 NumberFormat = 603 + // NumberFormatID604 selects Excelize number-format ID 604 with format code "[$VEF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID604 NumberFormat = 604 + // NumberFormatID605 selects Excelize number-format ID 605 with format code "[$VND]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID605 NumberFormat = 605 + // NumberFormatID606 selects Excelize number-format ID 606 with format code "[$VUV]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID606 NumberFormat = 606 + // NumberFormatID607 selects Excelize number-format ID 607 with format code "[$WST]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID607 NumberFormat = 607 + // NumberFormatID608 selects Excelize number-format ID 608 with format code "[$XAF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID608 NumberFormat = 608 + // NumberFormatID609 selects Excelize number-format ID 609 with format code "[$XAG]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID609 NumberFormat = 609 + // NumberFormatID610 selects Excelize number-format ID 610 with format code "[$XAU]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID610 NumberFormat = 610 + // NumberFormatID611 selects Excelize number-format ID 611 with format code "[$XB5]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID611 NumberFormat = 611 + // NumberFormatID612 selects Excelize number-format ID 612 with format code "[$XBA]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID612 NumberFormat = 612 + // NumberFormatID613 selects Excelize number-format ID 613 with format code "[$XBB]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID613 NumberFormat = 613 + // NumberFormatID614 selects Excelize number-format ID 614 with format code "[$XBC]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID614 NumberFormat = 614 + // NumberFormatID615 selects Excelize number-format ID 615 with format code "[$XBD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID615 NumberFormat = 615 + // NumberFormatID616 selects Excelize number-format ID 616 with format code "[$XCD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID616 NumberFormat = 616 + // NumberFormatID617 selects Excelize number-format ID 617 with format code "[$XDR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID617 NumberFormat = 617 + // NumberFormatID618 selects Excelize number-format ID 618 with format code "[$XFO]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID618 NumberFormat = 618 + // NumberFormatID619 selects Excelize number-format ID 619 with format code "[$XFU]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID619 NumberFormat = 619 + // NumberFormatID620 selects Excelize number-format ID 620 with format code "[$XOF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID620 NumberFormat = 620 + // NumberFormatID621 selects Excelize number-format ID 621 with format code "[$XPD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID621 NumberFormat = 621 + // NumberFormatID622 selects Excelize number-format ID 622 with format code "[$XPF]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID622 NumberFormat = 622 + // NumberFormatID623 selects Excelize number-format ID 623 with format code "[$XPT]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID623 NumberFormat = 623 + // NumberFormatID624 selects Excelize number-format ID 624 with format code "[$XTS]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID624 NumberFormat = 624 + // NumberFormatID625 selects Excelize number-format ID 625 with format code "[$XXX]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID625 NumberFormat = 625 + // NumberFormatID626 selects Excelize number-format ID 626 with format code "[$YER]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID626 NumberFormat = 626 + // NumberFormatID627 selects Excelize number-format ID 627 with format code "[$YUM]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID627 NumberFormat = 627 + // NumberFormatID628 selects Excelize number-format ID 628 with format code "[$ZAR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID628 NumberFormat = 628 + // NumberFormatID629 selects Excelize number-format ID 629 with format code "[$ZMK]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID629 NumberFormat = 629 + // NumberFormatID630 selects Excelize number-format ID 630 with format code "[$ZMW]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID630 NumberFormat = 630 + // NumberFormatID631 selects Excelize number-format ID 631 with format code "[$ZWD]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID631 NumberFormat = 631 + // NumberFormatID632 selects Excelize number-format ID 632 with format code "[$ZWL]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID632 NumberFormat = 632 + // NumberFormatID633 selects Excelize number-format ID 633 with format code "[$ZWN]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID633 NumberFormat = 633 + // NumberFormatID634 selects Excelize number-format ID 634 with format code "[$ZWR]\\ #,##0.00". + // + // See https://xuri.me/excelize/en/style.html + NumberFormatID634 NumberFormat = 634 +) + +func (value NumberFormat) Code() (string, bool) { + switch value { + case NumberFormatID0: + return "general", true + case NumberFormatID1: + return "0", true + case NumberFormatID2: + return "0.00", true + case NumberFormatID3: + return "#,##0", true + case NumberFormatID4: + return "#,##0.00", true + case NumberFormatID9: + return "0%", true + case NumberFormatID10: + return "0.00%", true + case NumberFormatID11: + return "0.00E+00", true + case NumberFormatID12: + return "# ?/?", true + case NumberFormatID13: + return "# ??/??", true + case NumberFormatID14: + return "mm-dd-yy", true + case NumberFormatID15: + return "d-mmm-yy", true + case NumberFormatID16: + return "d-mmm", true + case NumberFormatID17: + return "mmm-yy", true + case NumberFormatID18: + return "h:mm AM/PM", true + case NumberFormatID19: + return "h:mm:ss AM/PM", true + case NumberFormatID20: + return "hh:mm", true + case NumberFormatID21: + return "hh:mm:ss", true + case NumberFormatID22: + return "m/d/yy hh:mm", true + case NumberFormatID37: + return "#,##0 ;(#,##0)", true + case NumberFormatID38: + return "#,##0 ;[red](#,##0)", true + case NumberFormatID39: + return "#,##0.00 ;(#,##0.00)", true + case NumberFormatID40: + return "#,##0.00 ;[red](#,##0.00)", true + case NumberFormatID41: + return "_(* #,##0_);_(* \\(#,##0\\);_(* \"-\"_);_(@_)", true + case NumberFormatID42: + return "_(\"$\"* #,##0_);_(\"$\"* \\(#,##0\\);_(\"$\"* \"-\"_);_(@_)", true + case NumberFormatID43: + return "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)", true + case NumberFormatID44: + return "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)", true + case NumberFormatID45: + return "mm:ss", true + case NumberFormatID46: + return "[h]:mm:ss", true + case NumberFormatID47: + return "mm:ss.0", true + case NumberFormatID48: + return "##0.0E+0", true + case NumberFormatID49: + return "@", true + case NumberFormatID164: + return "\"¥\"#,##0.00", true + case NumberFormatID165: + return "[$$-409]#,##0.00", true + case NumberFormatID166: + return "[$$-45C]#,##0.00", true + case NumberFormatID167: + return "[$$-1004]#,##0.00", true + case NumberFormatID168: + return "[$$-404]#,##0.00", true + case NumberFormatID169: + return "[$$-C09]#,##0.00", true + case NumberFormatID170: + return "[$$-2809]#,##0.00", true + case NumberFormatID171: + return "[$$-1009]#,##0.00", true + case NumberFormatID172: + return "[$$-2009]#,##0.00", true + case NumberFormatID173: + return "[$$-1409]#,##0.00", true + case NumberFormatID174: + return "[$$-4809]#,##0.00", true + case NumberFormatID175: + return "[$$-2C09]#,##0.00", true + case NumberFormatID176: + return "[$$-2409]#,##0.00", true + case NumberFormatID177: + return "[$$-1000]#,##0.00", true + case NumberFormatID178: + return "#,##0.00\\ [$$-C0C]", true + case NumberFormatID179: + return "[$$-475]#,##0.00", true + case NumberFormatID180: + return "[$$-83E]#,##0.00", true + case NumberFormatID181: + return "[$$-86B]\\ #,##0.00", true + case NumberFormatID182: + return "[$$-340A]\\ #,##0.00", true + case NumberFormatID183: + return "[$$-240A]#,##0.00", true + case NumberFormatID184: + return "[$$-300A]\\ #,##0.00", true + case NumberFormatID185: + return "[$$-440A]#,##0.00", true + case NumberFormatID186: + return "[$$-80A]#,##0.00", true + case NumberFormatID187: + return "[$$-500A]#,##0.00", true + case NumberFormatID188: + return "[$$-540A]#,##0.00", true + case NumberFormatID189: + return "[$$-380A]\\ #,##0.00", true + case NumberFormatID190: + return "[$£-809]#,##0.00", true + case NumberFormatID191: + return "[$£-491]#,##0.00", true + case NumberFormatID192: + return "[$£-452]#,##0.00", true + case NumberFormatID193: + return "[$¥-804]#,##0.00", true + case NumberFormatID194: + return "[$¥-411]#,##0.00", true + case NumberFormatID195: + return "[$¥-478]#,##0.00", true + case NumberFormatID196: + return "[$¥-451]#,##0.00", true + case NumberFormatID197: + return "[$¥-480]#,##0.00", true + case NumberFormatID198: + return "#,##0.00\\ [$֏-42B]", true + case NumberFormatID199: + return "[$؋-463]#,##0.00", true + case NumberFormatID200: + return "[$؋-48C]#,##0.00", true + case NumberFormatID201: + return "[$৳-845]\\ #,##0.00", true + case NumberFormatID202: + return "#,##0.00[$៛-453]", true + case NumberFormatID203: + return "[$₡-140A]#,##0.00", true + case NumberFormatID204: + return "[$₦-468]\\ #,##0.00", true + case NumberFormatID205: + return "[$₦-470]\\ #,##0.00", true + case NumberFormatID206: + return "[$₩-412]#,##0.00", true + case NumberFormatID207: + return "[$₪-40D]\\ #,##0.00", true + case NumberFormatID208: + return "#,##0.00\\ [$₫-42A]", true + case NumberFormatID209: + return "#,##0.00\\ [$€-42D]", true + case NumberFormatID210: + return "#,##0.00\\ [$€-47E]", true + case NumberFormatID211: + return "#,##0.00\\ [$€-403]", true + case NumberFormatID212: + return "#,##0.00\\ [$€-483]", true + case NumberFormatID213: + return "[$€-813]\\ #,##0.00", true + case NumberFormatID214: + return "[$€-413]\\ #,##0.00", true + case NumberFormatID215: + return "[$€-1809]#,##0.00", true + case NumberFormatID216: + return "#,##0.00\\ [$€-425]", true + case NumberFormatID217: + return "[$€-2]\\ #,##0.00", true + case NumberFormatID218: + return "#,##0.00\\ [$€-1]", true + case NumberFormatID219: + return "#,##0.00\\ [$€-40B]", true + case NumberFormatID220: + return "#,##0.00\\ [$€-80C]", true + case NumberFormatID221: + return "#,##0.00\\ [$€-40C]", true + case NumberFormatID222: + return "#,##0.00\\ [$€-140C]", true + case NumberFormatID223: + return "#,##0.00\\ [$€-180C]", true + case NumberFormatID224: + return "[$€-200C]#,##0.00", true + case NumberFormatID225: + return "#,##0.00\\ [$€-456]", true + case NumberFormatID226: + return "#,##0.00\\ [$€-C07]", true + case NumberFormatID227: + return "#,##0.00\\ [$€-407]", true + case NumberFormatID228: + return "#,##0.00\\ [$€-1007]", true + case NumberFormatID229: + return "#,##0.00\\ [$€-408]", true + case NumberFormatID230: + return "#,##0.00\\ [$€-243B]", true + case NumberFormatID231: + return "[$€-83C]#,##0.00", true + case NumberFormatID232: + return "[$€-410]\\ #,##0.00", true + case NumberFormatID233: + return "[$€-476]#,##0.00", true + case NumberFormatID234: + return "#,##0.00\\ [$€-2C1A]", true + case NumberFormatID235: + return "[$€-426]\\ #,##0.00", true + case NumberFormatID236: + return "#,##0.00\\ [$€-427]", true + case NumberFormatID237: + return "#,##0.00\\ [$€-82E]", true + case NumberFormatID238: + return "#,##0.00\\ [$€-46E]", true + case NumberFormatID239: + return "[$€-43A]#,##0.00", true + case NumberFormatID240: + return "#,##0.00\\ [$€-C3B]", true + case NumberFormatID241: + return "#,##0.00\\ [$€-482]", true + case NumberFormatID242: + return "#,##0.00\\ [$€-816]", true + case NumberFormatID243: + return "#,##0.00\\ [$€-301A]", true + case NumberFormatID244: + return "#,##0.00\\ [$€-203B]", true + case NumberFormatID245: + return "#,##0.00\\ [$€-41B]", true + case NumberFormatID246: + return "#,##0.00\\ [$€-424]", true + case NumberFormatID247: + return "#,##0.00\\ [$€-C0A]", true + case NumberFormatID248: + return "#,##0.00\\ [$€-81D]", true + case NumberFormatID249: + return "#,##0.00\\ [$€-484]", true + case NumberFormatID250: + return "#,##0.00\\ [$€-42E]", true + case NumberFormatID251: + return "[$€-462]\\ #,##0.00", true + case NumberFormatID252: + return "#,##0.00\\ [$₭-454]", true + case NumberFormatID253: + return "#,##0.00\\ [$₮-450]", true + case NumberFormatID254: + return "[$₮-C50]#,##0.00", true + case NumberFormatID255: + return "[$₱-3409]#,##0.00", true + case NumberFormatID256: + return "[$₱-464]#,##0.00", true + case NumberFormatID257: + return "#,##0.00[$₴-422]", true + case NumberFormatID258: + return "[$₸-43F]#,##0.00", true + case NumberFormatID259: + return "[$₹-460]#,##0.00", true + case NumberFormatID260: + return "[$₹-4009]\\ #,##0.00", true + case NumberFormatID261: + return "[$₹-447]\\ #,##0.00", true + case NumberFormatID262: + return "[$₹-439]\\ #,##0.00", true + case NumberFormatID263: + return "[$₹-44B]\\ #,##0.00", true + case NumberFormatID264: + return "[$₹-860]#,##0.00", true + case NumberFormatID265: + return "[$₹-457]\\ #,##0.00", true + case NumberFormatID266: + return "[$₹-458]#,##0.00", true + case NumberFormatID267: + return "[$₹-44E]\\ #,##0.00", true + case NumberFormatID268: + return "[$₹-861]#,##0.00", true + case NumberFormatID269: + return "[$₹-448]\\ #,##0.00", true + case NumberFormatID270: + return "[$₹-446]\\ #,##0.00", true + case NumberFormatID271: + return "[$₹-44F]\\ #,##0.00", true + case NumberFormatID272: + return "[$₹-459]#,##0.00", true + case NumberFormatID273: + return "[$₹-449]\\ #,##0.00", true + case NumberFormatID274: + return "[$₹-820]#,##0.00", true + case NumberFormatID275: + return "#,##0.00\\ [$₺-41F]", true + case NumberFormatID276: + return "#,##0.00\\ [$₼-42C]", true + case NumberFormatID277: + return "#,##0.00\\ [$₼-82C]", true + case NumberFormatID278: + return "#,##0.00\\ [$₽-419]", true + case NumberFormatID279: + return "#,##0.00[$₽-485]", true + case NumberFormatID280: + return "#,##0.00\\ [$₾-437]", true + case NumberFormatID281: + return "[$B/.-180A]\\ #,##0.00", true + case NumberFormatID282: + return "[$Br-472]#,##0.00", true + case NumberFormatID283: + return "[$Br-477]#,##0.00", true + case NumberFormatID284: + return "#,##0.00[$Br-473]", true + case NumberFormatID285: + return "[$Bs-46B]\\ #,##0.00", true + case NumberFormatID286: + return "[$Bs-400A]\\ #,##0.00", true + case NumberFormatID287: + return "[$Bs.-200A]\\ #,##0.00", true + case NumberFormatID288: + return "[$BWP-832]\\ #,##0.00", true + case NumberFormatID289: + return "[$C$-4C0A]#,##0.00", true + case NumberFormatID290: + return "[$CA$-85D]#,##0.00", true + case NumberFormatID291: + return "[$CA$-47C]#,##0.00", true + case NumberFormatID292: + return "[$CA$-45D]#,##0.00", true + case NumberFormatID293: + return "[$CFA-340C]#,##0.00", true + case NumberFormatID294: + return "[$CFA-280C]#,##0.00", true + case NumberFormatID295: + return "#,##0.00\\ [$CFA-867]", true + case NumberFormatID296: + return "#,##0.00\\ [$CFA-488]", true + case NumberFormatID297: + return "#,##0.00\\ [$CHF-100C]", true + case NumberFormatID298: + return "[$CHF-1407]\\ #,##0.00", true + case NumberFormatID299: + return "[$CHF-807]\\ #,##0.00", true + case NumberFormatID300: + return "[$CHF-810]\\ #,##0.00", true + case NumberFormatID301: + return "[$CHF-417]\\ #,##0.00", true + case NumberFormatID302: + return "[$CLP-47A]\\ #,##0.00", true + case NumberFormatID303: + return "[$CN¥-850]#,##0.00", true + case NumberFormatID304: + return "#,##0.00\\ [$DZD-85F]", true + case NumberFormatID305: + return "[$FCFA-2C0C]#,##0.00", true + case NumberFormatID306: + return "#,##0.00\\ [$Ft-40E]", true + case NumberFormatID307: + return "[$G-3C0C]#,##0.00", true + case NumberFormatID308: + return "[$Gs.-3C0A]\\ #,##0.00", true + case NumberFormatID309: + return "[$GTQ-486]#,##0.00", true + case NumberFormatID310: + return "[$HK$-C04]#,##0.00", true + case NumberFormatID311: + return "[$HK$-3C09]#,##0.00", true + case NumberFormatID312: + return "#,##0.00\\ [$HRK-41A]", true + case NumberFormatID313: + return "[$IDR-3809]#,##0.00", true + case NumberFormatID314: + return "[$IQD-492]#,##0.00", true + case NumberFormatID315: + return "#,##0.00\\ [$ISK-40F]", true + case NumberFormatID316: + return "[$K-455]#,##0.00", true + case NumberFormatID317: + return "#,##0.00\\ [$Kč-405]", true + case NumberFormatID318: + return "#,##0.00\\ [$KM-141A]", true + case NumberFormatID319: + return "#,##0.00\\ [$KM-101A]", true + case NumberFormatID320: + return "#,##0.00\\ [$KM-181A]", true + case NumberFormatID321: + return "[$kr-438]\\ #,##0.00", true + case NumberFormatID322: + return "[$kr-43B]\\ #,##0.00", true + case NumberFormatID323: + return "#,##0.00\\ [$kr-83B]", true + case NumberFormatID324: + return "[$kr-414]\\ #,##0.00", true + case NumberFormatID325: + return "[$kr-814]\\ #,##0.00", true + case NumberFormatID326: + return "#,##0.00\\ [$kr-41D]", true + case NumberFormatID327: + return "[$kr.-406]\\ #,##0.00", true + case NumberFormatID328: + return "[$kr.-46F]\\ #,##0.00", true + case NumberFormatID329: + return "[$Ksh-441]#,##0.00", true + case NumberFormatID330: + return "[$L-818]#,##0.00", true + case NumberFormatID331: + return "[$L-819]#,##0.00", true + case NumberFormatID332: + return "[$L-480A]\\ #,##0.00", true + case NumberFormatID333: + return "#,##0.00\\ [$Lekë-41C]", true + case NumberFormatID334: + return "[$MAD-45F]#,##0.00", true + case NumberFormatID335: + return "[$MAD-380C]#,##0.00", true + case NumberFormatID336: + return "#,##0.00\\ [$MAD-105F]", true + case NumberFormatID337: + return "[$MOP$-1404]#,##0.00", true + case NumberFormatID338: + return "#,##0.00\\ [$MVR-465]_-", true + case NumberFormatID339: + return "#,##0.00[$Nfk-873]", true + case NumberFormatID340: + return "[$NGN-466]#,##0.00", true + case NumberFormatID341: + return "[$NGN-467]#,##0.00", true + case NumberFormatID342: + return "[$NGN-469]#,##0.00", true + case NumberFormatID343: + return "[$NGN-471]#,##0.00", true + case NumberFormatID344: + return "[$NOK-103B]\\ #,##0.00", true + case NumberFormatID345: + return "[$NOK-183B]\\ #,##0.00", true + case NumberFormatID346: + return "[$NZ$-481]#,##0.00", true + case NumberFormatID347: + return "[$PKR-859]\\ #,##0.00", true + case NumberFormatID348: + return "[$PYG-474]#,##0.00", true + case NumberFormatID349: + return "[$Q-100A]#,##0.00", true + case NumberFormatID350: + return "[$R-436]\\ #,##0.00", true + case NumberFormatID351: + return "[$R-1C09]\\ #,##0.00", true + case NumberFormatID352: + return "[$R-435]\\ #,##0.00", true + case NumberFormatID353: + return "[$R$-416]\\ #,##0.00", true + case NumberFormatID354: + return "[$RD$-1C0A]#,##0.00", true + case NumberFormatID355: + return "#,##0.00\\ [$RF-487]", true + case NumberFormatID356: + return "[$RM-4409]#,##0.00", true + case NumberFormatID357: + return "[$RM-43E]#,##0.00", true + case NumberFormatID358: + return "#,##0.00\\ [$RON-418]", true + case NumberFormatID359: + return "[$Rp-421]#,##0.00", true + case NumberFormatID360: + return "[$Rs-420]#,##0.00_-", true + case NumberFormatID361: + return "[$Rs.-849]\\ #,##0.00", true + case NumberFormatID362: + return "#,##0.00\\ [$RSD-81A]", true + case NumberFormatID363: + return "#,##0.00\\ [$RSD-C1A]", true + case NumberFormatID364: + return "#,##0.00\\ [$RUB-46D]", true + case NumberFormatID365: + return "#,##0.00\\ [$RUB-444]", true + case NumberFormatID366: + return "[$S/.-C6B]\\ #,##0.00", true + case NumberFormatID367: + return "[$S/.-280A]\\ #,##0.00", true + case NumberFormatID368: + return "#,##0.00\\ [$SEK-143B]", true + case NumberFormatID369: + return "#,##0.00\\ [$SEK-1C3B]", true + case NumberFormatID370: + return "#,##0.00\\ [$soʻm-443]", true + case NumberFormatID371: + return "#,##0.00\\ [$soʻm-843]", true + case NumberFormatID372: + return "#,##0.00\\ [$SYP-45A]", true + case NumberFormatID373: + return "[$THB-41E]#,##0.00", true + case NumberFormatID374: + return "#,##0.00[$TMT-442]", true + case NumberFormatID375: + return "[$US$-3009]#,##0.00", true + case NumberFormatID376: + return "[$ZAR-46C]\\ #,##0.00", true + case NumberFormatID377: + return "[$ZAR-430]#,##0.00", true + case NumberFormatID378: + return "[$ZAR-431]#,##0.00", true + case NumberFormatID379: + return "[$ZAR-432]\\ #,##0.00", true + case NumberFormatID380: + return "[$ZAR-433]#,##0.00", true + case NumberFormatID381: + return "[$ZAR-434]\\ #,##0.00", true + case NumberFormatID382: + return "#,##0.00\\ [$zł-415]", true + case NumberFormatID383: + return "#,##0.00\\ [$ден-42F]", true + case NumberFormatID384: + return "#,##0.00\\ [$КМ-201A]", true + case NumberFormatID385: + return "#,##0.00\\ [$КМ-1C1A]", true + case NumberFormatID386: + return "#,##0.00\\ [$лв.-402]", true + case NumberFormatID387: + return "#,##0.00\\ [$р.-423]", true + case NumberFormatID388: + return "#,##0.00\\ [$сом-440]", true + case NumberFormatID389: + return "#,##0.00\\ [$сом-428]", true + case NumberFormatID390: + return "[$ج.م.-C01]\\ #,##0.00_-", true + case NumberFormatID391: + return "[$د.أ.-2C01]\\ #,##0.00_-", true + case NumberFormatID392: + return "[$د.إ.-3801]\\ #,##0.00_-", true + case NumberFormatID393: + return "[$د.ب.-3C01]\\ #,##0.00_-", true + case NumberFormatID394: + return "[$د.ت.-1C01]\\ #,##0.00_-", true + case NumberFormatID395: + return "[$د.ج.-1401]\\ #,##0.00_-", true + case NumberFormatID396: + return "[$د.ع.-801]\\ #,##0.00_-", true + case NumberFormatID397: + return "[$د.ك.-3401]\\ #,##0.00_-", true + case NumberFormatID398: + return "[$د.ل.-1001]#,##0.00_-", true + case NumberFormatID399: + return "[$د.م.-1801]\\ #,##0.00_-", true + case NumberFormatID400: + return "[$ر-846]\\ #,##0.00", true + case NumberFormatID401: + return "[$ر.س.-401]\\ #,##0.00_-", true + case NumberFormatID402: + return "[$ر.ع.-2001]\\ #,##0.00_-", true + case NumberFormatID403: + return "[$ر.ق.-4001]\\ #,##0.00_-", true + case NumberFormatID404: + return "[$ر.ي.-2401]\\ #,##0.00_-", true + case NumberFormatID405: + return "[$ریال-429]#,##0.00_-", true + case NumberFormatID406: + return "[$ل.س.-2801]\\ #,##0.00_-", true + case NumberFormatID407: + return "[$ل.ل.-3001]\\ #,##0.00_-", true + case NumberFormatID408: + return "[$ብር-45E]#,##0.00", true + case NumberFormatID409: + return "[$रू-461]#,##0.00", true + case NumberFormatID410: + return "[$රු.-45B]\\ #,##0.00", true + case NumberFormatID411: + return "[$ADP]\\ #,##0.00", true + case NumberFormatID412: + return "[$AED]\\ #,##0.00", true + case NumberFormatID413: + return "[$AFA]\\ #,##0.00", true + case NumberFormatID414: + return "[$AFN]\\ #,##0.00", true + case NumberFormatID415: + return "[$ALL]\\ #,##0.00", true + case NumberFormatID416: + return "[$AMD]\\ #,##0.00", true + case NumberFormatID417: + return "[$ANG]\\ #,##0.00", true + case NumberFormatID418: + return "[$AOA]\\ #,##0.00", true + case NumberFormatID419: + return "[$ARS]\\ #,##0.00", true + case NumberFormatID420: + return "[$ATS]\\ #,##0.00", true + case NumberFormatID421: + return "[$AUD]\\ #,##0.00", true + case NumberFormatID422: + return "[$AWG]\\ #,##0.00", true + case NumberFormatID423: + return "[$AZM]\\ #,##0.00", true + case NumberFormatID424: + return "[$AZN]\\ #,##0.00", true + case NumberFormatID425: + return "[$BAM]\\ #,##0.00", true + case NumberFormatID426: + return "[$BBD]\\ #,##0.00", true + case NumberFormatID427: + return "[$BDT]\\ #,##0.00", true + case NumberFormatID428: + return "[$BEF]\\ #,##0.00", true + case NumberFormatID429: + return "[$BGL]\\ #,##0.00", true + case NumberFormatID430: + return "[$BGN]\\ #,##0.00", true + case NumberFormatID431: + return "[$BHD]\\ #,##0.00", true + case NumberFormatID432: + return "[$BIF]\\ #,##0.00", true + case NumberFormatID433: + return "[$BMD]\\ #,##0.00", true + case NumberFormatID434: + return "[$BND]\\ #,##0.00", true + case NumberFormatID435: + return "[$BOB]\\ #,##0.00", true + case NumberFormatID436: + return "[$BOV]\\ #,##0.00", true + case NumberFormatID437: + return "[$BRL]\\ #,##0.00", true + case NumberFormatID438: + return "[$BSD]\\ #,##0.00", true + case NumberFormatID439: + return "[$BTN]\\ #,##0.00", true + case NumberFormatID440: + return "[$BWP]\\ #,##0.00", true + case NumberFormatID441: + return "[$BYR]\\ #,##0.00", true + case NumberFormatID442: + return "[$BZD]\\ #,##0.00", true + case NumberFormatID443: + return "[$CAD]\\ #,##0.00", true + case NumberFormatID444: + return "[$CDF]\\ #,##0.00", true + case NumberFormatID445: + return "[$CHE]\\ #,##0.00", true + case NumberFormatID446: + return "[$CHF]\\ #,##0.00", true + case NumberFormatID447: + return "[$CHW]\\ #,##0.00", true + case NumberFormatID448: + return "[$CLF]\\ #,##0.00", true + case NumberFormatID449: + return "[$CLP]\\ #,##0.00", true + case NumberFormatID450: + return "[$CNY]\\ #,##0.00", true + case NumberFormatID451: + return "[$COP]\\ #,##0.00", true + case NumberFormatID452: + return "[$COU]\\ #,##0.00", true + case NumberFormatID453: + return "[$CRC]\\ #,##0.00", true + case NumberFormatID454: + return "[$CSD]\\ #,##0.00", true + case NumberFormatID455: + return "[$CUC]\\ #,##0.00", true + case NumberFormatID456: + return "[$CVE]\\ #,##0.00", true + case NumberFormatID457: + return "[$CYP]\\ #,##0.00", true + case NumberFormatID458: + return "[$CZK]\\ #,##0.00", true + case NumberFormatID459: + return "[$DEM]\\ #,##0.00", true + case NumberFormatID460: + return "[$DJF]\\ #,##0.00", true + case NumberFormatID461: + return "[$DKK]\\ #,##0.00", true + case NumberFormatID462: + return "[$DOP]\\ #,##0.00", true + case NumberFormatID463: + return "[$DZD]\\ #,##0.00", true + case NumberFormatID464: + return "[$ECS]\\ #,##0.00", true + case NumberFormatID465: + return "[$ECV]\\ #,##0.00", true + case NumberFormatID466: + return "[$EEK]\\ #,##0.00", true + case NumberFormatID467: + return "[$EGP]\\ #,##0.00", true + case NumberFormatID468: + return "[$ERN]\\ #,##0.00", true + case NumberFormatID469: + return "[$ESP]\\ #,##0.00", true + case NumberFormatID470: + return "[$ETB]\\ #,##0.00", true + case NumberFormatID471: + return "[$EUR]\\ #,##0.00", true + case NumberFormatID472: + return "[$FIM]\\ #,##0.00", true + case NumberFormatID473: + return "[$FJD]\\ #,##0.00", true + case NumberFormatID474: + return "[$FKP]\\ #,##0.00", true + case NumberFormatID475: + return "[$FRF]\\ #,##0.00", true + case NumberFormatID476: + return "[$GBP]\\ #,##0.00", true + case NumberFormatID477: + return "[$GEL]\\ #,##0.00", true + case NumberFormatID478: + return "[$GHC]\\ #,##0.00", true + case NumberFormatID479: + return "[$GHS]\\ #,##0.00", true + case NumberFormatID480: + return "[$GIP]\\ #,##0.00", true + case NumberFormatID481: + return "[$GMD]\\ #,##0.00", true + case NumberFormatID482: + return "[$GNF]\\ #,##0.00", true + case NumberFormatID483: + return "[$GRD]\\ #,##0.00", true + case NumberFormatID484: + return "[$GTQ]\\ #,##0.00", true + case NumberFormatID485: + return "[$GYD]\\ #,##0.00", true + case NumberFormatID486: + return "[$HKD]\\ #,##0.00", true + case NumberFormatID487: + return "[$HNL]\\ #,##0.00", true + case NumberFormatID488: + return "[$HRK]\\ #,##0.00", true + case NumberFormatID489: + return "[$HTG]\\ #,##0.00", true + case NumberFormatID490: + return "[$HUF]\\ #,##0.00", true + case NumberFormatID491: + return "[$IDR]\\ #,##0.00", true + case NumberFormatID492: + return "[$IEP]\\ #,##0.00", true + case NumberFormatID493: + return "[$ILS]\\ #,##0.00", true + case NumberFormatID494: + return "[$INR]\\ #,##0.00", true + case NumberFormatID495: + return "[$IQD]\\ #,##0.00", true + case NumberFormatID496: + return "[$IRR]\\ #,##0.00", true + case NumberFormatID497: + return "[$ISK]\\ #,##0.00", true + case NumberFormatID498: + return "[$ITL]\\ #,##0.00", true + case NumberFormatID499: + return "[$JMD]\\ #,##0.00", true + case NumberFormatID500: + return "[$JOD]\\ #,##0.00", true + case NumberFormatID501: + return "[$JPY]\\ #,##0.00", true + case NumberFormatID502: + return "[$KAF]\\ #,##0.00", true + case NumberFormatID503: + return "[$KES]\\ #,##0.00", true + case NumberFormatID504: + return "[$KGS]\\ #,##0.00", true + case NumberFormatID505: + return "[$KHR]\\ #,##0.00", true + case NumberFormatID506: + return "[$KMF]\\ #,##0.00", true + case NumberFormatID507: + return "[$KPW]\\ #,##0.00", true + case NumberFormatID508: + return "[$KRW]\\ #,##0.00", true + case NumberFormatID509: + return "[$KWD]\\ #,##0.00", true + case NumberFormatID510: + return "[$KYD]\\ #,##0.00", true + case NumberFormatID511: + return "[$KZT]\\ #,##0.00", true + case NumberFormatID512: + return "[$LAK]\\ #,##0.00", true + case NumberFormatID513: + return "[$LBP]\\ #,##0.00", true + case NumberFormatID514: + return "[$LKR]\\ #,##0.00", true + case NumberFormatID515: + return "[$LRD]\\ #,##0.00", true + case NumberFormatID516: + return "[$LSL]\\ #,##0.00", true + case NumberFormatID517: + return "[$LTL]\\ #,##0.00", true + case NumberFormatID518: + return "[$LUF]\\ #,##0.00", true + case NumberFormatID519: + return "[$LVL]\\ #,##0.00", true + case NumberFormatID520: + return "[$LYD]\\ #,##0.00", true + case NumberFormatID521: + return "[$MAD]\\ #,##0.00", true + case NumberFormatID522: + return "[$MDL]\\ #,##0.00", true + case NumberFormatID523: + return "[$MGA]\\ #,##0.00", true + case NumberFormatID524: + return "[$MGF]\\ #,##0.00", true + case NumberFormatID525: + return "[$MKD]\\ #,##0.00", true + case NumberFormatID526: + return "[$MMK]\\ #,##0.00", true + case NumberFormatID527: + return "[$MNT]\\ #,##0.00", true + case NumberFormatID528: + return "[$MOP]\\ #,##0.00", true + case NumberFormatID529: + return "[$MRO]\\ #,##0.00", true + case NumberFormatID530: + return "[$MTL]\\ #,##0.00", true + case NumberFormatID531: + return "[$MUR]\\ #,##0.00", true + case NumberFormatID532: + return "[$MVR]\\ #,##0.00", true + case NumberFormatID533: + return "[$MWK]\\ #,##0.00", true + case NumberFormatID534: + return "[$MXN]\\ #,##0.00", true + case NumberFormatID535: + return "[$MXV]\\ #,##0.00", true + case NumberFormatID536: + return "[$MYR]\\ #,##0.00", true + case NumberFormatID537: + return "[$MZM]\\ #,##0.00", true + case NumberFormatID538: + return "[$MZN]\\ #,##0.00", true + case NumberFormatID539: + return "[$NAD]\\ #,##0.00", true + case NumberFormatID540: + return "[$NGN]\\ #,##0.00", true + case NumberFormatID541: + return "[$NIO]\\ #,##0.00", true + case NumberFormatID542: + return "[$NLG]\\ #,##0.00", true + case NumberFormatID543: + return "[$NOK]\\ #,##0.00", true + case NumberFormatID544: + return "[$NPR]\\ #,##0.00", true + case NumberFormatID545: + return "[$NTD]\\ #,##0.00", true + case NumberFormatID546: + return "[$NZD]\\ #,##0.00", true + case NumberFormatID547: + return "[$OMR]\\ #,##0.00", true + case NumberFormatID548: + return "[$PAB]\\ #,##0.00", true + case NumberFormatID549: + return "[$PEN]\\ #,##0.00", true + case NumberFormatID550: + return "[$PGK]\\ #,##0.00", true + case NumberFormatID551: + return "[$PHP]\\ #,##0.00", true + case NumberFormatID552: + return "[$PKR]\\ #,##0.00", true + case NumberFormatID553: + return "[$PLN]\\ #,##0.00", true + case NumberFormatID554: + return "[$PTE]\\ #,##0.00", true + case NumberFormatID555: + return "[$PYG]\\ #,##0.00", true + case NumberFormatID556: + return "[$QAR]\\ #,##0.00", true + case NumberFormatID557: + return "[$ROL]\\ #,##0.00", true + case NumberFormatID558: + return "[$RON]\\ #,##0.00", true + case NumberFormatID559: + return "[$RSD]\\ #,##0.00", true + case NumberFormatID560: + return "[$RUB]\\ #,##0.00", true + case NumberFormatID561: + return "[$RUR]\\ #,##0.00", true + case NumberFormatID562: + return "[$RWF]\\ #,##0.00", true + case NumberFormatID563: + return "[$SAR]\\ #,##0.00", true + case NumberFormatID564: + return "[$SBD]\\ #,##0.00", true + case NumberFormatID565: + return "[$SCR]\\ #,##0.00", true + case NumberFormatID566: + return "[$SDD]\\ #,##0.00", true + case NumberFormatID567: + return "[$SDG]\\ #,##0.00", true + case NumberFormatID568: + return "[$SDP]\\ #,##0.00", true + case NumberFormatID569: + return "[$SEK]\\ #,##0.00", true + case NumberFormatID570: + return "[$SGD]\\ #,##0.00", true + case NumberFormatID571: + return "[$SHP]\\ #,##0.00", true + case NumberFormatID572: + return "[$SIT]\\ #,##0.00", true + case NumberFormatID573: + return "[$SKK]\\ #,##0.00", true + case NumberFormatID574: + return "[$SLL]\\ #,##0.00", true + case NumberFormatID575: + return "[$SOS]\\ #,##0.00", true + case NumberFormatID576: + return "[$SPL]\\ #,##0.00", true + case NumberFormatID577: + return "[$SRD]\\ #,##0.00", true + case NumberFormatID578: + return "[$SRG]\\ #,##0.00", true + case NumberFormatID579: + return "[$STD]\\ #,##0.00", true + case NumberFormatID580: + return "[$SVC]\\ #,##0.00", true + case NumberFormatID581: + return "[$SYP]\\ #,##0.00", true + case NumberFormatID582: + return "[$SZL]\\ #,##0.00", true + case NumberFormatID583: + return "[$THB]\\ #,##0.00", true + case NumberFormatID584: + return "[$TJR]\\ #,##0.00", true + case NumberFormatID585: + return "[$TJS]\\ #,##0.00", true + case NumberFormatID586: + return "[$TMM]\\ #,##0.00", true + case NumberFormatID587: + return "[$TMT]\\ #,##0.00", true + case NumberFormatID588: + return "[$TND]\\ #,##0.00", true + case NumberFormatID589: + return "[$TOP]\\ #,##0.00", true + case NumberFormatID590: + return "[$TRL]\\ #,##0.00", true + case NumberFormatID591: + return "[$TRY]\\ #,##0.00", true + case NumberFormatID592: + return "[$TTD]\\ #,##0.00", true + case NumberFormatID593: + return "[$TWD]\\ #,##0.00", true + case NumberFormatID594: + return "[$TZS]\\ #,##0.00", true + case NumberFormatID595: + return "[$UAH]\\ #,##0.00", true + case NumberFormatID596: + return "[$UGX]\\ #,##0.00", true + case NumberFormatID597: + return "[$USD]\\ #,##0.00", true + case NumberFormatID598: + return "[$USN]\\ #,##0.00", true + case NumberFormatID599: + return "[$USS]\\ #,##0.00", true + case NumberFormatID600: + return "[$UYI]\\ #,##0.00", true + case NumberFormatID601: + return "[$UYU]\\ #,##0.00", true + case NumberFormatID602: + return "[$UZS]\\ #,##0.00", true + case NumberFormatID603: + return "[$VEB]\\ #,##0.00", true + case NumberFormatID604: + return "[$VEF]\\ #,##0.00", true + case NumberFormatID605: + return "[$VND]\\ #,##0.00", true + case NumberFormatID606: + return "[$VUV]\\ #,##0.00", true + case NumberFormatID607: + return "[$WST]\\ #,##0.00", true + case NumberFormatID608: + return "[$XAF]\\ #,##0.00", true + case NumberFormatID609: + return "[$XAG]\\ #,##0.00", true + case NumberFormatID610: + return "[$XAU]\\ #,##0.00", true + case NumberFormatID611: + return "[$XB5]\\ #,##0.00", true + case NumberFormatID612: + return "[$XBA]\\ #,##0.00", true + case NumberFormatID613: + return "[$XBB]\\ #,##0.00", true + case NumberFormatID614: + return "[$XBC]\\ #,##0.00", true + case NumberFormatID615: + return "[$XBD]\\ #,##0.00", true + case NumberFormatID616: + return "[$XCD]\\ #,##0.00", true + case NumberFormatID617: + return "[$XDR]\\ #,##0.00", true + case NumberFormatID618: + return "[$XFO]\\ #,##0.00", true + case NumberFormatID619: + return "[$XFU]\\ #,##0.00", true + case NumberFormatID620: + return "[$XOF]\\ #,##0.00", true + case NumberFormatID621: + return "[$XPD]\\ #,##0.00", true + case NumberFormatID622: + return "[$XPF]\\ #,##0.00", true + case NumberFormatID623: + return "[$XPT]\\ #,##0.00", true + case NumberFormatID624: + return "[$XTS]\\ #,##0.00", true + case NumberFormatID625: + return "[$XXX]\\ #,##0.00", true + case NumberFormatID626: + return "[$YER]\\ #,##0.00", true + case NumberFormatID627: + return "[$YUM]\\ #,##0.00", true + case NumberFormatID628: + return "[$ZAR]\\ #,##0.00", true + case NumberFormatID629: + return "[$ZMK]\\ #,##0.00", true + case NumberFormatID630: + return "[$ZMW]\\ #,##0.00", true + case NumberFormatID631: + return "[$ZWD]\\ #,##0.00", true + case NumberFormatID632: + return "[$ZWL]\\ #,##0.00", true + case NumberFormatID633: + return "[$ZWN]\\ #,##0.00", true + case NumberFormatID634: + return "[$ZWR]\\ #,##0.00", true + default: + return "", false + } +} + +// StyleLanguage identifies a language tag in Excelize's localized number-format tables; newer Style APIs use the resolved format code. +// +// See https://xuri.me/excelize/en/style.html +type StyleLanguage string + +const ( + // StyleLanguageJa_jp identifies language tag "ja-jp" for localized number-format lookup. + // + // See https://xuri.me/excelize/en/style.html + StyleLanguageJa_jp StyleLanguage = "ja-jp" + // StyleLanguageKo_kr identifies language tag "ko-kr" for localized number-format lookup. + // + // See https://xuri.me/excelize/en/style.html + StyleLanguageKo_kr StyleLanguage = "ko-kr" + // StyleLanguageTh_th identifies language tag "th-th" for localized number-format lookup. + // + // See https://xuri.me/excelize/en/style.html + StyleLanguageTh_th StyleLanguage = "th-th" + // StyleLanguageZh_cn identifies language tag "zh-cn" for localized number-format lookup. + // + // See https://xuri.me/excelize/en/style.html + StyleLanguageZh_cn StyleLanguage = "zh-cn" + // StyleLanguageZh_tw identifies language tag "zh-tw" for localized number-format lookup. + // + // See https://xuri.me/excelize/en/style.html + StyleLanguageZh_tw StyleLanguage = "zh-tw" +) + +func StyleLanguageValues() []StyleLanguage { + return []StyleLanguage{"ja-jp", "ko-kr", "th-th", "zh-cn", "zh-tw"} +} + +func (value NumberFormat) CodeForLanguage(language StyleLanguage) (string, bool) { + switch language { + case "ja-jp": + switch value { + case NumberFormatID27: + return "[$-411]ge.m.d", true + case NumberFormatID28: + return "[$-411]ggge\"年\"m\"月\"d\"日\"", true + case NumberFormatID29: + return "[$-411]ggge\"年\"m\"月\"d\"日\"", true + case NumberFormatID30: + return "m/d/yy", true + case NumberFormatID31: + return "yyyy\"年\"m\"月\"d\"日\"", true + case NumberFormatID32: + return "h\"時\"mm\"分\"", true + case NumberFormatID33: + return "h\"時\"mm\"分\"ss\"秒\"", true + case NumberFormatID34: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID35: + return "m\"月\"d\"日\"", true + case NumberFormatID36: + return "[$-411]ge.m.d", true + case NumberFormatID50: + return "[$-411]ge.m.d", true + case NumberFormatID51: + return "[$-411]ggge\"年\"m\"月\"d\"日\"", true + case NumberFormatID52: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID53: + return "m\"月\"d\"日\"", true + case NumberFormatID54: + return "[$-411]ggge\"年\"m\"月\"d\"日\"", true + case NumberFormatID55: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID56: + return "m\"月\"d\"日\"", true + case NumberFormatID57: + return "[$-411]ge.m.d", true + case NumberFormatID58: + return "[$-411]ggge\"年\"m\"月\"d\"日\"", true + } + case "ko-kr": + switch value { + case NumberFormatID27: + return "yyyy\"年\" mm\"月\" dd\"日\"", true + case NumberFormatID28: + return "mm-dd", true + case NumberFormatID29: + return "mm-dd", true + case NumberFormatID30: + return "mm-dd-yy", true + case NumberFormatID31: + return "yyyy\"년\" mm\"월\" dd\"일\"", true + case NumberFormatID32: + return "h\"시\" mm\"분\"", true + case NumberFormatID33: + return "h\"시\" mm\"분\" ss\"초\"", true + case NumberFormatID34: + return "yyyy-mm-dd", true + case NumberFormatID35: + return "yyyy-mm-dd", true + case NumberFormatID36: + return "yyyy\"年\" mm\"月\" dd\"日\"", true + case NumberFormatID50: + return "yyyy\"年\" mm\"月\" dd\"日\"", true + case NumberFormatID51: + return "mm-dd", true + case NumberFormatID52: + return "yyyy-mm-dd", true + case NumberFormatID53: + return "yyyy-mm-dd", true + case NumberFormatID54: + return "mm-dd", true + case NumberFormatID55: + return "yyyy-mm-dd", true + case NumberFormatID56: + return "yyyy-mm-dd", true + case NumberFormatID57: + return "yyyy\"年\" mm\"月\" dd\"日\"", true + case NumberFormatID58: + return "mm-dd", true + } + case "th-th": + switch value { + case NumberFormatID59: + return "t0", true + case NumberFormatID60: + return "t0.00", true + case NumberFormatID61: + return "t#,##0", true + case NumberFormatID62: + return "t#,##0.00", true + case NumberFormatID67: + return "t0%", true + case NumberFormatID68: + return "t0.00%", true + case NumberFormatID69: + return "t# ?/?", true + case NumberFormatID70: + return "t# ??/??", true + case NumberFormatID71: + return "ว/ด/ปปปป", true + case NumberFormatID72: + return "ว-ดดด-ปป", true + case NumberFormatID73: + return "ว-ดดด", true + case NumberFormatID74: + return "ดดด-ปป", true + case NumberFormatID75: + return "ช:นน", true + case NumberFormatID76: + return "ช:นน:ทท", true + case NumberFormatID77: + return "ว/ด/ปปปป ช:นน", true + case NumberFormatID78: + return "นน:ทท", true + case NumberFormatID79: + return "[ช]:นน:ทท", true + case NumberFormatID80: + return "นน:ทท.0", true + case NumberFormatID81: + return "d/m/bb", true + } + case "zh-cn": + switch value { + case NumberFormatID27: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID28: + return "m\"月\"d\"日\"", true + case NumberFormatID29: + return "m\"月\"d\"日\"", true + case NumberFormatID30: + return "m/d/yy", true + case NumberFormatID31: + return "yyyy\"年\"m\"月\"d\"日\"", true + case NumberFormatID32: + return "h\"时\"mm\"分\"", true + case NumberFormatID33: + return "h\"时\"mm\"分\"ss\"秒\"", true + case NumberFormatID34: + return "上午/下午h\"时\"mm\"分\"", true + case NumberFormatID35: + return "上午/下午h\"时\"mm\"分\"ss\"秒\"", true + case NumberFormatID36: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID50: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID51: + return "m\"月\"d\"日\"", true + case NumberFormatID52: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID53: + return "m\"月\"d\"日\"", true + case NumberFormatID54: + return "m\"月\"d\"日\"", true + case NumberFormatID55: + return "上午/下午h\"时\"mm\"分\"", true + case NumberFormatID56: + return "上午/下午h\"时\"mm\"分\"ss\"秒\"", true + case NumberFormatID57: + return "yyyy\"年\"m\"月\"", true + case NumberFormatID58: + return "m\"月\"d\"日\"", true + } + case "zh-tw": + switch value { + case NumberFormatID27: + return "[$-404]e/m/d", true + case NumberFormatID28: + return "[$-404]e\"年\"m\"月\"d\"日\"", true + case NumberFormatID29: + return "[$-404]e\"年\"m\"月\"d\"日\"", true + case NumberFormatID30: + return "m/d/yy", true + case NumberFormatID31: + return "yyyy\"年\"m\"月\"d\"日\"", true + case NumberFormatID32: + return "hh\"時\"mm\"分\"", true + case NumberFormatID33: + return "hh\"時\"mm\"分\"ss\"秒\"", true + case NumberFormatID34: + return "上午/下午hh\"時\"mm\"分\"", true + case NumberFormatID35: + return "上午/下午hh\"時\"mm\"分\"ss\"秒\"", true + case NumberFormatID36: + return "[$-404]e/m/d", true + case NumberFormatID50: + return "[$-404]e/m/d", true + case NumberFormatID51: + return "[$-404]e\"年\"m\"月\"d\"日\"", true + case NumberFormatID52: + return "上午/下午hh\"時\"mm\"分\"", true + case NumberFormatID53: + return "上午/下午hh\"時\"mm\"分\"ss\"秒\"", true + case NumberFormatID54: + return "[$-404]e\"年\"m\"月\"d\"日\"", true + case NumberFormatID55: + return "上午/下午hh\"時\"mm\"分\"", true + case NumberFormatID56: + return "上午/下午hh\"時\"mm\"分\"ss\"秒\"", true + case NumberFormatID57: + return "[$-404]e/m/d", true + case NumberFormatID58: + return "[$-404]e\"年\"m\"月\"d\"日\"", true + } + } + return value.Code() +} + +// AlignmentField identifies a public field of excelize.Alignment; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type AlignmentField string + +const ( + // AlignmentFieldHorizontal identifies excelize.Alignment.Horizontal. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldHorizontal AlignmentField = "Horizontal" + // AlignmentFieldIndent identifies excelize.Alignment.Indent. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldIndent AlignmentField = "Indent" + // AlignmentFieldJustifyLastLine identifies excelize.Alignment.JustifyLastLine. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldJustifyLastLine AlignmentField = "JustifyLastLine" + // AlignmentFieldReadingOrder identifies excelize.Alignment.ReadingOrder. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldReadingOrder AlignmentField = "ReadingOrder" + // AlignmentFieldRelativeIndent identifies excelize.Alignment.RelativeIndent. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldRelativeIndent AlignmentField = "RelativeIndent" + // AlignmentFieldShrinkToFit identifies excelize.Alignment.ShrinkToFit. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldShrinkToFit AlignmentField = "ShrinkToFit" + // AlignmentFieldTextRotation identifies excelize.Alignment.TextRotation. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldTextRotation AlignmentField = "TextRotation" + // AlignmentFieldVertical identifies excelize.Alignment.Vertical. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldVertical AlignmentField = "Vertical" + // AlignmentFieldWrapText identifies excelize.Alignment.WrapText. + // + // See https://xuri.me/excelize/en/style.html + AlignmentFieldWrapText AlignmentField = "WrapText" +) + +func AlignmentFieldValues() []AlignmentField { + return []AlignmentField{"Horizontal", "Indent", "JustifyLastLine", "ReadingOrder", "RelativeIndent", "ShrinkToFit", "TextRotation", "Vertical", "WrapText"} +} + +// BorderField identifies a public field of excelize.Border; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type BorderField string + +const ( + // BorderFieldType identifies excelize.Border.Type. + // + // See https://xuri.me/excelize/en/style.html + BorderFieldType BorderField = "Type" + // BorderFieldColor identifies excelize.Border.Color. + // + // See https://xuri.me/excelize/en/style.html + BorderFieldColor BorderField = "Color" + // BorderFieldStyle identifies excelize.Border.Style. + // + // See https://xuri.me/excelize/en/style.html + BorderFieldStyle BorderField = "Style" +) + +func BorderFieldValues() []BorderField { return []BorderField{"Type", "Color", "Style"} } + +// FontField identifies a public field of excelize.Font; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type FontField string + +const ( + // FontFieldBold identifies excelize.Font.Bold. + // + // See https://xuri.me/excelize/en/style.html + FontFieldBold FontField = "Bold" + // FontFieldItalic identifies excelize.Font.Italic. + // + // See https://xuri.me/excelize/en/style.html + FontFieldItalic FontField = "Italic" + // FontFieldUnderline identifies excelize.Font.Underline. + // + // See https://xuri.me/excelize/en/style.html + FontFieldUnderline FontField = "Underline" + // FontFieldFamily identifies excelize.Font.Family. + // + // See https://xuri.me/excelize/en/style.html + FontFieldFamily FontField = "Family" + // FontFieldSize identifies excelize.Font.Size. + // + // See https://xuri.me/excelize/en/style.html + FontFieldSize FontField = "Size" + // FontFieldStrike identifies excelize.Font.Strike. + // + // See https://xuri.me/excelize/en/style.html + FontFieldStrike FontField = "Strike" + // FontFieldColor identifies excelize.Font.Color. + // + // See https://xuri.me/excelize/en/style.html + FontFieldColor FontField = "Color" + // FontFieldColorIndexed identifies excelize.Font.ColorIndexed. + // + // See https://xuri.me/excelize/en/style.html + FontFieldColorIndexed FontField = "ColorIndexed" + // FontFieldColorTheme identifies excelize.Font.ColorTheme. + // + // See https://xuri.me/excelize/en/style.html + FontFieldColorTheme FontField = "ColorTheme" + // FontFieldColorTint identifies excelize.Font.ColorTint. + // + // See https://xuri.me/excelize/en/style.html + FontFieldColorTint FontField = "ColorTint" + // FontFieldVertAlign identifies excelize.Font.VertAlign. + // + // See https://xuri.me/excelize/en/style.html + FontFieldVertAlign FontField = "VertAlign" + // FontFieldCharset identifies excelize.Font.Charset. + // + // See https://xuri.me/excelize/en/style.html + FontFieldCharset FontField = "Charset" +) + +func FontFieldValues() []FontField { + return []FontField{"Bold", "Italic", "Underline", "Family", "Size", "Strike", "Color", "ColorIndexed", "ColorTheme", "ColorTint", "VertAlign", "Charset"} +} + +// FillField identifies a public field of excelize.Fill; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type FillField string + +const ( + // FillFieldType identifies excelize.Fill.Type. + // + // See https://xuri.me/excelize/en/style.html + FillFieldType FillField = "Type" + // FillFieldPattern identifies excelize.Fill.Pattern. + // + // See https://xuri.me/excelize/en/style.html + FillFieldPattern FillField = "Pattern" + // FillFieldColor identifies excelize.Fill.Color. + // + // See https://xuri.me/excelize/en/style.html + FillFieldColor FillField = "Color" + // FillFieldShading identifies excelize.Fill.Shading. + // + // See https://xuri.me/excelize/en/style.html + FillFieldShading FillField = "Shading" + // FillFieldTransparency identifies excelize.Fill.Transparency. + // + // See https://xuri.me/excelize/en/style.html + FillFieldTransparency FillField = "Transparency" +) + +func FillFieldValues() []FillField { + return []FillField{"Type", "Pattern", "Color", "Shading", "Transparency"} +} + +// ProtectionField identifies a public field of excelize.Protection; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type ProtectionField string + +const ( + // ProtectionFieldHidden identifies excelize.Protection.Hidden. + // + // See https://xuri.me/excelize/en/style.html + ProtectionFieldHidden ProtectionField = "Hidden" + // ProtectionFieldLocked identifies excelize.Protection.Locked. + // + // See https://xuri.me/excelize/en/style.html + ProtectionFieldLocked ProtectionField = "Locked" +) + +func ProtectionFieldValues() []ProtectionField { return []ProtectionField{"Hidden", "Locked"} } + +// StyleField identifies a public field of excelize.Style; these values are field names rather than formatting values. +// +// See https://xuri.me/excelize/en/style.html +type StyleField string + +const ( + // StyleFieldBorder identifies excelize.Style.Border. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldBorder StyleField = "Border" + // StyleFieldFill identifies excelize.Style.Fill. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldFill StyleField = "Fill" + // StyleFieldFont identifies excelize.Style.Font. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldFont StyleField = "Font" + // StyleFieldAlignment identifies excelize.Style.Alignment. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldAlignment StyleField = "Alignment" + // StyleFieldProtection identifies excelize.Style.Protection. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldProtection StyleField = "Protection" + // StyleFieldNumFmt identifies excelize.Style.NumFmt. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldNumFmt StyleField = "NumFmt" + // StyleFieldDecimalPlaces identifies excelize.Style.DecimalPlaces. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldDecimalPlaces StyleField = "DecimalPlaces" + // StyleFieldCustomNumFmt identifies excelize.Style.CustomNumFmt. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldCustomNumFmt StyleField = "CustomNumFmt" + // StyleFieldNegRed identifies excelize.Style.NegRed. + // + // See https://xuri.me/excelize/en/style.html + StyleFieldNegRed StyleField = "NegRed" +) + +func StyleFieldValues() []StyleField { + return []StyleField{"Border", "Fill", "Font", "Alignment", "Protection", "NumFmt", "DecimalPlaces", "CustomNumFmt", "NegRed"} +} + +// IndexedColor selects an indexed palette entry or system color for excelize.Font.ColorIndexed; workbook palettes can override the displayed RGB. +// +// See https://xuri.me/excelize/en/style.html +type IndexedColor int + +const ( + // IndexedColorUnset leaves the indexed font color selector unspecified (ID -1). + // + // See https://xuri.me/excelize/en/style.html + IndexedColorUnset IndexedColor = -1 + // IndexedColorIndex0 selects palette index 0; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex0 IndexedColor = 0 + // IndexedColorIndex1 selects palette index 1; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex1 IndexedColor = 1 + // IndexedColorIndex2 selects palette index 2; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex2 IndexedColor = 2 + // IndexedColorIndex3 selects palette index 3; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex3 IndexedColor = 3 + // IndexedColorIndex4 selects palette index 4; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex4 IndexedColor = 4 + // IndexedColorIndex5 selects palette index 5; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex5 IndexedColor = 5 + // IndexedColorIndex6 selects palette index 6; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex6 IndexedColor = 6 + // IndexedColorIndex7 selects palette index 7; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex7 IndexedColor = 7 + // IndexedColorIndex8 selects palette index 8; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex8 IndexedColor = 8 + // IndexedColorIndex9 selects palette index 9; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex9 IndexedColor = 9 + // IndexedColorIndex10 selects palette index 10; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex10 IndexedColor = 10 + // IndexedColorIndex11 selects palette index 11; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex11 IndexedColor = 11 + // IndexedColorIndex12 selects palette index 12; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex12 IndexedColor = 12 + // IndexedColorIndex13 selects palette index 13; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex13 IndexedColor = 13 + // IndexedColorIndex14 selects palette index 14; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex14 IndexedColor = 14 + // IndexedColorIndex15 selects palette index 15; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex15 IndexedColor = 15 + // IndexedColorIndex16 selects palette index 16; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex16 IndexedColor = 16 + // IndexedColorIndex17 selects palette index 17; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex17 IndexedColor = 17 + // IndexedColorIndex18 selects palette index 18; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex18 IndexedColor = 18 + // IndexedColorIndex19 selects palette index 19; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex19 IndexedColor = 19 + // IndexedColorIndex20 selects palette index 20; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex20 IndexedColor = 20 + // IndexedColorIndex21 selects palette index 21; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex21 IndexedColor = 21 + // IndexedColorIndex22 selects palette index 22; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex22 IndexedColor = 22 + // IndexedColorIndex23 selects palette index 23; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex23 IndexedColor = 23 + // IndexedColorIndex24 selects palette index 24; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex24 IndexedColor = 24 + // IndexedColorIndex25 selects palette index 25; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex25 IndexedColor = 25 + // IndexedColorIndex26 selects palette index 26; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex26 IndexedColor = 26 + // IndexedColorIndex27 selects palette index 27; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex27 IndexedColor = 27 + // IndexedColorIndex28 selects palette index 28; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex28 IndexedColor = 28 + // IndexedColorIndex29 selects palette index 29; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex29 IndexedColor = 29 + // IndexedColorIndex30 selects palette index 30; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex30 IndexedColor = 30 + // IndexedColorIndex31 selects palette index 31; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex31 IndexedColor = 31 + // IndexedColorIndex32 selects palette index 32; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex32 IndexedColor = 32 + // IndexedColorIndex33 selects palette index 33; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex33 IndexedColor = 33 + // IndexedColorIndex34 selects palette index 34; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex34 IndexedColor = 34 + // IndexedColorIndex35 selects palette index 35; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex35 IndexedColor = 35 + // IndexedColorIndex36 selects palette index 36; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex36 IndexedColor = 36 + // IndexedColorIndex37 selects palette index 37; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex37 IndexedColor = 37 + // IndexedColorIndex38 selects palette index 38; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex38 IndexedColor = 38 + // IndexedColorIndex39 selects palette index 39; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex39 IndexedColor = 39 + // IndexedColorIndex40 selects palette index 40; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex40 IndexedColor = 40 + // IndexedColorIndex41 selects palette index 41; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex41 IndexedColor = 41 + // IndexedColorIndex42 selects palette index 42; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex42 IndexedColor = 42 + // IndexedColorIndex43 selects palette index 43; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex43 IndexedColor = 43 + // IndexedColorIndex44 selects palette index 44; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex44 IndexedColor = 44 + // IndexedColorIndex45 selects palette index 45; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex45 IndexedColor = 45 + // IndexedColorIndex46 selects palette index 46; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex46 IndexedColor = 46 + // IndexedColorIndex47 selects palette index 47; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex47 IndexedColor = 47 + // IndexedColorIndex48 selects palette index 48; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex48 IndexedColor = 48 + // IndexedColorIndex49 selects palette index 49; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex49 IndexedColor = 49 + // IndexedColorIndex50 selects palette index 50; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex50 IndexedColor = 50 + // IndexedColorIndex51 selects palette index 51; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex51 IndexedColor = 51 + // IndexedColorIndex52 selects palette index 52; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex52 IndexedColor = 52 + // IndexedColorIndex53 selects palette index 53; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex53 IndexedColor = 53 + // IndexedColorIndex54 selects palette index 54; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex54 IndexedColor = 54 + // IndexedColorIndex55 selects palette index 55; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex55 IndexedColor = 55 + // IndexedColorIndex56 selects palette index 56; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex56 IndexedColor = 56 + // IndexedColorIndex57 selects palette index 57; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex57 IndexedColor = 57 + // IndexedColorIndex58 selects palette index 58; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex58 IndexedColor = 58 + // IndexedColorIndex59 selects palette index 59; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex59 IndexedColor = 59 + // IndexedColorIndex60 selects palette index 60; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex60 IndexedColor = 60 + // IndexedColorIndex61 selects palette index 61; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex61 IndexedColor = 61 + // IndexedColorIndex62 selects palette index 62; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex62 IndexedColor = 62 + // IndexedColorIndex63 selects palette index 63; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex63 IndexedColor = 63 + // IndexedColorIndex64 selects palette index 64; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex64 IndexedColor = 64 + // IndexedColorIndex65 selects palette index 65; the workbook palette determines its RGB color. + // + // See https://xuri.me/excelize/en/style.html + IndexedColorIndex65 IndexedColor = 65 + // IndexedColorSystemForeground selects the system foreground color (index 64). + // + // See https://xuri.me/excelize/en/style.html + IndexedColorSystemForeground IndexedColor = 64 + // IndexedColorSystemBackground selects the system background color (index 65). + // + // See https://xuri.me/excelize/en/style.html + IndexedColorSystemBackground IndexedColor = 65 +) + +// ThemeColor selects a workbook theme slot for excelize.Font.ColorTheme; its displayed color depends on the workbook theme. +// +// See https://xuri.me/excelize/en/style.html +type ThemeColor int + +const ( + // ThemeColorLight1 selects the workbook theme's Light1 color slot (ID 0). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorLight1 ThemeColor = 0 + // ThemeColorDark1 selects the workbook theme's Dark1 color slot (ID 1). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorDark1 ThemeColor = 1 + // ThemeColorLight2 selects the workbook theme's Light2 color slot (ID 2). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorLight2 ThemeColor = 2 + // ThemeColorDark2 selects the workbook theme's Dark2 color slot (ID 3). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorDark2 ThemeColor = 3 + // ThemeColorAccent1 selects the workbook theme's Accent1 color slot (ID 4). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent1 ThemeColor = 4 + // ThemeColorAccent2 selects the workbook theme's Accent2 color slot (ID 5). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent2 ThemeColor = 5 + // ThemeColorAccent3 selects the workbook theme's Accent3 color slot (ID 6). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent3 ThemeColor = 6 + // ThemeColorAccent4 selects the workbook theme's Accent4 color slot (ID 7). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent4 ThemeColor = 7 + // ThemeColorAccent5 selects the workbook theme's Accent5 color slot (ID 8). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent5 ThemeColor = 8 + // ThemeColorAccent6 selects the workbook theme's Accent6 color slot (ID 9). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorAccent6 ThemeColor = 9 + // ThemeColorHyperlink selects the workbook theme's Hyperlink color slot (ID 10). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorHyperlink ThemeColor = 10 + // ThemeColorFollowedHyperlink selects the workbook theme's FollowedHyperlink color slot (ID 11). + // + // See https://xuri.me/excelize/en/style.html + ThemeColorFollowedHyperlink ThemeColor = 11 +) + +// FontCharset identifies an OOXML font character set for excelize.Font.Charset; these numeric selectors are not RGB colors or text encodings to apply to cell values. +// +// See https://xuri.me/excelize/en/style.html +type FontCharset int + +const ( + // FontCharsetANSI selects the ANSI font character set (ID 0). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetANSI FontCharset = 0 + // FontCharsetDefault selects the Default font character set (ID 1). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetDefault FontCharset = 1 + // FontCharsetSymbol selects the Symbol font character set (ID 2). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetSymbol FontCharset = 2 + // FontCharsetMac selects the Mac font character set (ID 77). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetMac FontCharset = 77 + // FontCharsetShiftJIS selects the ShiftJIS font character set (ID 128). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetShiftJIS FontCharset = 128 + // FontCharsetHangul selects the Hangul font character set (ID 129). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetHangul FontCharset = 129 + // FontCharsetJohab selects the Johab font character set (ID 130). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetJohab FontCharset = 130 + // FontCharsetGB2312 selects the GB2312 font character set (ID 134). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetGB2312 FontCharset = 134 + // FontCharsetBig5 selects the Big5 font character set (ID 136). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetBig5 FontCharset = 136 + // FontCharsetGreek selects the Greek font character set (ID 161). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetGreek FontCharset = 161 + // FontCharsetTurkish selects the Turkish font character set (ID 162). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetTurkish FontCharset = 162 + // FontCharsetVietnamese selects the Vietnamese font character set (ID 163). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetVietnamese FontCharset = 163 + // FontCharsetHebrew selects the Hebrew font character set (ID 177). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetHebrew FontCharset = 177 + // FontCharsetArabic selects the Arabic font character set (ID 178). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetArabic FontCharset = 178 + // FontCharsetBaltic selects the Baltic font character set (ID 186). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetBaltic FontCharset = 186 + // FontCharsetRussian selects the Russian font character set (ID 204). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetRussian FontCharset = 204 + // FontCharsetThai selects the Thai font character set (ID 222). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetThai FontCharset = 222 + // FontCharsetEastEurope selects the EastEurope font character set (ID 238). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetEastEurope FontCharset = 238 + // FontCharsetOEM selects the OEM font character set (ID 255). + // + // See https://xuri.me/excelize/en/style.html + FontCharsetOEM FontCharset = 255 +) + +// AlignmentDefinition preserves all public excelize.Alignment fields with typed categorical values. +type AlignmentDefinition struct { + Horizontal AlignmentHorizontal + Indent int + JustifyLastLine bool + ReadingOrder AlignmentReadingOrder + RelativeIndent int + ShrinkToFit bool + TextRotation int + Vertical AlignmentVertical + WrapText bool +} + +func (in AlignmentDefinition) ToExcelize() excelize.Alignment { + out := excelize.Alignment{} + out.Horizontal = string(in.Horizontal) + out.Indent = in.Indent + out.JustifyLastLine = in.JustifyLastLine + out.ReadingOrder = uint64(in.ReadingOrder) + out.RelativeIndent = in.RelativeIndent + out.ShrinkToFit = in.ShrinkToFit + out.TextRotation = in.TextRotation + out.Vertical = string(in.Vertical) + out.WrapText = in.WrapText + return out +} +func AlignmentDefinitionFromExcelize(in excelize.Alignment) AlignmentDefinition { + out := AlignmentDefinition{} + out.Horizontal = AlignmentHorizontal(in.Horizontal) + out.Indent = in.Indent + out.JustifyLastLine = in.JustifyLastLine + out.ReadingOrder = AlignmentReadingOrder(in.ReadingOrder) + out.RelativeIndent = in.RelativeIndent + out.ShrinkToFit = in.ShrinkToFit + out.TextRotation = in.TextRotation + out.Vertical = AlignmentVertical(in.Vertical) + out.WrapText = in.WrapText + return out +} + +// BorderDefinition preserves all public excelize.Border fields with typed categorical values. +type BorderDefinition struct { + Type BorderPosition + Color string + Style BorderStyle +} + +func (in BorderDefinition) ToExcelize() excelize.Border { + out := excelize.Border{} + out.Type = string(in.Type) + out.Color = in.Color + out.Style = int(in.Style) + return out +} +func BorderDefinitionFromExcelize(in excelize.Border) BorderDefinition { + out := BorderDefinition{} + out.Type = BorderPosition(in.Type) + out.Color = in.Color + out.Style = BorderStyle(in.Style) + return out +} + +// FontDefinition preserves all public excelize.Font fields with typed categorical values. +type FontDefinition struct { + Bold bool + Italic bool + Underline FontUnderline + Family string + Size float64 + Strike bool + Color string + ColorIndexed IndexedColor + ColorTheme *ThemeColor + ColorTint float64 + VertAlign FontVerticalAlign + Charset *FontCharset +} + +func (in FontDefinition) ToExcelize() excelize.Font { + out := excelize.Font{} + out.Bold = in.Bold + out.Italic = in.Italic + out.Underline = string(in.Underline) + out.Family = in.Family + out.Size = in.Size + out.Strike = in.Strike + out.Color = in.Color + out.ColorIndexed = int(in.ColorIndexed) + if in.ColorTheme != nil { + value := int(*in.ColorTheme) + out.ColorTheme = &value + } + out.ColorTint = in.ColorTint + out.VertAlign = string(in.VertAlign) + if in.Charset != nil { + value := int(*in.Charset) + out.Charset = &value + } + return out +} +func FontDefinitionFromExcelize(in excelize.Font) FontDefinition { + out := FontDefinition{} + out.Bold = in.Bold + out.Italic = in.Italic + out.Underline = FontUnderline(in.Underline) + out.Family = in.Family + out.Size = in.Size + out.Strike = in.Strike + out.Color = in.Color + out.ColorIndexed = IndexedColor(in.ColorIndexed) + if in.ColorTheme != nil { + value := ThemeColor(*in.ColorTheme) + out.ColorTheme = &value + } + out.ColorTint = in.ColorTint + out.VertAlign = FontVerticalAlign(in.VertAlign) + if in.Charset != nil { + value := FontCharset(*in.Charset) + out.Charset = &value + } + return out +} + +// FillDefinition preserves all public excelize.Fill fields with typed categorical values. +type FillDefinition struct { + Type FillType + Pattern FillPattern + Color []string + Shading GradientShading + Transparency int +} + +func (in FillDefinition) ToExcelize() excelize.Fill { + out := excelize.Fill{} + out.Type = string(in.Type) + out.Pattern = int(in.Pattern) + out.Color = in.Color + out.Shading = int(in.Shading) + out.Transparency = in.Transparency + return out +} +func FillDefinitionFromExcelize(in excelize.Fill) FillDefinition { + out := FillDefinition{} + out.Type = FillType(in.Type) + out.Pattern = FillPattern(in.Pattern) + out.Color = in.Color + out.Shading = GradientShading(in.Shading) + out.Transparency = in.Transparency + return out +} + +// ProtectionDefinition preserves all public excelize.Protection fields with typed categorical values. +type ProtectionDefinition struct { + Hidden bool + Locked bool +} + +func (in ProtectionDefinition) ToExcelize() excelize.Protection { + out := excelize.Protection{} + out.Hidden = in.Hidden + out.Locked = in.Locked + return out +} +func ProtectionDefinitionFromExcelize(in excelize.Protection) ProtectionDefinition { + out := ProtectionDefinition{} + out.Hidden = in.Hidden + out.Locked = in.Locked + return out +} + +// StyleDefinition preserves all public excelize.Style fields with typed categorical values. +type StyleDefinition struct { + Border []BorderDefinition + Fill FillDefinition + Font *FontDefinition + Alignment *AlignmentDefinition + Protection *ProtectionDefinition + NumFmt NumberFormat + DecimalPlaces *int + CustomNumFmt *string + NegRed bool +} + +func (in StyleDefinition) ToExcelize() excelize.Style { + out := excelize.Style{} + if in.Border != nil { + out.Border = make([]excelize.Border, len(in.Border)) + for i, value := range in.Border { + out.Border[i] = value.ToExcelize() + } + } + out.Fill = in.Fill.ToExcelize() + if in.Font != nil { + value := in.Font.ToExcelize() + out.Font = &value + } + if in.Alignment != nil { + value := in.Alignment.ToExcelize() + out.Alignment = &value + } + if in.Protection != nil { + value := in.Protection.ToExcelize() + out.Protection = &value + } + out.NumFmt = int(in.NumFmt) + out.DecimalPlaces = in.DecimalPlaces + out.CustomNumFmt = in.CustomNumFmt + out.NegRed = in.NegRed + return out +} +func StyleDefinitionFromExcelize(in excelize.Style) StyleDefinition { + out := StyleDefinition{} + if in.Border != nil { + out.Border = make([]BorderDefinition, len(in.Border)) + for i, value := range in.Border { + out.Border[i] = BorderDefinitionFromExcelize(value) + } + } + out.Fill = FillDefinitionFromExcelize(in.Fill) + if in.Font != nil { + value := FontDefinitionFromExcelize(*in.Font) + out.Font = &value + } + if in.Alignment != nil { + value := AlignmentDefinitionFromExcelize(*in.Alignment) + out.Alignment = &value + } + if in.Protection != nil { + value := ProtectionDefinitionFromExcelize(*in.Protection) + out.Protection = &value + } + out.NumFmt = NumberFormat(in.NumFmt) + out.DecimalPlaces = in.DecimalPlaces + out.CustomNumFmt = in.CustomNumFmt + out.NegRed = in.NegRed + return out +} + +// GradientShading selects a gradient preset in the selected Excelize dependency; preset numbering may change between versions. +// +// See https://xuri.me/excelize/en/style.html +type GradientShading int + +const ( + // GradientShadingPreset0 selects preset 0: linear gradient, degree 90, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset0 GradientShading = 0 + // GradientShadingPreset1 selects preset 1: linear gradient, degree 270, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset1 GradientShading = 1 + // GradientShadingPreset2 selects preset 2: linear gradient, degree 90, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,0.5,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset2 GradientShading = 2 + // GradientShadingPreset3 selects preset 3: linear gradient, degree 0, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset3 GradientShading = 3 + // GradientShadingPreset4 selects preset 4: linear gradient, degree 180, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset4 GradientShading = 4 + // GradientShadingPreset5 selects preset 5: linear gradient, degree 0, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,0.5,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset5 GradientShading = 5 + // GradientShadingPreset6 selects preset 6: linear gradient, degree 45, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset6 GradientShading = 6 + // GradientShadingPreset7 selects preset 7: linear gradient, degree 255, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset7 GradientShading = 7 + // GradientShadingPreset8 selects preset 8: linear gradient, degree 45, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,0.5,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset8 GradientShading = 8 + // GradientShadingPreset9 selects preset 9: linear gradient, degree 135, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset9 GradientShading = 9 + // GradientShadingPreset10 selects preset 10: linear gradient, degree 315, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset10 GradientShading = 10 + // GradientShadingPreset11 selects preset 11: linear gradient, degree 135, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,0.5,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset11 GradientShading = 11 + // GradientShadingPreset12 selects preset 12: path gradient, degree 0, path bounds (left 0, right 0, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset12 GradientShading = 12 + // GradientShadingPreset13 selects preset 13: path gradient, degree 0, path bounds (left 1, right 1, top 0, bottom 0), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset13 GradientShading = 13 + // GradientShadingPreset14 selects preset 14: path gradient, degree 0, path bounds (left 0, right 0, top 1, bottom 1), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset14 GradientShading = 14 + // GradientShadingPreset15 selects preset 15: path gradient, degree 0, path bounds (left 1, right 1, top 1, bottom 1), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset15 GradientShading = 15 + // GradientShadingPreset16 selects preset 16: path gradient, degree 0, path bounds (left 0.5, right 0.5, top 0.5, bottom 0.5), and stop positions [0,1]. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingPreset16 GradientShading = 16 + // GradientShadingCount is the number of generated choices; it is not a valid formatting value. + // + // See https://xuri.me/excelize/en/style.html + GradientShadingCount GradientShading = 17 +) diff --git a/excelizestyle/style_test.go b/excelizestyle/style_test.go new file mode 100644 index 0000000..231ff7e --- /dev/null +++ b/excelizestyle/style_test.go @@ -0,0 +1,109 @@ +package excelizestyle + +import ( + "reflect" + "testing" + + "github.com/xuri/excelize/v2" +) + +func TestStyleDefinitionPreservesEveryField(t *testing.T) { + var original excelize.Style + populate(reflect.ValueOf(&original).Elem()) + converted := StyleDefinitionFromExcelize(original).ToExcelize() + if !reflect.DeepEqual(original, converted) { + t.Fatalf("typed conversion lost a field:\noriginal: %#v\nconverted: %#v", original, converted) + } + // Nil and empty slices are observably different and must survive conversion. + empty := excelize.Style{Border: []excelize.Border{}} + if got := StyleDefinitionFromExcelize(empty).ToExcelize(); !reflect.DeepEqual(empty, got) { + t.Fatal("empty Border changed to nil") + } + if got := StyleDefinitionFromExcelize(excelize.Style{}).ToExcelize(); !reflect.DeepEqual(excelize.Style{}, got) { + t.Fatal("zero Style changed") + } +} + +func populate(v reflect.Value) { + switch v.Kind() { + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if v.Field(i).CanSet() { + populate(v.Field(i)) + } + } + case reflect.Ptr: + v.Set(reflect.New(v.Type().Elem())) + populate(v.Elem()) + case reflect.Slice: + v.Set(reflect.MakeSlice(v.Type(), 2, 2)) + for i := 0; i < v.Len(); i++ { + populate(v.Index(i)) + } + case reflect.Bool: + v.SetBool(true) + case reflect.Int, reflect.Int64: + v.SetInt(7) + case reflect.Uint64: + v.SetUint(2) + case reflect.Float64: + v.SetFloat(0.25) + case reflect.String: + v.SetString("custom-value") + } +} + +func TestGeneratedFieldsMatchExcelize(t *testing.T) { + inventories := []struct { + value interface{} + fields []string + }{ + {excelize.Style{}, stringsOf(StyleFieldValues())}, + {excelize.Border{}, stringsOf(BorderFieldValues())}, + {excelize.Fill{}, stringsOf(FillFieldValues())}, + {excelize.Font{}, stringsOf(FontFieldValues())}, + {excelize.Alignment{}, stringsOf(AlignmentFieldValues())}, + {excelize.Protection{}, stringsOf(ProtectionFieldValues())}, + } + for _, inventory := range inventories { + typ := reflect.TypeOf(inventory.value) + var expected []string + for i := 0; i < typ.NumField(); i++ { + if typ.Field(i).PkgPath == "" { + expected = append(expected, typ.Field(i).Name) + } + } + if !reflect.DeepEqual(expected, inventory.fields) { + t.Errorf("%s generated fields are stale; run go generate ./excelizeutil: want %v, got %v", typ.Name(), expected, inventory.fields) + } + } +} + +func stringsOf[T ~string](values []T) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = string(v) + } + return result +} + +func TestGeneratedEnumsAndExistingAliases(t *testing.T) { + if BorderStyleContinuous0 != BorderStyleHair || BorderStyleDash2 != BorderStyleMediumDashed || BorderStyleSlantDash != BorderStyleSlantDashDot { + t.Fatal("existing border aliases changed") + } + if AlignmentVerticalBottom != "bottom" || BorderPositionDiagonalUp != "diagonalUp" || BorderPositionDiagonalDown != "diagonalDown" { + t.Fatal("missing categorical values") + } + if code, ok := NumberFormatID14.Code(); !ok || code == "" { + t.Fatal("missing builtin number format") + } + if _, ok := NumberFormat(999999).Code(); ok { + t.Fatal("unknown number format accepted") + } + for _, pattern := range []FillPattern{FillPatternNone, FillPatternSolid, FillPatternGray0625} { + got, ok := ParseFillPattern(pattern.String()) + if !ok || got != pattern { + t.Fatal("fill enum conversion changed") + } + } +} diff --git a/excelizeutil/STYLE_COVERAGE.md b/excelizeutil/STYLE_COVERAGE.md new file mode 100644 index 0000000..d571837 --- /dev/null +++ b/excelizeutil/STYLE_COVERAGE.md @@ -0,0 +1,53 @@ +# `excelize.Style` の取得・再適用範囲 + +## 対象バージョンと契約 + +この表は、PR の統合ツリーで使われる Excelize **v2.10.1** の `excelize.Style` を基準にしています。対応する公開型の全40フィールドを棚卸しし、セルの実効書式を取得して `NewStyle` で再適用できる範囲を定義します。元の `NewStyle` 入力を復元することや、任意の OOXML を無損失で複製することは契約に含めません。 + +PR ブランチは main の依存更新を取り込み、Excelize v2.10.1 を使用します。生成器は v2.6.0 の旧定義形式にも対応しますが、依存バージョンを変更した場合は `go generate ./excelizeutil` で型付き定義を再生成する必要があります。v2.6.0 には `Style.Lang` があり、`DecimalPlaces` は `int`、`Fill.Transparency` と Font の色参照フィールドはありません。v2.10.1 には `Style.Lang` がなく、`DecimalPlaces` は `*int` です。旧版の `Lang` 入力は workbook から一意に復元できず、選択された完全な書式コードを保持します。gradient は依存中の AST から生成し、v2.10.1 の17 preset と v2.6.0 の6 preset をそれぞれ扱います。 + +フィールド定義の根拠は Excelize の固定タグにある [`xmlStyles.go`](https://github.com/qax-os/excelize/blob/v2.10.1/xmlStyles.go)、[`styles.go`](https://github.com/qax-os/excelize/blob/v2.10.1/styles.go)、[`numfmt.go`](https://github.com/qax-os/excelize/blob/v2.10.1/numfmt.go) です。 + +## フィールド対応表 + +| 型 | 公開フィールド | 取得・再適用と検証 | +|---|---|---| +| `Style` | `Border`, `Fill`, `Font`, `Alignment`, `Protection` | 下記の各型に従って実効値を取得し、保存・再読込後に同じ書式となることを確認します。 | +| `Style` | `NumFmt`, `CustomNumFmt` | 組み込み ID は `NumFmt`、ユーザー定義コードは完全な書式文字列を `CustomNumFmt` に保持します。組み込み形式と quoted/escaped 文字を含む独自形式を再適用テストします。 | +| `Style` | `DecimalPlaces`, `NegRed` | v2.10.1 の組み込み形式では Excelize の `GetStyle` が返す値を使います。独自形式では decimal 数や `[Red]` を文字列から推測せず、同じ意味を持つ完全な `CustomNumFmt` を保存します。組み込みの decimal 数は個別に再適用テストします。 | +| `Border` | `Type`, `Color`, `Style` | 6種類の辺と style 0〜13を検証します。色は実効 RGB で保持します。 | +| `Fill` | `Type`, `Pattern`, `Color` | pattern 0〜18と単色を検証します。`Color` は foreground を優先し、なければ background を取得します。 | +| `Fill` | `Shading` | v2.10.1 の全17 gradient preset を、geometry、stop 数、stop 位置、終端色まで確認します。v2.6.0 では同バージョンの6 preset を使います。 | +| `Fill` | `Transparency` | v2.10.1 の公開フィールドですが、Excelize では chart / shape 用でセル書式には保存されません。セル書式の取得・再適用対象外です。 | +| `Font` | `Bold`, `Italic`, `Strike` | boolean 属性を保持します。値のない ``、``、`` は OOXML の既定に従って `true` とします。各項目の保存後 XML と値を検証します。 | +| `Font` | `Underline`, `Family`, `Size`, `VertAlign`, `Charset` | 取得後に再適用し、XML 属性と再読込値を確認します。v2.10.1 固有の `VertAlign` / `Charset` は v2.6.0 では型にありません。 | +| `Font` | `Color` | v2.10.1 では OOXML に RGB がある場合に限り、Tint 適用前の RGB を保持します。theme / indexed のみの色では空です。これにより同じ tint を二度適用しません。v2.6.0 は色参照フィールドがないため、解決した見た目の RGB を返します。RGB のみで再適用する見た目も別に検証します。 | +| `Font` | `ColorIndexed`, `ColorTheme`, `ColorTint` | v2.10.1 では元の selector と tint を保持し、同じ theme / palette の workbook で再適用できることを確認します。色の selector と見た目の RGB は別々に検証します。v2.6.0 ではこれらの公開フィールドがありません。 | +| `Alignment` | `Horizontal`, `Vertical`, `Indent`, `RelativeIndent`, `ReadingOrder`, `TextRotation`, `WrapText`, `ShrinkToFit`, `JustifyLastLine` | 9項目を全て設定し、返却値、保存・再読込後の値、再出力 XML の全属性を検証します。親 `cellStyleXfs` からの継承も確認します。 | +| `Protection` | `Hidden`, `Locked` | 両方の値を保持します。XMLで `locked` が省略された場合は既定値 `true` として扱います。属性省略、明示的な解除 (`locked="0"`)、親 `cellStyleXfs` からの継承を、保存・再読込と XML 属性で検証します。 | + +`applyFont="0"` の `cellXfs` は参照先 `cellStyleXfs` のフォントを継承する規則です。親の `fontId="0"` も有効な参照として扱い、親フォント0をカスタマイズした fixture の取得・別 workbook への再適用・再読込を確認します。`applyFont="1"` で `fontId` が省略された場合も、実装が解決する font ID 0 を適用し、カスタム font0 の再適用を確認します。`applyFont` / `applyAlignment` / `applyProtection` の継承規則は [MS-OE376 §3.8.45](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/59922f8b-0edc-4e93-a822-9f22254aec46) に記載されています。CT_Xf で `fontId` は optional 属性、ID は `` collection への0-based indexです([ECMA-376 §3.8.45](https://c-rex.net/samples/ooxml/e1/Part4/OOXML_P4_DOCX_xf_topic_ID0E13S6.html))。 + +## 再適用できない入力 + +- v2.10.1 の gradient は17 preset に対応します。3 stop の preset 2・5・8・11 は、position が `0, 0.5, 1` で、最初と最後の実効色が同じ場合に限り、`Style.Fill.Color` の2色へ縮約できます。gradient の任意 geometry、任意 stop 配置、異なる3つ目の色は公開 `Style` で表現できません。未対応 gradient は種類だけを返し、色を返さないため、そのまま `NewStyle` へ渡すとエラーになります。 +- pattern fill は foreground と background の2色を使えますが、`Style.Fill.Color` は1色だけです。両方あるときは foreground を保持し、background は再適用時に失われます。background のみある場合も API の再適用は foreground として出力します。 +- `Fill.Transparency` はセル style XML に保存されず、値を復元できません。 +- 未知の pattern ID と border style 名は公開列挙値へ写せないため 0 (`none`) として返します。auto 色、未知の色参照、palette / theme から解決できない色は空色になります。 +- `Border` には `outline`、`vertical`、`horizontal` の公開表現がありません。取得対象は `left`、`right`、`top`、`bottom`、`diagonalUp`、`diagonalDown` です。未対応の border style 名は `Style` 0 (`none`) に写ります。 +- `Font` の `Color` は auto 色を表現しません。`` は空色とし、再適用では自動色指定そのものを出力できません。黒を示す indexed 0 へ誤変換することはありません。theme / indexed の参照値は v2.10.1 で保持できますが、見た目を RGB 固定にしたい場合は `ColorIndexed` を -1、`ColorTheme` を nil、`ColorTint` を 0 にし、表示色を `Color` に指定します。参照 identity と見た目の固定は異なる選択です。同じ identity と見た目を保つテストでは、再適用先の theme / palette が元と同一であることを確認します。 +- `ColorIndexed` の zero 値は「未指定」と「index 0」を区別できません。実装は RGB、theme、auto がある場合は selector なしとし、index 単独の zero は index 0 として扱います。公開 API の型に状態を表す値がないため、明示された index 0 と selector 省略の両方を区別できません。 +- `formatCode16` は読み込み時に選択した書式コードを `CustomNumFmt` に保持します。`NewStyle` は通常の `formatCode` を出力するため、拡張属性そのものは再生成しません。decimal や `[Red]` を推測して誤るより、選択した完全なコードを保持します。 +- OOXML に複数の書式定義や named style があるときは、セルに適用された実効書式を返します。元の XF の構造や継承元の参照関係は保存しません。 + +## 検証 + +`style_roundtrip_test.go` のテストは v2.10.1 の40フィールド棚卸し、Font fieldごとの再適用、全 gradient preset、pattern 0〜18、border style 0〜13、theme/indexed identity と RGB、組み込み decimal 数、明示適用・継承および省略 ID による Font ID 0、空 boolean 属性、疎な border、pattern の2色制約、Alignment 全9属性、Protection の既定値・解除・継承を確認します。gradient、色参照、Alignment、Protection などの焦点項目は、独自 extractor の戻り値だけでなく、保存後の `xl/styles.xml` を XML として読み直して比較します。 + +## 全フィールドの型付き定義と enum の更新 + +`excelizestyle.StyleDefinition` と入れ子の定義は、依存中の Excelize の公開 Style 関連フィールドを全て保持します。罫線・塗り・配置・下線・色セレクタ・charset・数値書式・言語などの選択値には型付き enum を使い、任意の色・サイズ・文字列・数値と bool は元の型で保持します。`ToExcelize` と `StyleDefinitionFromExcelize` は全フィールドの相互変換を提供します。`GetStyleDefinition` はセルの取得結果をこの型付き定義で返します。元の Style 入力と workbook から復元できる値は異なり、上記の再適用制約は引き続き適用されます。 + +`go generate ./excelizeutil` は、依存中の Excelize のソースを Go AST で解析し、公開 enum、全フィールド名 enum、型付き定義、相互変換、gradient geometry を生成します。罫線・pattern・gradient・数値書式などの定義追加は再生成で反映されます。配置の選択値は AST のドキュメントコメントを解析し、旧版の文書に欠けている OOXML の `general` と `bottom` を補います。Excelize が enum として定義していない reading order、theme slot、charset は OOXML の定義を明示します。任意値の領域を AST だけから推測して enum 化することはしません。 + +依存更新後は生成を実行し、`(cd excelizeutil && go run ./cmd/gen-style-enums -check)` で生成物の更新漏れを確認してください。上流の構造変更・未知の gradient geometry は生成エラーになります。新規フィールドの定義と相互変換は自動で追加されますが、workbook 取得処理の対応可否は別途確認が必要です。使い方と生成範囲は [`excelizestyle/README.md`](../excelizestyle/README.md) に記載しています。 diff --git a/excelizeutil/cmd/gen-style-enums/additional.go b/excelizeutil/cmd/gen-style-enums/additional.go new file mode 100644 index 0000000..f43f36b --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/additional.go @@ -0,0 +1,628 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +func saveGenerated(path string, content []byte) error { + formatted, err := format.Source(content) + if err != nil { + return fmt.Errorf("format %s: %w", path, err) + } + if *checkOutput { + current, err := os.ReadFile(path) + if err != nil { + return err + } + if !bytes.Equal(current, formatted) { + return fmt.Errorf("%s is stale; run go generate ./excelizeutil", path) + } + return nil + } + return os.WriteFile(path, formatted, 0o644) +} + +func expressionText(expr ast.Expr) string { + var b bytes.Buffer + _ = format.Node(&b, token.NewFileSet(), expr) + return b.String() +} + +func initializer(file *ast.File, name string) ast.Expr { + var result ast.Expr + ast.Inspect(file, func(n ast.Node) bool { + if result != nil { + return false + } + switch node := n.(type) { + case *ast.ValueSpec: + for i, id := range node.Names { + if id.Name == name && i < len(node.Values) { + result = node.Values[i] + } + } + case *ast.AssignStmt: + for i, lhs := range node.Lhs { + if id, ok := lhs.(*ast.Ident); ok && id.Name == name && i < len(node.Rhs) { + result = node.Rhs[i] + } + } + } + return true + }) + return result +} + +func stringLiteral(expr ast.Expr) (string, error) { + v, ok := expr.(*ast.BasicLit) + if !ok || v.Kind != token.STRING { + return "", fmt.Errorf("expected string literal: %s", expressionText(expr)) + } + return strconv.Unquote(v.Value) +} + +func stringSwitchValues(file *ast.File, tag string) ([]string, error) { + var values []string + var failure error + ast.Inspect(file, func(n ast.Node) bool { + sw, ok := n.(*ast.SwitchStmt) + if !ok || sw.Tag == nil || !strings.HasSuffix(expressionText(sw.Tag), tag) { + return true + } + for _, statement := range sw.Body.List { + c := statement.(*ast.CaseClause) + for _, expr := range c.List { + value, err := stringLiteral(expr) + if err != nil { + failure = err + return false + } + values = appendUnique(values, value) + } + } + return false + }) + if failure != nil { + return nil, failure + } + if len(values) == 0 { + return nil, fmt.Errorf("cannot locate string switch %s", tag) + } + return values, nil +} + +func appendUnique(values []string, value string) []string { + for _, existing := range values { + if existing == value { + return values + } + } + return append(values, value) +} + +// Alignment has no validation table in Excelize's implementation. Its documented +// choices live in NewStyle's AST comment; general/bottom are OOXML defaults that +// older Excelize documentation omits. +func alignmentOptions(file *ast.File, axis string) ([]string, error) { + for _, group := range file.Comments { + lines := strings.Split(group.Text(), "\n") + for i, line := range lines { + if !strings.Contains(line, axis+" alignment") { + continue + } + var values []string + for _, row := range lines[i+1:] { + text := strings.TrimSpace(row) + if len(values) > 0 && text == "" { + break + } + if text == "" || text == "Style" || strings.HasPrefix(text, "--") { + continue + } + if strings.ContainsAny(text, " :|'().,\t") { + continue + } + if _, err := enumIdentifier(text); err == nil { + values = appendUnique(values, text) + } + } + if len(values) > 0 { + return values, nil + } + } + } + return nil, fmt.Errorf("cannot locate documented %s alignment choices", axis) +} + +func writeStringEnum(out *bytes.Buffer, name string, values []string) error { + fmt.Fprintf(out, "type %s string\n\nconst (\n", name) + seen := map[string]bool{} + for _, value := range values { + suffix := "Unset" + var err error + if value != "" { + suffix, err = enumIdentifier(strings.ReplaceAll(value, "-", "_")) + } + if err != nil { + return err + } + if seen[suffix] { + return fmt.Errorf("duplicate %s constant %s", name, suffix) + } + seen[suffix] = true + fmt.Fprintf(out, "%s%s %s = %q\n", name, suffix, name, value) + } + fmt.Fprintln(out, ")") + fmt.Fprintf(out, "\nfunc %sValues() []%s { return []%s{", name, name, name) + for _, value := range values { + fmt.Fprintf(out, "%q,", value) + } + out.WriteString("} }\n\n") + return nil +} + +func additionalEnums(dir string, styles *ast.File, specs []enumSpec) ([]byte, []byte, error) { + var public bytes.Buffer + public.WriteString("// Code generated by go generate ./excelizeutil; DO NOT EDIT.\n\npackage excelizestyle\n\nimport \"github.com/xuri/excelize/v2\"\n\n") + for _, spec := range specs { + values, err := findEnumValues(styles, spec) + if err != nil { + return nil, nil, err + } + spec.typeName = strings.ToUpper(spec.typeName[:1]) + spec.typeName[1:] + spec.constantPrefix = spec.typeName + if err := writeEnum(&public, spec, values); err != nil { + return nil, nil, err + } + } + positions, err := stringSwitchValues(styles, "v.Type") + if expr := initializer(styles, "styleBorderTypes"); expr != nil { + positions, err = stringValues(expr, "styleBorderTypes") + } + if err != nil { + return nil, nil, err + } + fillTypes, err := stringSwitchValues(styles, ".Fill.Type") + if err != nil { + return nil, nil, err + } + horizontal, err := alignmentOptions(styles, "horizontal") + if err != nil { + return nil, nil, err + } + vertical, err := alignmentOptions(styles, "vertical") + if err != nil { + return nil, nil, err + } + horizontal = appendUnique(horizontal, "general") + vertical = appendUnique(vertical, "bottom") + underlineExpr := initializer(styles, "fontUnderlineType") + underline := []string{} + template, err := parser.ParseFile(token.NewFileSet(), filepath.Join(dir, "templates.go"), nil, 0) + if err != nil { + return nil, nil, err + } + if expr := initializer(template, "supportedUnderlineTypes"); expr != nil { + underline, err = stringValues(expr, "supportedUnderlineTypes") + } else if literal, ok := underlineExpr.(*ast.CompositeLit); ok { + for _, e := range literal.Elts { + value, e := stringLiteral(e.(*ast.KeyValueExpr).Key) + if e != nil { + return nil, nil, e + } + underline = append(underline, value) + } + } else { + return nil, nil, fmt.Errorf("cannot locate underline choices") + } + if err != nil { + return nil, nil, err + } + // An empty underline/fill represents an unset value and must remain expressible. + fillTypes = appendUnique(fillTypes, "") + underline = appendUnique(underline, "") + vertAlign := []string{"baseline", "superscript", "subscript"} + ast.Inspect(styles, func(n ast.Node) bool { + call, ok := n.(*ast.CallExpr) + if !ok || len(call.Args) < 2 || !strings.HasSuffix(expressionText(call.Args[1]), ".VertAlign") { + return true + } + if _, ok := call.Args[0].(*ast.CompositeLit); ok { + values, e := stringValues(call.Args[0], "VertAlign") + if e == nil { + vertAlign = values + } + } + return true + }) + for _, spec := range []struct { + name string + values []string + }{ + {"BorderPosition", positions}, {"FillType", fillTypes}, + {"AlignmentHorizontal", horizontal}, {"AlignmentVertical", vertical}, + {"FontUnderline", underline}, {"FontVerticalAlign", vertAlign}, + } { + if err := writeStringEnum(&public, spec.name, spec.values); err != nil { + return nil, nil, err + } + } + public.WriteString("type AlignmentReadingOrder uint64\nconst (AlignmentReadingOrderContext AlignmentReadingOrder = iota; AlignmentReadingOrderLeftToRight; AlignmentReadingOrderRightToLeft)\n\n") + numberSource := styles + if initializer(styles, "builtInNumFmt") == nil { + numberSource, err = parser.ParseFile(token.NewFileSet(), filepath.Join(dir, "numfmt.go"), nil, 0) + if err != nil { + return nil, nil, err + } + } + if err := numberAndLanguageEnums(&public, numberSource); err != nil { + return nil, nil, err + } + if err := fieldEnums(&public, dir); err != nil { + return nil, nil, err + } + if err := colorEnums(&public, template); err != nil { + return nil, nil, err + } + if err := typedDefinitions(&public, dir); err != nil { + return nil, nil, err + } + presets, err := gradientPresets(styles) + if err != nil { + return nil, nil, err + } + fmt.Fprintln(&public, "type GradientShading int\nconst (") + for i, preset := range presets { + kind := preset.kind + if kind == "" { + kind = "linear" + } + fmt.Fprintf(&public, "// GradientShadingPreset%d selects preset %d: %s gradient, degree %g, path bounds (left %g, right %g, top %g, bottom %g), and stop positions [%s].\n//\n// See %s\n", i, i, kind, preset.degree, preset.left, preset.right, preset.top, preset.bottom, numbers(preset.stops), officialStyleDocs) + fmt.Fprintf(&public, "GradientShadingPreset%d GradientShading = %d\n", i, i) + } + fmt.Fprintf(&public, "GradientShadingCount GradientShading = %d\n)\n", len(presets)) + var geometry bytes.Buffer + fmt.Fprintln(&geometry, "func gradientGeometries() []gradientGeometry { return []gradientGeometry{") + for _, preset := range presets { + fmt.Fprintf(&geometry, "{typeName:%q,degree:%g,left:%g,right:%g,top:%g,bottom:%g,positions:[]float64{%s}},\n", preset.kind, preset.degree, preset.left, preset.right, preset.top, preset.bottom, numbers(preset.stops)) + } + fmt.Fprintln(&geometry, "} }") + return public.Bytes(), geometry.Bytes(), nil +} + +func numberAndLanguageEnums(out *bytes.Buffer, styles *ast.File) error { + values := map[int]string{} + allIDs := map[int]bool{} + for _, name := range []string{"builtInNumFmt", "currencyNumFmt"} { + formats, ok := initializer(styles, name).(*ast.CompositeLit) + if !ok { + return fmt.Errorf("cannot locate %s", name) + } + for _, item := range formats.Elts { + kv, ok := item.(*ast.KeyValueExpr) + if !ok { + return fmt.Errorf("unexpected number format entry") + } + id, err := strconv.Atoi(expressionText(kv.Key)) + if err != nil { + return err + } + code, err := stringLiteral(kv.Value) + if err != nil { + return err + } + values[id] = code + allIDs[id] = true + } + } + languages, ok := initializer(styles, "langNumFmt").(*ast.CompositeLit) + if !ok { + return fmt.Errorf("cannot locate langNumFmt") + } + languageFormats := map[string]map[int]string{} + for _, item := range languages.Elts { + kv := item.(*ast.KeyValueExpr) + code, err := stringLiteral(kv.Key) + if err != nil { + return err + } + formats, ok := kv.Value.(*ast.CompositeLit) + if !ok { + return fmt.Errorf("unexpected language formats") + } + languageFormats[code] = map[int]string{} + for _, item := range formats.Elts { + kv := item.(*ast.KeyValueExpr) + id, err := strconv.Atoi(expressionText(kv.Key)) + if err != nil { + return err + } + value, err := stringLiteral(kv.Value) + if err != nil { + return err + } + languageFormats[code][id] = value + allIDs[id] = true + } + } + ids := []int{} + for id := range allIDs { + ids = append(ids, id) + } + sort.Ints(ids) + fmt.Fprintln(out, "type NumberFormat int\nconst (") + for _, id := range ids { + fmt.Fprintf(out, "NumberFormatID%d NumberFormat = %d\n", id, id) + } + fmt.Fprintln(out, ")\n\nfunc (value NumberFormat) Code() (string,bool) { switch value {") + for _, id := range ids { + if code, ok := values[id]; ok { + fmt.Fprintf(out, "case NumberFormatID%d: return %q,true\n", id, code) + } + } + out.WriteString("default:return \"\",false\n} }\n\n") + var codes []string + for code := range languageFormats { + codes = append(codes, code) + } + sort.Strings(codes) + if err := writeStringEnum(out, "StyleLanguage", codes); err != nil { + return err + } + fmt.Fprintln(out, "func (value NumberFormat) CodeForLanguage(language StyleLanguage) (string, bool) { switch language {") + for _, code := range codes { + fmt.Fprintf(out, "case %q: switch value {\n", code) + for _, id := range ids { + if value, ok := languageFormats[code][id]; ok { + fmt.Fprintf(out, "case NumberFormatID%d: return %q,true\n", id, value) + } + } + fmt.Fprintln(out, "}") + } + fmt.Fprintln(out, "}; return value.Code()\n}") + return nil +} + +func fieldEnums(out *bytes.Buffer, dir string) error { + file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(dir, "xmlStyles.go"), nil, 0) + if err != nil { + return err + } + wanted := map[string]bool{"Style": true, "Border": true, "Fill": true, "Font": true, "Alignment": true, "Protection": true} + found := map[string]bool{} + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, s := range group.Specs { + spec, ok := s.(*ast.TypeSpec) + if !ok || !wanted[spec.Name.Name] { + continue + } + structure, ok := spec.Type.(*ast.StructType) + if !ok { + return fmt.Errorf("%s is no longer a struct", spec.Name.Name) + } + var fields []string + for _, field := range structure.Fields.List { + for _, name := range field.Names { + if name.IsExported() { + fields = append(fields, name.Name) + } + } + } + if err := writeStringEnum(out, spec.Name.Name+"Field", fields); err != nil { + return err + } + found[spec.Name.Name] = true + } + } + if len(found) != len(wanted) { + return fmt.Errorf("one or more public Style types are missing") + } + return nil +} + +type gradientPreset struct { + kind string + degree, left, right, top, bottom float64 + stops []float64 +} + +func numbers(values []float64) string { + var parts []string + for _, v := range values { + parts = append(parts, strconv.FormatFloat(v, 'g', -1, 64)) + } + return strings.Join(parts, ",") +} +func numeric(expr ast.Expr) (float64, error) { return strconv.ParseFloat(expressionText(expr), 64) } +func gradientPresets(styles *ast.File) ([]gradientPreset, error) { + if expr := initializer(styles, "styleFillVariants"); expr != nil { + fn, ok := expr.(*ast.FuncLit) + if !ok { + call, called := expr.(*ast.CallExpr) + if called { + fn, ok = call.Fun.(*ast.FuncLit) + } + } + if !ok { + return nil, fmt.Errorf("unexpected styleFillVariants initializer") + } + var literals *ast.CompositeLit + for _, stmt := range fn.Body.List { + if ret, ok := stmt.(*ast.ReturnStmt); ok && len(ret.Results) == 1 { + literals, _ = ret.Results[0].(*ast.CompositeLit) + } + } + if literals == nil { + return nil, fmt.Errorf("missing gradient return literal") + } + var values []gradientPreset + for _, expr := range literals.Elts { + literal, ok := expr.(*ast.CompositeLit) + if !ok { + return nil, fmt.Errorf("unexpected gradient preset") + } + p := gradientPreset{} + for _, item := range literal.Elts { + kv, ok := item.(*ast.KeyValueExpr) + if !ok { + return nil, fmt.Errorf("unexpected gradient field") + } + name := expressionText(kv.Key) + switch name { + case "Type": + v, err := stringLiteral(kv.Value) + if err != nil { + return nil, err + } + p.kind = v + case "Stop": + stops, ok := kv.Value.(*ast.CompositeLit) + if !ok { + return nil, fmt.Errorf("unexpected gradient stops") + } + for _, expr := range stops.Elts { + stop, ok := expr.(*ast.CompositeLit) + if !ok { + return nil, fmt.Errorf("unexpected gradient stop") + } + position := float64(0) + for _, item := range stop.Elts { + kv := item.(*ast.KeyValueExpr) + if expressionText(kv.Key) != "Position" { + return nil, fmt.Errorf("unsupported gradient stop field") + } + var err error + position, err = numeric(kv.Value) + if err != nil { + return nil, err + } + } + p.stops = append(p.stops, position) + } + default: + if err := setGeometry(&p, name, kv.Value); err != nil { + return nil, err + } + } + } + if len(p.stops) < 2 || len(p.stops) > 3 { + return nil, fmt.Errorf("gradient stop count is not representable by the two-color API") + } + values = append(values, p) + } + return values, nil + } + // Excelize v2.6 builds presets with a switch and a degree lookup slice. + twoColors, indexedPositions := false, false + ast.Inspect(styles, func(n ast.Node) bool { + if node, ok := n.(*ast.IfStmt); ok && expressionText(node.Cond) == "len(style.Fill.Color) != 2" { + twoColors = true + } + if node, ok := n.(*ast.AssignStmt); ok && len(node.Lhs) == 1 && len(node.Rhs) == 1 && expressionText(node.Lhs[0]) == "stop.Position" && expressionText(node.Rhs[0]) == "float64(index)" { + indexedPositions = true + } + return true + }) + if !twoColors || !indexedPositions { + return nil, fmt.Errorf("unsupported legacy gradient stop construction") + } + variants, ok := initializer(styles, "variants").(*ast.CompositeLit) + if !ok { + return nil, fmt.Errorf("cannot locate legacy gradient variants") + } + var sw *ast.SwitchStmt + ast.Inspect(styles, func(n ast.Node) bool { + if v, ok := n.(*ast.SwitchStmt); ok && v.Tag != nil && strings.HasSuffix(expressionText(v.Tag), ".Fill.Shading") { + sw = v + return false + } + return true + }) + if sw == nil { + return nil, fmt.Errorf("cannot locate legacy shading switch") + } + indexed := map[int]gradientPreset{} + for _, stmt := range sw.Body.List { + c := stmt.(*ast.CaseClause) + for _, expr := range c.List { + id, err := strconv.Atoi(expressionText(expr)) + if err != nil { + return nil, err + } + p := gradientPreset{stops: []float64{0, 1}} + for _, stmt := range c.Body { + a, ok := stmt.(*ast.AssignStmt) + if !ok || len(a.Lhs) != 1 || len(a.Rhs) != 1 { + return nil, fmt.Errorf("unsupported legacy gradient statement") + } + lhs, ok := a.Lhs[0].(*ast.SelectorExpr) + if !ok || expressionText(lhs.X) != "gradient" { + return nil, fmt.Errorf("unsupported legacy gradient assignment") + } + switch lhs.Sel.Name { + case "Type": + p.kind, err = stringLiteral(a.Rhs[0]) + case "Degree": + if index, ok := a.Rhs[0].(*ast.IndexExpr); ok && expressionText(index.X) == "variants" { + if id >= len(variants.Elts) { + return nil, fmt.Errorf("gradient index out of range") + } + p.degree, err = numeric(variants.Elts[id]) + } else { + err = setGeometry(&p, "Degree", a.Rhs[0]) + } + default: + err = setGeometry(&p, lhs.Sel.Name, a.Rhs[0]) + } + if err != nil { + return nil, err + } + } + indexed[id] = p + } + } + values := make([]gradientPreset, len(indexed)) + for i := range values { + p, ok := indexed[i] + if !ok { + return nil, fmt.Errorf("non-contiguous gradient preset IDs") + } + values[i] = p + } + return values, nil +} +func setGeometry(p *gradientPreset, name string, value ast.Expr) error { + n, err := numeric(value) + if err != nil { + return err + } + switch name { + case "Degree": + p.degree = n + case "Left": + p.left = n + case "Right": + p.right = n + case "Top": + p.top = n + case "Bottom": + p.bottom = n + default: + return fmt.Errorf("unknown gradient field %s", name) + } + return nil +} diff --git a/excelizeutil/cmd/gen-style-enums/additional_test.go b/excelizeutil/cmd/gen-style-enums/additional_test.go new file mode 100644 index 0000000..cedb3a2 --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/additional_test.go @@ -0,0 +1,132 @@ +package main + +import ( + "bytes" + "go/parser" + "go/token" + "os" + "path/filepath" + "strings" + "testing" +) + +const fixtureStyles = `package excelize +var styleFillPatterns = []string{"none", "newPattern"} +var styleBorders = []string{"none", "newBorder"} +var styleBorderTypes = []string{"left", "diagonalUp"} +var builtInNumFmt = map[int]string{0:"general", 14:"mm-dd-yy"} +var currencyNumFmt = map[int]string{164:"currency-code"} +var langNumFmt = map[string]map[int]string{"ja-jp":{27:"localized-code"}} +var styleFillVariants = func() []xlsxGradientFill { return []xlsxGradientFill{ + {Degree:111, Stop:[]*xlsxGradientFillStop{{}, {Position:0.5}, {Position:1}}}, + {Type:"path", Left:0.5, Stop:[]*xlsxGradientFillStop{{}, {Position:1}}}, +} } +// The following the type of horizontal alignment in cells: +// +// Style +// ----- +// left +// newHorizontal +// +// The following the type of vertical alignment in cells: +// +// Style +// ----- +// top +// newVertical +// +func NewStyle() {} +func newFills(style Style) {switch style.Fill.Type {case "gradient":case "pattern":}} +` +const fixtureTypes = `package excelize +type Border struct {Type string; Color string; Style int} +type Fill struct {Type string; Pattern int; Color []string; Shading int} +type Font struct {Bold bool; Underline string; NewFlag bool} +type Alignment struct {Horizontal string; Vertical string; ReadingOrder uint64} +type Protection struct {Locked bool; Hidden bool} +type Style struct {Border []Border; Fill Fill; Font *Font; Alignment *Alignment; Protection *Protection; NumFmt int; NewValue string} +` + +func TestGenerationTracksNewFieldsValuesAndGradientPresets(t *testing.T) { + dir := t.TempDir() + for name, content := range map[string]string{"xmlStyles.go": fixtureTypes, "templates.go": `package excelize; var supportedUnderlineTypes=[]string{"none","single","double","newUnderline"}`} { + if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0600); err != nil { + t.Fatal(err) + } + } + file, err := parser.ParseFile(token.NewFileSet(), "styles.go", fixtureStyles, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + specs := []enumSpec{{typeName: "fillPattern", constantPrefix: "fillPattern", packageVar: "styleFillPatterns"}, {typeName: "borderStyle", constantPrefix: "borderStyle", packageVar: "styleBorders"}} + public, geometry, err := additionalEnums(dir, file, specs) + if err != nil { + t.Fatal(err) + } + for _, fragment := range []string{"FillPatternNewPattern", "BorderStyleNewBorder", "AlignmentHorizontalNewHorizontal", "FontUnderlineNewUnderline", "FontFieldNewFlag", "StyleFieldNewValue", "out.NewValue = in.NewValue", "NumberFormatID164", "NumberFormatID27", "GradientShadingPreset1"} { + if !strings.Contains(string(public), fragment) { + t.Errorf("generated output does not track upstream addition %q", fragment) + } + } + if !bytes.Contains(geometry, []byte("degree:111")) || !bytes.Contains(geometry, []byte("0,0.5,1")) { + t.Fatalf("gradient geometry was not derived from source: %s", geometry) + } + if _, err := parser.ParseFile(token.NewFileSet(), "generated.go", public, 0); err != nil { + t.Fatal(err) + } +} + +func TestUnknownGradientGeometryFailsGeneration(t *testing.T) { + source := strings.Replace(fixtureStyles, "Degree:111", "NewGeometry:111", 1) + file, err := parser.ParseFile(token.NewFileSet(), "styles.go", source, 0) + if err != nil { + t.Fatal(err) + } + if _, err := gradientPresets(file); err == nil { + t.Fatal("unknown gradient field was silently ignored") + } +} + +func TestCheckReportsStaleOutputWithoutWriting(t *testing.T) { + old := *checkOutput + *checkOutput = true + defer func() { *checkOutput = old }() + path := filepath.Join(t.TempDir(), "generated.go") + original := []byte("package stale\n") + if err := os.WriteFile(path, original, 0600); err != nil { + t.Fatal(err) + } + if err := saveGenerated(path, []byte("package updated\n")); err == nil { + t.Fatal("stale generated file accepted") + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, original) { + t.Fatal("check mode modified a file") + } +} + +func TestGeneratedFilesMatchSelectedDependency(t *testing.T) { + dir, err := excelizeModuleDir() + if err != nil { + t.Fatal(err) + } + public, private, err := generatedSources(dir) + if err != nil { + t.Fatal(err) + } + for path, expected := range map[string][]byte{ + "../../../excelizestyle/style_enums_gen.go": public, + "../../style_enums_gen.go": private, + } { + actual, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(actual, expected) { + t.Errorf("%s is stale for the selected Excelize dependency; run go generate ./excelizeutil", path) + } + } +} diff --git a/excelizeutil/cmd/gen-style-enums/colors.go b/excelizeutil/cmd/gen-style-enums/colors.go new file mode 100644 index 0000000..fb08554 --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/colors.go @@ -0,0 +1,40 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" +) + +// These identifiers are defined by OOXML, not by a closed enum in Excelize. +// Arbitrary underlying values remain expressible for future/custom definitions. +func colorEnums(out *bytes.Buffer, templates *ast.File) error { + paletteSize := 64 + if expr := initializer(templates, "IndexedColorMapping"); expr != nil { + colors, err := stringValues(expr, "IndexedColorMapping") + if err != nil { + return err + } + paletteSize = len(colors) + } + fmt.Fprintln(out, "type IndexedColor int\nconst (\nIndexedColorUnset IndexedColor = -1") + for i := 0; i < paletteSize; i++ { + fmt.Fprintf(out, "IndexedColorIndex%d IndexedColor = %d\n", i, i) + } + fmt.Fprintf(out, "IndexedColorSystemForeground IndexedColor = %d\nIndexedColorSystemBackground IndexedColor = %d\n)\n", 64, 65) + fmt.Fprintln(out, "type ThemeColor int\nconst (") + for i, name := range []string{"Light1", "Dark1", "Light2", "Dark2", "Accent1", "Accent2", "Accent3", "Accent4", "Accent5", "Accent6", "Hyperlink", "FollowedHyperlink"} { + fmt.Fprintf(out, "ThemeColor%s ThemeColor = %d\n", name, i) + } + fmt.Fprintln(out, ")\ntype FontCharset int\nconst (") + for _, v := range []struct { + name string + id int + }{ + {"ANSI", 0}, {"Default", 1}, {"Symbol", 2}, {"Mac", 77}, {"ShiftJIS", 128}, {"Hangul", 129}, {"Johab", 130}, {"GB2312", 134}, {"Big5", 136}, {"Greek", 161}, {"Turkish", 162}, {"Vietnamese", 163}, {"Hebrew", 177}, {"Arabic", 178}, {"Baltic", 186}, {"Russian", 204}, {"Thai", 222}, {"EastEurope", 238}, {"OEM", 255}, + } { + fmt.Fprintf(out, "FontCharset%s FontCharset = %d\n", v.name, v.id) + } + fmt.Fprintln(out, ")") + return nil +} diff --git a/excelizeutil/cmd/gen-style-enums/definitions.go b/excelizeutil/cmd/gen-style-enums/definitions.go new file mode 100644 index 0000000..19afac4 --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/definitions.go @@ -0,0 +1,151 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "path/filepath" + "strings" +) + +var definitionTypes = map[string]bool{"Style": true, "Border": true, "Fill": true, "Font": true, "Alignment": true, "Protection": true} +var enumFields = map[string]string{ + "Border.Type": "BorderPosition", "Border.Style": "BorderStyle", + "Fill.Type": "FillType", "Fill.Pattern": "FillPattern", "Fill.Shading": "GradientShading", + "Font.Underline": "FontUnderline", "Font.VertAlign": "FontVerticalAlign", + "Font.ColorIndexed": "IndexedColor", "Font.ColorTheme": "*ThemeColor", "Font.Charset": "*FontCharset", + "Alignment.Horizontal": "AlignmentHorizontal", "Alignment.Vertical": "AlignmentVertical", "Alignment.ReadingOrder": "AlignmentReadingOrder", + "Style.NumFmt": "NumberFormat", "Style.Lang": "StyleLanguage", +} + +func typedDefinitions(out *bytes.Buffer, dir string) error { + file, err := parser.ParseFile(token.NewFileSet(), filepath.Join(dir, "xmlStyles.go"), nil, 0) + if err != nil { + return err + } + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, raw := range group.Specs { + spec, ok := raw.(*ast.TypeSpec) + if !ok || !definitionTypes[spec.Name.Name] { + continue + } + structure, ok := spec.Type.(*ast.StructType) + if !ok { + return fmt.Errorf("%s is no longer a struct", spec.Name.Name) + } + name := spec.Name.Name + fmt.Fprintf(out, "// %sDefinition preserves all public excelize.%s fields with typed categorical values.\ntype %sDefinition struct {\n", name, name, name) + for _, field := range structure.Fields.List { + for _, id := range field.Names { + if !id.IsExported() { + continue + } + kind := definitionFieldType(field.Type) + if enum, ok := enumFields[name+"."+id.Name]; ok { + kind = enum + } + fmt.Fprintf(out, "%s %s\n", id.Name, kind) + } + } + fmt.Fprintln(out, "}") + for _, reverse := range []bool{false, true} { + if reverse { + fmt.Fprintf(out, "func %sDefinitionFromExcelize(in excelize.%s) %sDefinition {\n out := %sDefinition{}\n", name, name, name, name) + } else { + fmt.Fprintf(out, "func (in %sDefinition) ToExcelize() excelize.%s {\n out := excelize.%s{}\n", name, name, name) + } + for _, field := range structure.Fields.List { + for _, id := range field.Names { + if id.IsExported() { + writeConversion(out, name, id.Name, field.Type, reverse) + } + } + } + fmt.Fprintln(out, "return out\n}") + } + } + } + return nil +} + +func definitionFieldType(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + if definitionTypes[t.Name] { + return t.Name + "Definition" + } + switch t.Name { + case "bool", "int", "uint64", "float64", "string": + return t.Name + default: + return "excelize." + t.Name + } + case *ast.StarExpr: + return "*" + definitionFieldType(t.X) + case *ast.ArrayType: + if t.Len == nil { + return "[]" + definitionFieldType(t.Elt) + } + } + return expressionText(expr) +} + +func writeConversion(out *bytes.Buffer, owner, field string, expr ast.Expr, reverse bool) { + if enum, ok := enumFields[owner+"."+field]; ok { + if pointer, ok := expr.(*ast.StarExpr); ok { + cast := expressionText(pointer.X) + if reverse { + cast = strings.TrimPrefix(enum, "*") + } + fmt.Fprintf(out, "if in.%s != nil { value := %s(*in.%s); out.%s = &value }\n", field, cast, field, field) + return + } + cast := expressionText(expr) + if reverse { + cast = enum + } + fmt.Fprintf(out, "out.%s = %s(in.%s)\n", field, cast, field) + return + } + if id, ok := expr.(*ast.Ident); ok && definitionTypes[id.Name] { + if reverse { + fmt.Fprintf(out, "out.%s = %sDefinitionFromExcelize(in.%s)\n", field, id.Name, field) + } else { + fmt.Fprintf(out, "out.%s = in.%s.ToExcelize()\n", field, field) + } + return + } + if ptr, ok := expr.(*ast.StarExpr); ok { + if id, ok := ptr.X.(*ast.Ident); ok && definitionTypes[id.Name] { + if reverse { + fmt.Fprintf(out, "if in.%s != nil { value := %sDefinitionFromExcelize(*in.%s); out.%s = &value }\n", field, id.Name, field, field) + } else { + fmt.Fprintf(out, "if in.%s != nil { value := in.%s.ToExcelize(); out.%s = &value }\n", field, field, field) + } + return + } + } + if slice, ok := expr.(*ast.ArrayType); ok && slice.Len == nil { + if id, ok := slice.Elt.(*ast.Ident); ok && definitionTypes[id.Name] { + kind := "excelize." + id.Name + if reverse { + kind = id.Name + "Definition" + } + fmt.Fprintf(out, "if in.%s != nil { out.%s = make([]%s,len(in.%s)); for i, value := range in.%s {", field, field, kind, field, field) + if reverse { + fmt.Fprintf(out, "out.%s[i] = %sDefinitionFromExcelize(value)", field, id.Name) + } else { + fmt.Fprintf(out, "out.%s[i] = value.ToExcelize()", field) + } + fmt.Fprintln(out, "} }") + return + } + } + fmt.Fprintf(out, "out.%s = in.%s\n", field, field) +} diff --git a/excelizeutil/cmd/gen-style-enums/godoc.go b/excelizeutil/cmd/gen-style-enums/godoc.go new file mode 100644 index 0000000..7127bdd --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/godoc.go @@ -0,0 +1,237 @@ +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "sort" + "strconv" + "strings" +) + +const officialStyleDocs = "https://xuri.me/excelize/en/style.html" + +type enumDocumentation struct{ description, section string } + +var enumDocs = map[string]enumDocumentation{ + "FillPattern": {"selects the pattern index used by excelize.Fill.Pattern", "pattern"}, + "BorderStyle": {"selects the line style index used by excelize.Border.Style", "border"}, + "BorderPosition": {"selects the edge or diagonal used by excelize.Border.Type", "border"}, + "FillType": {"selects pattern or gradient filling for excelize.Fill.Type; the empty value leaves the fill unset", "fill"}, + "AlignmentHorizontal": {"selects horizontal cell alignment, including general alignment", "align"}, + "AlignmentVertical": {"selects vertical cell alignment, including bottom alignment", "align"}, + "AlignmentReadingOrder": {"selects context-dependent, left-to-right, or right-to-left cell text direction", "align"}, + "FontUnderline": {"selects the underline mode accepted by the selected Excelize dependency; the empty value leaves it unset", "underline"}, + "FontVerticalAlign": {"selects baseline, superscript, or subscript placement of font text", ""}, + "NumberFormat": {"selects an Excelize number-format ID; Code and CodeForLanguage resolve the dependency's format tables, not custom workbook definitions", "number_format"}, + "StyleLanguage": {"identifies a language tag in Excelize's localized number-format tables; newer Style APIs use the resolved format code", "number_format"}, + "GradientShading": {"selects a gradient preset in the selected Excelize dependency; preset numbering may change between versions", "shading"}, + "IndexedColor": {"selects an indexed palette entry or system color for excelize.Font.ColorIndexed; workbook palettes can override the displayed RGB", ""}, + "ThemeColor": {"selects a workbook theme slot for excelize.Font.ColorTheme; its displayed color depends on the workbook theme", ""}, + "FontCharset": {"identifies an OOXML font character set for excelize.Font.Charset; these numeric selectors are not RGB colors or text encodings to apply to cell values", ""}, +} + +func enumDoc(name string) enumDocumentation { + if doc, ok := enumDocs[name]; ok { + return doc + } + if strings.HasSuffix(name, "Field") { + owner := strings.TrimSuffix(name, "Field") + section := "" + switch owner { + case "Border": + section = "border" + case "Fill": + section = "fill" + case "Alignment": + section = "align" + } + return enumDocumentation{"identifies a public field of excelize." + owner + "; these values are field names rather than formatting values", section} + } + return enumDocumentation{"represents an Excelize style selector", ""} +} + +func docLink(_ enumDocumentation) string { + return officialStyleDocs +} + +// documentEnums adds GoDoc to generated enum types and every named constant. +// Descriptions are produced at generation time, so regeneration preserves them. +func documentEnums(source []byte) ([]byte, error) { + set := token.NewFileSet() + file, err := parser.ParseFile(set, "generated.go", source, parser.ParseComments) + if err != nil { + return nil, err + } + formatCodes := map[string]string{} + for _, declaration := range file.Decls { + function, ok := declaration.(*ast.FuncDecl) + if !ok || function.Name.Name != "Code" { + continue + } + ast.Inspect(function.Body, func(node ast.Node) bool { + clause, ok := node.(*ast.CaseClause) + if !ok || len(clause.List) != 1 || len(clause.Body) != 1 { + return true + } + name, ok := clause.List[0].(*ast.Ident) + if !ok { + return true + } + statement, ok := clause.Body[0].(*ast.ReturnStmt) + if !ok || len(statement.Results) < 1 { + return true + } + value, err := stringLiteral(statement.Results[0]) + if err == nil { + formatCodes[name.Name] = value + } + return true + }) + } + typeNames := map[string]bool{} + type insertion struct { + offset int + text string + } + var edits []insertion + add := func(pos token.Pos, name, description string, doc enumDocumentation) { + edits = append(edits, insertion{set.Position(pos).Offset, fmt.Sprintf("// %s %s.\n//\n// See %s\n", name, description, docLink(doc))}) + } + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok || group.Tok != token.TYPE { + continue + } + for _, raw := range group.Specs { + spec := raw.(*ast.TypeSpec) + underlying, ok := spec.Type.(*ast.Ident) + if !ok { + continue + } + if underlying.Name != "int" && underlying.Name != "uint64" && underlying.Name != "string" { + continue + } + name := spec.Name.Name + typeNames[name] = true + doc := enumDoc(name) + if spec.Doc == nil && group.Doc == nil { + add(group.Pos(), name, doc.description, doc) + } + } + } + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok || group.Tok != token.CONST { + continue + } + currentType := "" + for _, raw := range group.Specs { + spec := raw.(*ast.ValueSpec) + if id, ok := spec.Type.(*ast.Ident); ok { + currentType = id.Name + } + if !typeNames[currentType] { + continue + } + for _, id := range spec.Names { + if spec.Doc != nil { + continue + } + name := id.Name + doc := enumDoc(currentType) + description := enumValueDescription(currentType, strings.TrimPrefix(name, currentType)) + switch { + case strings.HasSuffix(name, "Count"): + description = "is the number of generated choices; it is not a valid formatting value" + case strings.HasSuffix(currentType, "Field"): + description = "identifies excelize." + strings.TrimSuffix(currentType, "Field") + "." + strings.TrimPrefix(name, currentType) + case strings.HasPrefix(name, "NumberFormatID"): + description = "selects Excelize number-format ID " + strings.TrimPrefix(name, "NumberFormatID") + if code, ok := formatCodes[name]; ok { + description += " with format code " + strconv.Quote(code) + } else { + description += "; its format depends on the language selected through CodeForLanguage" + } + case strings.HasPrefix(name, "GradientShadingPreset"): + description = "selects gradient preset " + strings.TrimPrefix(name, "GradientShadingPreset") + " in the selected Excelize dependency" + case name == "IndexedColorUnset": + description = "leaves the indexed font color selector unspecified (ID -1)" + case strings.HasPrefix(name, "IndexedColorIndex"): + description = "selects palette index " + strings.TrimPrefix(name, "IndexedColorIndex") + "; the workbook palette determines its RGB color" + case name == "IndexedColorSystemForeground": + description = "selects the system foreground color (index 64)" + case name == "IndexedColorSystemBackground": + description = "selects the system background color (index 65)" + case name == "AlignmentReadingOrderContext": + description = "derives text direction from the first strong directional character" + case name == "AlignmentReadingOrderLeftToRight": + description = "reads cell text from left to right" + case name == "AlignmentReadingOrderRightToLeft": + description = "reads cell text from right to left" + case len(spec.Values) == 1: + if literal, ok := spec.Values[0].(*ast.BasicLit); ok { + if literal.Kind == token.STRING { + value, e := strconv.Unquote(literal.Value) + if e != nil { + return nil, e + } + if value == "" { + description = "leaves the formatting selector unset" + } else { + description = stringValueDescription(currentType, value) + } + } else { + description += " (ID " + literal.Value + ")" + } + } + } + add(spec.Pos(), name, description, doc) + } + } + } + sort.SliceStable(edits, func(i, j int) bool { return edits[i].offset > edits[j].offset }) + result := string(source) + for _, edit := range edits { + result = result[:edit.offset] + edit.text + result[edit.offset:] + } + return []byte(result), nil +} + +func enumValueDescription(kind, value string) string { + if kind == "BorderStyle" { + names := map[string]string{"None": "no border", "Thin": "a thin solid border", "Medium": "a medium solid border", "Dashed": "a dashed border", "Dotted": "a dotted border", "Thick": "a thick solid border", "Double": "a double-line border", "Hair": "a hairline border", "MediumDashed": "a medium dashed border", "DashDot": "a dash-dot border", "MediumDashDot": "a medium dash-dot border", "DashDotDot": "a dash-dot-dot border", "MediumDashDotDot": "a medium dash-dot-dot border", "SlantDashDot": "a slanted dash-dot border"} + if description, ok := names[value]; ok { + return "selects " + description + } + } + if kind == "FillPattern" { + return "selects the " + strconv.Quote(strings.ToLower(value[:1])+value[1:]) + " cell-fill pattern" + } + if kind == "ThemeColor" { + return "selects the workbook theme's " + value + " color slot" + } + if kind == "FontCharset" { + return "selects the " + value + " font character set" + } + return "selects " + value +} + +func stringValueDescription(kind, value string) string { + meanings := map[string]map[string]string{ + "BorderPosition": {"left": "selects the left cell edge", "right": "selects the right cell edge", "top": "selects the top cell edge", "bottom": "selects the bottom cell edge", "diagonalUp": "selects the diagonal from bottom-left to top-right", "diagonalDown": "selects the diagonal from top-left to bottom-right"}, + "FillType": {"pattern": "uses the selected fill pattern and its foreground color", "gradient": "uses the selected gradient preset with two endpoint colors"}, + "AlignmentHorizontal": {"general": "aligns according to the cell value's data type", "left": "aligns text to the left edge", "center": "centers text horizontally", "right": "aligns text to the right edge", "fill": "repeats the cell text across the available width", "justify": "justifies text to both horizontal edges", "centerContinuous": "centers text across adjacent cells", "distributed": "distributes text evenly across the available width"}, + "AlignmentVertical": {"top": "aligns text to the top edge", "center": "centers text vertically", "bottom": "aligns text to the bottom edge", "justify": "justifies text vertically", "distributed": "distributes text evenly across the available height"}, + "FontUnderline": {"none": "explicitly disables underlining", "single": "uses a single underline", "double": "uses a double underline"}, + "FontVerticalAlign": {"baseline": "uses normal baseline text", "superscript": "raises text above the baseline", "subscript": "lowers text below the baseline"}, + } + if text, ok := meanings[kind][value]; ok { + return text + } + if kind == "StyleLanguage" { + return "identifies language tag " + strconv.Quote(value) + " for localized number-format lookup" + } + return "selects " + strconv.Quote(value) + " for " + kind +} diff --git a/excelizeutil/cmd/gen-style-enums/godoc_test.go b/excelizeutil/cmd/gen-style-enums/godoc_test.go new file mode 100644 index 0000000..cd51a10 --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/godoc_test.go @@ -0,0 +1,90 @@ +package main + +import ( + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" +) + +func TestEnumDocumentationCoversTypesAndConstants(t *testing.T) { + dir, err := excelizeModuleDir() + if err != nil { + t.Fatal(err) + } + public, _, err := generatedSources(dir) + if err != nil { + t.Fatal(err) + } + file, err := parser.ParseFile(token.NewFileSet(), "generated.go", public, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + enums := map[string]bool{} + check := func(name string, doc *ast.CommentGroup) { + t.Helper() + if doc == nil { + t.Errorf("%s has no GoDoc", name) + return + } + text := doc.Text() + if !strings.HasPrefix(text, name+" ") { + t.Errorf("%s GoDoc must start with its name: %s", name, text) + } + if !strings.Contains(text, officialStyleDocs) { + t.Errorf("%s has no official documentation reference", name) + } + } + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok || group.Tok != token.TYPE { + continue + } + for _, raw := range group.Specs { + spec := raw.(*ast.TypeSpec) + underlying, ok := spec.Type.(*ast.Ident) + if !ok { + continue + } + if underlying.Name != "int" && underlying.Name != "string" && underlying.Name != "uint64" { + continue + } + enums[spec.Name.Name] = true + doc := spec.Doc + if doc == nil { + doc = group.Doc + } + check(spec.Name.Name, doc) + } + } + count := 0 + for _, decl := range file.Decls { + group, ok := decl.(*ast.GenDecl) + if !ok || group.Tok != token.CONST { + continue + } + kind := "" + for _, raw := range group.Specs { + spec := raw.(*ast.ValueSpec) + if id, ok := spec.Type.(*ast.Ident); ok { + kind = id.Name + } + if !enums[kind] { + continue + } + for _, name := range spec.Names { + check(name.Name, spec.Doc) + count++ + } + } + } + if len(enums) < 10 || count < 100 { + t.Fatal("enum documentation inventory is unexpectedly small") + } + for _, fragment := range []string{"selects a thin solid border", "with format code", "path bounds", "identifies excelize.Font.Bold"} { + if !strings.Contains(string(public), fragment) { + t.Errorf("missing meaningful explanation %q", fragment) + } + } +} diff --git a/excelizeutil/cmd/gen-style-enums/main.go b/excelizeutil/cmd/gen-style-enums/main.go new file mode 100644 index 0000000..2d6c88c --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/main.go @@ -0,0 +1,294 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "unicode" +) + +type enumSpec struct { + typeName string + constantPrefix string + packageVar string + legacyFunction string + legacyVariable string +} + +var sourceDir = flag.String("source", "", "Excelize source directory (default: selected module)") +var checkOutput = flag.Bool("check", false, "fail if generated files need updating") + +func main() { + flag.Parse() + if err := generate(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func generate() error { + moduleDir := *sourceDir + var err error + if moduleDir == "" { + moduleDir, err = excelizeModuleDir() + } + if err != nil { + return err + } + public, private, err := generatedSources(moduleDir) + if err != nil { + return err + } + if err := saveGenerated("../excelizestyle/style_enums_gen.go", public); err != nil { + return err + } + return saveGenerated("style_enums_gen.go", private) +} + +func generatedSources(moduleDir string) ([]byte, []byte, error) { + filename := filepath.Join(moduleDir, "styles.go") + file, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors|parser.ParseComments) + if err != nil { + return nil, nil, fmt.Errorf("parse Excelize styles.go: %w", err) + } + + specs := []enumSpec{ + { + typeName: "fillPattern", + constantPrefix: "fillPattern", + packageVar: "styleFillPatterns", + legacyFunction: "newFills", + legacyVariable: "patterns", + }, + { + typeName: "borderStyle", + constantPrefix: "borderStyle", + packageVar: "styleBorders", + legacyFunction: "newBorders", + legacyVariable: "styles", + }, + } + + var output bytes.Buffer + output.WriteString("// Code generated by go generate; DO NOT EDIT.\n\npackage excelizeutil\n\nimport \"github.com/tomtwinkle/excelizeam/excelizestyle\"\n\n") + for _, spec := range specs { + publicName, err := enumIdentifier(spec.typeName) + if err != nil { + return nil, nil, err + } + fmt.Fprintf(&output, "type %s = excelizestyle.%s\nconst %sNone = excelizestyle.%sNone\nfunc parse%s(value string) (%s,bool) { return excelizestyle.Parse%s(value) }\n\n", spec.typeName, publicName, spec.typeName, publicName, publicName, spec.typeName, publicName) + } + + public, geometry, err := additionalEnums(moduleDir, file, specs) + if err != nil { + return nil, nil, err + } + output.Write(geometry) + public, err = format.Source(public) + if err != nil { + return nil, nil, err + } + public, err = documentEnums(public) + if err != nil { + return nil, nil, err + } + public, err = format.Source(public) + if err != nil { + return nil, nil, fmt.Errorf("format public enums: %w", err) + } + formatted, err := format.Source(output.Bytes()) + if err != nil { + return nil, nil, fmt.Errorf("format generated enums: %w", err) + } + return public, formatted, nil +} + +func excelizeModuleDir() (string, error) { + const modulePath = "github.com/xuri/excelize/v2" + download := exec.Command("go", "mod", "download", modulePath) + if output, err := download.CombinedOutput(); err != nil { + return "", fmt.Errorf("download Excelize module: %w: %s", err, strings.TrimSpace(string(output))) + } + command := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", modulePath) + output, err := command.CombinedOutput() + if err != nil { + return "", fmt.Errorf("locate Excelize module: %w: %s", err, strings.TrimSpace(string(output))) + } + dir := strings.TrimSpace(string(output)) + if dir == "" { + return "", fmt.Errorf("locate Excelize module: go list returned an empty directory") + } + return dir, nil +} + +func findEnumValues(file *ast.File, spec enumSpec) ([]string, error) { + for _, declaration := range file.Decls { + group, ok := declaration.(*ast.GenDecl) + if !ok || group.Tok != token.VAR { + continue + } + for _, rawSpec := range group.Specs { + valueSpec, ok := rawSpec.(*ast.ValueSpec) + if !ok { + continue + } + for i, name := range valueSpec.Names { + if name.Name != spec.packageVar { + continue + } + if i >= len(valueSpec.Values) { + return nil, fmt.Errorf("Excelize styles.go: %s has no initializer", spec.packageVar) + } + return stringValues(valueSpec.Values[i], spec.packageVar) + } + } + } + + for _, declaration := range file.Decls { + function, ok := declaration.(*ast.FuncDecl) + if !ok || function.Name.Name != spec.legacyFunction || function.Body == nil { + continue + } + var values []string + var found bool + var foundErr error + ast.Inspect(function.Body, func(node ast.Node) bool { + assignment, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + for i, left := range assignment.Lhs { + name, ok := left.(*ast.Ident) + if !ok || name.Name != spec.legacyVariable || i >= len(assignment.Rhs) { + continue + } + values, foundErr = stringValues(assignment.Rhs[i], spec.legacyVariable) + found = foundErr == nil + return false + } + return true + }) + if foundErr != nil { + return nil, foundErr + } + if found { + return values, nil + } + break + } + + return nil, fmt.Errorf( + "Excelize styles.go: could not find %s (expected package variable %s or %s.%s)", + spec.typeName, spec.packageVar, spec.legacyFunction, spec.legacyVariable, + ) +} + +func stringValues(expression ast.Expr, label string) ([]string, error) { + literal, ok := expression.(*ast.CompositeLit) + if !ok { + return nil, fmt.Errorf("Excelize styles.go: %s is not a composite literal", label) + } + array, ok := literal.Type.(*ast.ArrayType) + if !ok { + return nil, fmt.Errorf("Excelize styles.go: %s is not a slice or array", label) + } + identifier, ok := array.Elt.(*ast.Ident) + if !ok || identifier.Name != "string" { + return nil, fmt.Errorf("Excelize styles.go: %s is not a string slice", label) + } + values := make([]string, 0, len(literal.Elts)) + for _, element := range literal.Elts { + basic, ok := element.(*ast.BasicLit) + if !ok || basic.Kind != token.STRING { + return nil, fmt.Errorf("Excelize styles.go: %s contains a non-string value", label) + } + value, err := strconv.Unquote(basic.Value) + if err != nil { + return nil, fmt.Errorf("Excelize styles.go: decode %s value: %w", label, err) + } + values = append(values, value) + } + if len(values) == 0 { + return nil, fmt.Errorf("Excelize styles.go: %s is empty", label) + } + return values, nil +} + +func writeEnum(output *bytes.Buffer, spec enumSpec, values []string) error { + identifiers := make([]string, len(values)) + seenValues := make(map[string]struct{}, len(values)) + seenIdentifiers := make(map[string]struct{}, len(values)) + for i, value := range values { + if _, exists := seenValues[value]; exists { + return fmt.Errorf("Excelize styles.go: %s contains duplicate value %q", spec.typeName, value) + } + seenValues[value] = struct{}{} + name, err := enumIdentifier(value) + if err != nil { + return fmt.Errorf("Excelize styles.go: invalid %s value %q: %w", spec.typeName, value, err) + } + if _, exists := seenIdentifiers[name]; exists { + return fmt.Errorf("Excelize styles.go: %s values produce duplicate enum %s", spec.typeName, name) + } + seenIdentifiers[name] = struct{}{} + identifiers[i] = name + } + + fmt.Fprintf(output, "type %s int\n\nconst (\n", spec.typeName) + for i, identifier := range identifiers { + if i == 0 { + fmt.Fprintf(output, "\t%s%s %s = iota\n", spec.constantPrefix, identifier, spec.typeName) + } else { + fmt.Fprintf(output, "\t%s%s\n", spec.constantPrefix, identifier) + } + } + fmt.Fprintf(output, "\t%sCount\n)\n\n", spec.typeName) + + fmt.Fprintf(output, "func (value %s) String() string {\n\tswitch value {\n", spec.typeName) + for i, identifier := range identifiers { + fmt.Fprintf(output, "\tcase %s%s:\n\t\treturn %s\n", spec.constantPrefix, identifier, strconv.Quote(values[i])) + } + fmt.Fprintf(output, "\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n") + + typeName, err := enumIdentifier(spec.typeName) + if err != nil { + return err + } + parsePrefix := "parse" + if unicode.IsUpper([]rune(spec.typeName)[0]) { + parsePrefix = "Parse" + } + fmt.Fprintf(output, "func %s%s(value string) (%s, bool) {\n\tswitch value {\n", parsePrefix, typeName, spec.typeName) + for i, identifier := range identifiers { + fmt.Fprintf(output, "\tcase %s:\n\t\treturn %s%s, true\n", strconv.Quote(values[i]), spec.constantPrefix, identifier) + } + fmt.Fprintf(output, "\tdefault:\n\t\treturn 0, false\n\t}\n}\n\n") + return nil +} + +func enumIdentifier(value string) (string, error) { + runes := []rune(value) + if len(runes) == 0 { + return "", fmt.Errorf("empty value") + } + for _, character := range runes { + if !unicode.IsLetter(character) && !unicode.IsDigit(character) && character != '_' { + return "", fmt.Errorf("unsupported character %q", character) + } + } + if !unicode.IsLetter(runes[0]) && runes[0] != '_' { + return "", fmt.Errorf("identifier must start with a letter or underscore") + } + runes[0] = unicode.ToUpper(runes[0]) + name := string(runes) + return name, nil +} diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go new file mode 100644 index 0000000..39a120f --- /dev/null +++ b/excelizeutil/excelizeutil.go @@ -0,0 +1,891 @@ +//go:generate go run ./cmd/gen-style-enums + +package excelizeutil + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "reflect" + "strings" + + "github.com/tomtwinkle/excelizeam/excelizestyle" + "github.com/xuri/excelize/v2" +) + +var errInvalidStyleReference = errors.New("invalid style reference") + +// GetStyle returns the effective style for a cell in an XLSX workbook. +// It returns an error when the workbook, coordinates, sheet, or cell style is invalid. +func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) (excelize.Style, error) { + themeColors, numberFormats, indexedColors := readStyleMetadata(excelBuffer) + f, err := excelize.OpenReader(&excelBuffer) + if err != nil { + return excelize.Style{}, fmt.Errorf("open workbook: %w", err) + } + defer func() { _ = f.Close() }() + + cell, err := excelize.CoordinatesToCellName(corIdx, rowIdx) + if err != nil { + return excelize.Style{}, fmt.Errorf("convert cell coordinates: %w", err) + } + styleID, err := f.GetCellStyle(sheetName, cell) + if err != nil { + return excelize.Style{}, fmt.Errorf("get cell style: %w", err) + } + + format, err := resolveCellFormat(f, styleID) + if err != nil { + return excelize.Style{}, fmt.Errorf("resolve cell style: %w", err) + } + if err := validateCellStyleReferences(f, format, numberFormats); err != nil { + return excelize.Style{}, fmt.Errorf("resolve cell style: %w", err) + } + numFmt, customNumFmt := getCellNumberFormat(format.NumFmtID, numberFormats) + style := excelize.Style{ + Border: getCellBorder(f, format.BorderID, themeColors, indexedColors), + Fill: getCellFill(f, format.FillID, themeColors, indexedColors), + Font: getCellFont(f, format.FontID, format.HasFont, themeColors, indexedColors), + Alignment: format.Alignment, + Protection: format.Protection, + NumFmt: numFmt, + CustomNumFmt: customNumFmt, + } + applyNativeNumberFormatMetadata(&style, f, styleID, format, customNumFmt) + return style, nil +} + +// GetStyleDefinition returns the effective cell style with typed categorical fields. +func GetStyleDefinition(excelBuffer bytes.Buffer, sheetName string, column, row int) (excelizestyle.StyleDefinition, error) { + style, err := GetStyle(excelBuffer, sheetName, column, row) + if err != nil { + return excelizestyle.StyleDefinition{}, err + } + return excelizestyle.StyleDefinitionFromExcelize(style), nil +} + +type resolvedCellFormat struct { + FontID int + HasFont bool + FillID int + BorderID int + NumFmtID int + Alignment *excelize.Alignment + Protection *excelize.Protection +} + +func resolveCellFormat(f *excelize.File, styleID int) (resolvedCellFormat, error) { + if f.Styles == nil || f.Styles.CellXfs == nil || styleID < 0 || styleID >= len(f.Styles.CellXfs.Xf) { + return resolvedCellFormat{}, fmt.Errorf("%w: cell style ID %d is out of range", errInvalidStyleReference, styleID) + } + cellXF := f.Styles.CellXfs.Xf[styleID] + resolved := resolvedCellFormat{ + FontID: integerValue(cellXF.FontID), + HasFont: inheritedStylePresent(cellXF.FontID, nil, cellXF.ApplyFont), + FillID: integerValue(cellXF.FillID), + BorderID: integerValue(cellXF.BorderID), + NumFmtID: integerValue(cellXF.NumFmtID), + } + alignment := cellXF.Alignment + protection := cellXF.Protection + if cellXF.XfID != nil { + if f.Styles.CellStyleXfs == nil || *cellXF.XfID < 0 || *cellXF.XfID >= len(f.Styles.CellStyleXfs.Xf) { + return resolvedCellFormat{}, fmt.Errorf("%w: xfId=%d references cellStyleXfs with %d entries", errInvalidStyleReference, *cellXF.XfID, cellStyleXFCount(f)) + } + baseXF := f.Styles.CellStyleXfs.Xf[*cellXF.XfID] + resolved.FontID = inheritedStyleID(cellXF.FontID, baseXF.FontID, cellXF.ApplyFont) + resolved.HasFont = inheritedStylePresent(cellXF.FontID, baseXF.FontID, cellXF.ApplyFont) + resolved.FillID = inheritedStyleID(cellXF.FillID, baseXF.FillID, cellXF.ApplyFill) + resolved.BorderID = inheritedStyleID(cellXF.BorderID, baseXF.BorderID, cellXF.ApplyBorder) + resolved.NumFmtID = inheritedStyleID(cellXF.NumFmtID, baseXF.NumFmtID, cellXF.ApplyNumberFormat) + + if cellXF.ApplyAlignment == nil || !*cellXF.ApplyAlignment { + if baseXF.Alignment != nil { + alignment = baseXF.Alignment + } else if cellXF.ApplyAlignment != nil { + alignment = nil + } + } + if cellXF.ApplyProtection == nil || !*cellXF.ApplyProtection { + if baseXF.Protection != nil { + protection = baseXF.Protection + } else if cellXF.ApplyProtection != nil { + protection = nil + } + } + } + if alignment != nil { + resolved.Alignment = &excelize.Alignment{ + Horizontal: alignment.Horizontal, + Indent: alignment.Indent, + JustifyLastLine: alignment.JustifyLastLine, + ReadingOrder: alignment.ReadingOrder, + RelativeIndent: alignment.RelativeIndent, + ShrinkToFit: alignment.ShrinkToFit, + TextRotation: alignment.TextRotation, + Vertical: alignment.Vertical, + WrapText: alignment.WrapText, + } + } + if protection != nil { + resolved.Protection = &excelize.Protection{Locked: true} + if protection.Locked != nil { + resolved.Protection.Locked = *protection.Locked + } + if protection.Hidden != nil { + resolved.Protection.Hidden = *protection.Hidden + } + } + return resolved, nil +} + +func cellStyleXFCount(f *excelize.File) int { + if f == nil || f.Styles == nil || f.Styles.CellStyleXfs == nil { + return 0 + } + return len(f.Styles.CellStyleXfs.Xf) +} + +func validateCellStyleReferences(f *excelize.File, format resolvedCellFormat, numberFormats map[int]string) error { + styles := f.Styles + if format.HasFont { + if err := validateStyleCollectionReference("fontId", format.FontID, fontCount(f)); err != nil { + return err + } + if styles.Fonts.Font[format.FontID] == nil { + return fmt.Errorf("%w: fontId=%d references an empty font entry", errInvalidStyleReference, format.FontID) + } + } + if err := validateStyleCollectionReference("fillId", format.FillID, fillCount(f)); err != nil { + return err + } + if styles.Fills.Fill[format.FillID] == nil { + return fmt.Errorf("%w: fillId=%d references an empty fill entry", errInvalidStyleReference, format.FillID) + } + if err := validateStyleCollectionReference("borderId", format.BorderID, borderCount(f)); err != nil { + return err + } + if styles.Borders.Border[format.BorderID] == nil { + return fmt.Errorf("%w: borderId=%d references an empty border entry", errInvalidStyleReference, format.BorderID) + } + if format.NumFmtID < 0 { + return fmt.Errorf("%w: numFmtId=%d is negative", errInvalidStyleReference, format.NumFmtID) + } + if format.NumFmtID >= 164 { + if _, ok := numberFormats[format.NumFmtID]; !ok { + return fmt.Errorf("%w: numFmtId=%d has no custom number format definition", errInvalidStyleReference, format.NumFmtID) + } + } + return nil +} + +func validateStyleCollectionReference(name string, id, length int) error { + if id < 0 || id >= length { + return fmt.Errorf("%w: %s=%d is out of range for a collection with %d entries", errInvalidStyleReference, name, id, length) + } + return nil +} + +func fontCount(f *excelize.File) int { + if f == nil || f.Styles == nil || f.Styles.Fonts == nil { + return 0 + } + return len(f.Styles.Fonts.Font) +} + +func fillCount(f *excelize.File) int { + if f == nil || f.Styles == nil || f.Styles.Fills == nil { + return 0 + } + return len(f.Styles.Fills.Fill) +} + +func borderCount(f *excelize.File) int { + if f == nil || f.Styles == nil || f.Styles.Borders == nil { + return 0 + } + return len(f.Styles.Borders.Border) +} + +func inheritedStylePresent(child, parent *int, apply *bool) bool { + if apply != nil && *apply { + // fontId defaults to zero in CT_Xf, so apply=true with no explicit + // fontId still applies the default (possibly customized) font record. + return true + } + if parent != nil { + return true + } + if apply != nil { // apply=false with no base XF means no child style is active. + return false + } + return child != nil && *child != 0 +} + +func integerValue(value *int) int { + if value == nil { + return 0 + } + return *value +} + +func inheritedStyleID(child, parent *int, apply *bool) int { + if apply != nil && *apply { + return integerValue(child) + } + if parent != nil { + return *parent + } + if apply != nil && !*apply { + return 0 + } + return integerValue(child) +} + +func getCellBorder(f *excelize.File, borderID int, themeColors []string, indexedColors map[int]string) []excelize.Border { + if f.Styles.Borders == nil || borderID < 0 || borderID >= len(f.Styles.Borders.Border) || f.Styles.Borders.Border[borderID] == nil { + return nil + } + + definition := f.Styles.Borders.Border[borderID] + borders := make([]excelize.Border, 0, 6) + appendBorder := func(side, style string, color *xlsxColor) { + if style == "" { + return + } + border := excelize.Border{ + Type: side, + Style: borderStyleID(style), + } + if colors := getCellFillColor(color, themeColors, indexedColors); len(colors) > 0 { + border.Color = colors[0] + } + borders = append(borders, border) + } + + topStyle, topColor := borderLine(definition.Top) + bottomStyle, bottomColor := borderLine(definition.Bottom) + leftStyle, leftColor := borderLine(definition.Left) + rightStyle, rightColor := borderLine(definition.Right) + appendBorder("top", topStyle, topColor) + appendBorder("bottom", bottomStyle, bottomColor) + appendBorder("left", leftStyle, leftColor) + appendBorder("right", rightStyle, rightColor) + if definition.DiagonalUp { + diagonalStyle, diagonalColor := borderLine(definition.Diagonal) + appendBorder("diagonalUp", diagonalStyle, diagonalColor) + } + if definition.DiagonalDown { + diagonalStyle, diagonalColor := borderLine(definition.Diagonal) + appendBorder("diagonalDown", diagonalStyle, diagonalColor) + } + return borders +} + +func borderLine(line any) (string, *xlsxColor) { + value := reflect.ValueOf(line) + if !value.IsValid() { + return "", nil + } + if value.Kind() == reflect.Ptr { + if value.IsNil() { + return "", nil + } + value = value.Elem() + } + if value.Kind() != reflect.Struct { + return "", nil + } + style := value.FieldByName("Style") + if !style.IsValid() || style.Kind() != reflect.String { + return "", nil + } + return style.String(), colorValue(value.FieldByName("Color")) +} + +func borderStyleID(style string) int { + if value, ok := parseBorderStyle(style); ok { + return int(value) + } + return int(borderStyleNone) +} + +func getCellFill(f *excelize.File, fillID int, themeColors []string, indexedColors map[int]string) excelize.Fill { + if f.Styles.Fills == nil || fillID < 0 || fillID >= len(f.Styles.Fills.Fill) || f.Styles.Fills.Fill[fillID] == nil { + return excelize.Fill{} + } + definition := f.Styles.Fills.Fill[fillID] + if definition.GradientFill != nil { + return getCellGradientFill(definition.GradientFill, themeColors, indexedColors) + } + if definition.PatternFill == nil { + return excelize.Fill{} + } + patternFill := definition.PatternFill + + pattern := int(fillPatternNone) + if value, ok := parseFillPattern(patternFill.PatternType); ok { + pattern = int(value) + } + + color := patternFill.FgColor + if color == nil { + color = patternFill.BgColor + } + return excelize.Fill{ + Type: "pattern", + Pattern: pattern, + Color: getCellFillColor((*xlsxColor)(color), themeColors, indexedColors), + } +} + +type gradientGeometry struct { + typeName string + degree float64 + left float64 + right float64 + top float64 + bottom float64 + positions []float64 +} + +func getCellGradientFill(value any, themeColors []string, indexedColors map[int]string) excelize.Fill { + geometry, colors, ok := readGradient(value) + if !ok { + return excelize.Fill{Type: "gradient"} + } + for shading, preset := range gradientGeometries() { + if !sameGradientGeometry(geometry, preset) || len(colors) != len(preset.positions) { + continue + } + matchesPositions := true + for index, position := range preset.positions { + if colors[index].position != position { + matchesPositions = false + break + } + } + if !matchesPositions { + continue + } + resolved := make([][]string, len(colors)) + for index, stop := range colors { + resolved[index] = getCellFillColor(stop.color, themeColors, indexedColors) + if len(resolved[index]) == 0 { + return excelize.Fill{Type: "gradient"} + } + } + if len(colors) == 3 && !reflect.DeepEqual(resolved[0], resolved[2]) { + // Excelize's public API models these presets as color0, color1, + // color0. A different final stop cannot be represented. + return excelize.Fill{Type: "gradient"} + } + return excelize.Fill{Type: "gradient", Shading: shading, Color: []string{resolved[0][0], resolved[1][0]}} + } + return excelize.Fill{Type: "gradient"} +} + +func readGradient(value any) (gradientGeometry, []struct { + position float64 + color *xlsxColor +}, bool) { + ref := reflect.ValueOf(value) + if !ref.IsValid() { + return gradientGeometry{}, nil, false + } + if ref.Kind() == reflect.Ptr { + if ref.IsNil() { + return gradientGeometry{}, nil, false + } + ref = ref.Elem() + } + if ref.Kind() != reflect.Struct { + return gradientGeometry{}, nil, false + } + getFloat := func(name string) float64 { + field := ref.FieldByName(name) + if field.IsValid() && field.CanFloat() { + return field.Float() + } + return 0 + } + getString := func(name string) string { + field := ref.FieldByName(name) + if field.IsValid() && field.Kind() == reflect.String { + return field.String() + } + return "" + } + geometry := gradientGeometry{ + typeName: getString("Type"), + degree: getFloat("Degree"), + left: getFloat("Left"), + right: getFloat("Right"), + top: getFloat("Top"), + bottom: getFloat("Bottom"), + } + stops := ref.FieldByName("Stop") + if !stops.IsValid() || stops.Kind() != reflect.Slice { + return gradientGeometry{}, nil, false + } + colors := make([]struct { + position float64 + color *xlsxColor + }, 0, stops.Len()) + for index := 0; index < stops.Len(); index++ { + stop := stops.Index(index) + if stop.Kind() == reflect.Ptr { + if stop.IsNil() { + return gradientGeometry{}, nil, false + } + stop = stop.Elem() + } + position := stop.FieldByName("Position") + color := stop.FieldByName("Color") + if !position.IsValid() || !position.CanFloat() || !color.IsValid() { + return gradientGeometry{}, nil, false + } + colors = append(colors, struct { + position float64 + color *xlsxColor + }{position: position.Float(), color: colorValue(color)}) + } + geometry.positions = make([]float64, len(colors)) + for index, stop := range colors { + geometry.positions[index] = stop.position + } + return geometry, colors, true +} + +func hasExcelizeField(typ reflect.Type, fieldName string) bool { + _, ok := typ.FieldByName(fieldName) + return ok +} + +func sameGradientGeometry(a, b gradientGeometry) bool { + return a.typeName == b.typeName && a.degree == b.degree && a.left == b.left && a.right == b.right && a.top == b.top && a.bottom == b.bottom +} + +func getCellFont(f *excelize.File, fontID int, hasFont bool, themeColors []string, indexedColors map[int]string) *excelize.Font { + if f.Styles.Fonts == nil || fontID < 0 || fontID >= len(f.Styles.Fonts.Font) || (!hasFont && fontID == 0) || f.Styles.Fonts.Font[fontID] == nil { + return nil + } + font := f.Styles.Fonts.Font[fontID] + result := &excelize.Font{} + // Excelize v2.10.1 treats ColorIndexed's zero value as an explicit + // indexed color. Use -1 unless the OOXML color actually selects an index. + setExcelizeField(result, "ColorIndexed", -1) + result.Bold = xmlBooleanValue(font.B) + result.Italic = xmlBooleanValue(font.I) + if font.U != nil { + result.Underline = "single" + if font.U.Val != nil && *font.U.Val != "" { + result.Underline = *font.U.Val + } + } + if font.Name != nil && font.Name.Val != nil { + result.Family = *font.Name.Val + } + if font.Sz != nil && font.Sz.Val != nil { + result.Size = *font.Sz.Val + } + result.Strike = xmlBooleanValue(font.Strike) + color := colorValue(reflect.ValueOf(font.Color)) + if color != nil { + if hasExcelizeField(reflect.TypeOf(excelize.Font{}), "ColorTint") { + // v2.10.1 exposes tint and the theme/indexed selectors separately. + // Keep the untinted RGB fallback; returning a resolved tint here too + // would cause NewStyle to serialize and apply the tint twice. + result.Color = normalizeRGB(color.RGB) + } else if colors := getCellFillColor(color, themeColors, indexedColors); len(colors) > 0 { + // Older Style APIs lack selector and tint fields, so keep appearance. + result.Color = colors[0] + } + } + if color != nil { + if color.RGB == "" && color.Theme == nil && !color.Auto { + setExcelizeField(result, "ColorIndexed", color.Indexed) + } + if color.Theme != nil { + setExcelizeField(result, "ColorTheme", color.Theme) + } + setExcelizeField(result, "ColorTint", color.Tint) + } + if charset, ok := xmlAttributeValue(font, "Charset", "Val"); ok { + setExcelizeField(result, "Charset", charset) + } + if vertical, ok := xmlAttributeValue(font, "VertAlign", "Val"); ok { + setExcelizeField(result, "VertAlign", vertical) + } + return result +} + +func xmlBooleanValue(value any) bool { + ref := reflect.ValueOf(value) + if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.IsNil() { + return false + } + attribute := ref.Elem().FieldByName("Val") + if !attribute.IsValid() || (attribute.Kind() == reflect.Ptr && attribute.IsNil()) { + return true + } + if attribute.Kind() == reflect.Ptr { + attribute = attribute.Elem() + } + return attribute.Bool() +} + +func xmlAttributeValue(value any, elementName, attributeName string) (any, bool) { + ref := reflect.ValueOf(value) + if !ref.IsValid() { + return nil, false + } + if ref.Kind() == reflect.Ptr { + if ref.IsNil() { + return nil, false + } + ref = ref.Elem() + } + element := ref.FieldByName(elementName) + if !element.IsValid() || element.Kind() != reflect.Ptr || element.IsNil() { + return nil, false + } + attribute := element.Elem().FieldByName(attributeName) + if !attribute.IsValid() { + return nil, false + } + if attribute.Kind() == reflect.Ptr { + if attribute.IsNil() { + return nil, false + } + attribute = attribute.Elem() + } + return attribute.Interface(), true +} + +func colorValue(value reflect.Value) *xlsxColor { + if !value.IsValid() { + return nil + } + if value.Kind() == reflect.Ptr { + if value.IsNil() { + return nil + } + value = value.Elem() + } + if value.Kind() != reflect.Struct { + return nil + } + result := &xlsxColor{} + if field := value.FieldByName("Auto"); field.IsValid() && field.Kind() == reflect.Bool { + result.Auto = field.Bool() + } + if field := value.FieldByName("RGB"); field.IsValid() && field.Kind() == reflect.String { + result.RGB = field.String() + } + if field := value.FieldByName("Indexed"); field.IsValid() && field.Kind() == reflect.Int { + result.Indexed = int(field.Int()) + } + if field := value.FieldByName("Theme"); field.IsValid() && field.Kind() == reflect.Ptr && !field.IsNil() { + theme := int(field.Elem().Int()) + result.Theme = &theme + } + if field := value.FieldByName("Tint"); field.IsValid() && field.Kind() == reflect.Float64 { + result.Tint = field.Float() + } + return result +} + +func setExcelizeField(target any, name string, value any) bool { + ref := reflect.ValueOf(target) + if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.IsNil() { + return false + } + field := ref.Elem().FieldByName(name) + if !field.IsValid() || !field.CanSet() { + return false + } + input := reflect.ValueOf(value) + if field.Kind() == reflect.Ptr && input.Type().AssignableTo(field.Type().Elem()) { + pointer := reflect.New(field.Type().Elem()) + pointer.Elem().Set(input) + field.Set(pointer) + return true + } + if input.Type().AssignableTo(field.Type()) { + field.Set(input) + return true + } + if input.Type().ConvertibleTo(field.Type()) { + field.Set(input.Convert(field.Type())) + return true + } + return false +} + +func getCellNumberFormat(numFmtID int, numberFormats map[int]string) (int, *string) { + if format, ok := numberFormats[numFmtID]; ok { + return 0, &format + } + if numFmtID >= 0 && numFmtID < 164 { + return numFmtID, nil + } + return 0, nil +} + +type excelizeStyleGetter interface { + GetStyle(int) (*excelize.Style, error) +} + +func applyNativeNumberFormatMetadata(style *excelize.Style, f *excelize.File, styleID int, resolved resolvedCellFormat, customNumFmt *string) { + if customNumFmt != nil || f.Styles.CellXfs == nil || styleID < 0 || styleID >= len(f.Styles.CellXfs.Xf) { + return + } + cellXF := f.Styles.CellXfs.Xf[styleID] + if cellXF.NumFmtID == nil || *cellXF.NumFmtID != resolved.NumFmtID { + // A named-style XF can supply the effective ID. Its custom code is + // retained above; do not infer currency metadata from format text. + return + } + getter, ok := any(f).(excelizeStyleGetter) + if !ok { + return + } + nativeStyle, err := getter.GetStyle(styleID) + if err != nil || nativeStyle == nil { + return + } + if decimalPlaces, ok := excelizePublicFieldValue(nativeStyle, "DecimalPlaces"); ok { + setExcelizeField(style, "DecimalPlaces", decimalPlaces) + } + if negativeRed, ok := excelizePublicFieldValue(nativeStyle, "NegRed"); ok { + setExcelizeField(style, "NegRed", negativeRed) + } +} + +func excelizePublicFieldValue(target any, name string) (any, bool) { + ref := reflect.ValueOf(target) + if !ref.IsValid() { + return nil, false + } + if ref.Kind() == reflect.Ptr { + if ref.IsNil() { + return nil, false + } + ref = ref.Elem() + } + field := ref.FieldByName(name) + if !field.IsValid() { + return nil, false + } + if field.Kind() == reflect.Ptr { + if field.IsNil() { + return nil, false + } + field = field.Elem() + } + return field.Interface(), true +} + +type xlsxColor struct { + Auto bool `xml:"auto,attr,omitempty"` + RGB string `xml:"rgb,attr,omitempty"` + Indexed int `xml:"indexed,attr,omitempty"` + Theme *int `xml:"theme,attr"` + Tint float64 `xml:"tint,attr,omitempty"` +} + +func getCellFillColor(color *xlsxColor, themeColors []string, indexedColors map[int]string) []string { + if color == nil || color.Auto { + return nil + } + + rgb := color.RGB + if color.Theme != nil { + index := *color.Theme + if index < 0 || index >= len(themeColors) { + return nil + } + rgb = themeColors[index] + } else if rgb == "" { + rgb = indexedColor(indexedColors, color.Indexed) + } + + rgb = normalizeRGB(rgb) + if rgb == "" { + return nil + } + if color.Tint != 0 { + rgb = normalizeRGB(excelize.ThemeColor(strings.TrimPrefix(rgb, "#"), color.Tint)) + } + return []string{rgb} +} + +func normalizeRGB(rgb string) string { + rgb = strings.TrimPrefix(strings.ToUpper(rgb), "#") + if len(rgb) == 8 { + rgb = rgb[2:] + } + if len(rgb) != 6 { + return "" + } + return "#" + rgb +} + +func indexedColor(indexedColors map[int]string, index int) string { + if index < 0 { + return "" + } + if color, ok := indexedColors[index]; ok { + return color + } + if index < len(indexedColorMapping) { + return indexedColorMapping[index] + } + return "" +} + +func readStyleMetadata(buffer bytes.Buffer) ([]string, map[int]string, map[int]string) { + reader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(buffer.Len())) + if err != nil { + return nil, nil, nil + } + + var themeColors []string + var numberFormats map[int]string + var indexedColors map[int]string + for _, entry := range reader.File { + if entry.Name != "xl/theme/theme1.xml" && entry.Name != "xl/styles.xml" { + continue + } + file, err := entry.Open() + if err != nil { + continue + } + data, readErr := io.ReadAll(file) + _ = file.Close() + if readErr != nil { + continue + } + switch entry.Name { + case "xl/theme/theme1.xml": + themeColors = parseThemeColors(data) + case "xl/styles.xml": + numberFormats = parseNumberFormats(data) + indexedColors = parseIndexedColors(data) + } + } + return themeColors, numberFormats, indexedColors +} + +func parseThemeColors(data []byte) []string { + type colorChoice struct { + SysColor *struct { + LastColor string `xml:"lastClr,attr"` + } `xml:"sysClr"` + SRGBColor *struct { + Value string `xml:"val,attr"` + } `xml:"srgbClr"` + } + var theme struct { + Elements struct { + Scheme struct { + Dk1 colorChoice `xml:"dk1"` + Lt1 colorChoice `xml:"lt1"` + Dk2 colorChoice `xml:"dk2"` + Lt2 colorChoice `xml:"lt2"` + Accent1 colorChoice `xml:"accent1"` + Accent2 colorChoice `xml:"accent2"` + Accent3 colorChoice `xml:"accent3"` + Accent4 colorChoice `xml:"accent4"` + Accent5 colorChoice `xml:"accent5"` + Accent6 colorChoice `xml:"accent6"` + Hlink colorChoice `xml:"hlink"` + FolHlink colorChoice `xml:"folHlink"` + } `xml:"clrScheme"` + } `xml:"themeElements"` + } + if xml.Unmarshal(data, &theme) != nil { + return nil + } + choices := []colorChoice{ + theme.Elements.Scheme.Lt1, theme.Elements.Scheme.Dk1, + theme.Elements.Scheme.Lt2, theme.Elements.Scheme.Dk2, + theme.Elements.Scheme.Accent1, theme.Elements.Scheme.Accent2, + theme.Elements.Scheme.Accent3, theme.Elements.Scheme.Accent4, + theme.Elements.Scheme.Accent5, theme.Elements.Scheme.Accent6, + theme.Elements.Scheme.Hlink, theme.Elements.Scheme.FolHlink, + } + colors := make([]string, len(choices)) + for index, choice := range choices { + switch { + case choice.SysColor != nil: + colors[index] = choice.SysColor.LastColor + case choice.SRGBColor != nil: + colors[index] = choice.SRGBColor.Value + } + } + return colors +} + +func parseNumberFormats(data []byte) map[int]string { + type numberFormat struct { + ID int `xml:"numFmtId,attr"` + FormatCode string `xml:"formatCode,attr"` + FormatCode16 string `xml:"http://schemas.microsoft.com/office/spreadsheetml/2015/02/main formatCode16,attr"` + } + var formats struct { + NumFmts struct { + NumFmt []numberFormat `xml:"numFmt"` + } `xml:"numFmts"` + } + if xml.Unmarshal(data, &formats) != nil { + return nil + } + result := make(map[int]string, len(formats.NumFmts.NumFmt)) + for _, format := range formats.NumFmts.NumFmt { + code := format.FormatCode16 + if code == "" { + code = format.FormatCode + } + if code != "" { + result[format.ID] = code + } + } + return result +} + +func parseIndexedColors(data []byte) map[int]string { + var styles struct { + Colors struct { + IndexedColors struct { + RGBColors []struct { + RGB string `xml:"rgb,attr"` + } `xml:"rgbColor"` + } `xml:"indexedColors"` + } `xml:"colors"` + } + if xml.Unmarshal(data, &styles) != nil { + return nil + } + colors := make(map[int]string, len(styles.Colors.IndexedColors.RGBColors)) + for index, color := range styles.Colors.IndexedColors.RGBColors { + colors[index] = color.RGB + } + return colors +} + +var indexedColorMapping = []string{ + "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", + "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", + "800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080", + "9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF", + "000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF", + "00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99", + "3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696", + "003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333", + "000000", "FFFFFF", +} diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go new file mode 100644 index 0000000..16644d5 --- /dev/null +++ b/excelizeutil/excelizeutil_test.go @@ -0,0 +1,566 @@ +package excelizeutil + +import ( + "archive/zip" + "bytes" + "errors" + "fmt" + "io" + "os" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xuri/excelize/v2" +) + +func workbookWithStyle(t *testing.T, style *excelize.Style) bytes.Buffer { + t.Helper() + + f := excelize.NewFile() + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + styleID, err := f.NewStyle(style) + require.NoError(t, err) + require.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", styleID)) + + buffer, err := f.WriteToBuffer() + require.NoError(t, err) + return *buffer +} + +func mustGetStyle(t *testing.T, buffer bytes.Buffer, sheetName string, corIdx, rowIdx int) excelize.Style { + t.Helper() + style, err := GetStyle(buffer, sheetName, corIdx, rowIdx) + require.NoError(t, err) + return style +} + +func TestGetStyleReturnsOpenReaderError(t *testing.T) { + _, err := GetStyle(bytes.Buffer{}, "Sheet1", 1, 1) + require.Error(t, err) + assert.Contains(t, err.Error(), "open workbook") +} + +func TestGetStyleReturnsTruncatedWorkbookError(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{}) + truncated := append([]byte(nil), buffer.Bytes()[:buffer.Len()-22]...) + + _, err := GetStyle(*bytes.NewBuffer(truncated), "Sheet1", 1, 1) + require.Error(t, err) + assert.True(t, errors.Is(err, zip.ErrFormat), "GetStyle should preserve the ZIP format error") + assert.Contains(t, err.Error(), "open workbook") +} + +func TestGetStyleReturnsCoordinateError(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{}) + for _, coordinates := range [][2]int{{0, 1}, {1, 0}} { + _, err := GetStyle(buffer, "Sheet1", coordinates[0], coordinates[1]) + require.Error(t, err) + assert.Contains(t, err.Error(), "convert cell coordinates") + } +} + +func TestGetStyleReturnsGetCellStyleError(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{}) + _, err := GetStyle(buffer, "Missing", 1, 1) + require.Error(t, err) + assert.Contains(t, err.Error(), "get cell style") +} + +func TestGetStyleReturnsInvalidCellStyleIDError(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Font: &excelize.Font{Bold: true}}) + buffer = rewriteXLSXEntry(t, buffer, "xl/worksheets/sheet1.xml", func(data []byte) []byte { + old := []byte(`s="1"`) + require.Contains(t, string(data), string(old)) + return bytes.Replace(data, old, []byte(`s="999"`), 1) + }) + + _, err := GetStyle(buffer, "Sheet1", 1, 1) + require.ErrorIs(t, err, errInvalidStyleReference) +} + +func TestGetStyleReturnsInvalidStyleReferenceError(t *testing.T) { + format := `yyyy-mm-dd` + style := &excelize.Style{ + Font: &excelize.Font{Bold: true}, + Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#123456"}}, + Border: []excelize.Border{{Type: "top", Style: 1}}, + CustomNumFmt: &format, + } + tests := []struct { + name string + attribute string + }{ + {name: "font ID", attribute: "fontId"}, + {name: "fill ID", attribute: "fillId"}, + {name: "border ID", attribute: "borderId"}, + {name: "custom number format ID", attribute: "numFmtId"}, + {name: "named style XF ID", attribute: "xfId"}, + } + + for _, test := range tests { + for _, invalidID := range []int{-1, 999} { + t.Run(fmt.Sprintf("%s/%d", test.name, invalidID), func(t *testing.T) { + buffer := workbookWithStyle(t, style) + buffer = rewriteCellXFAttribute(t, buffer, test.attribute, invalidID) + + _, err := GetStyle(buffer, "Sheet1", 1, 1) + require.ErrorIs(t, err, errInvalidStyleReference) + assert.Contains(t, err.Error(), "resolve cell style") + }) + } + } +} + +func TestGetStylePreservesPatternFill(t *testing.T) { + tests := []struct { + name string + pattern int + }{ + {name: "solid", pattern: 1}, + {name: "dark horizontal", pattern: 5}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{ + Fill: excelize.Fill{ + Type: "pattern", + Pattern: test.pattern, + Color: []string{"#315D3C"}, + }, + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Fill + assert.Equal(t, "pattern", got.Type) + assert.Equal(t, test.pattern, got.Pattern) + assert.Equal(t, []string{"#315D3C"}, got.Color) + }) + } +} + +func TestGetStylePreservesFullStyle(t *testing.T) { + tests := []struct { + name string + style *excelize.Style + check func(*testing.T, excelize.Style) + }{ + { + name: "built in number format and style fields", + style: &excelize.Style{ + Font: &excelize.Font{ + Bold: true, + Italic: true, + Underline: "single", + Family: "Aptos", + Size: 11, + Strike: true, + Color: "#123456", + }, + Alignment: &excelize.Alignment{ + Horizontal: "center", + Indent: 2, + JustifyLastLine: true, + ReadingOrder: 1, + RelativeIndent: 3, + ShrinkToFit: true, + TextRotation: 45, + Vertical: "center", + WrapText: true, + }, + Protection: &excelize.Protection{Hidden: true, Locked: false}, + NumFmt: 14, + }, + check: func(t *testing.T, got excelize.Style) { + require.NotNil(t, got.Font) + wantFont := &excelize.Font{ + Bold: true, Italic: true, Underline: "single", Family: "Aptos", + Size: 11, Strike: true, Color: "#123456", + } + setPublicField(wantFont, "ColorIndexed", -1) + assert.Equal(t, wantFont, got.Font) + assert.Equal(t, &excelize.Alignment{ + Horizontal: "center", Indent: 2, JustifyLastLine: true, + ReadingOrder: 1, RelativeIndent: 3, ShrinkToFit: true, + TextRotation: 45, Vertical: "center", WrapText: true, + }, got.Alignment) + assert.Equal(t, &excelize.Protection{Hidden: true, Locked: false}, got.Protection) + assert.Equal(t, 14, got.NumFmt) + }, + }, + { + name: "custom number format", + style: func() *excelize.Style { + format := `yyyy-mm-dd "date"` + return &excelize.Style{CustomNumFmt: &format} + }(), + check: func(t *testing.T, got excelize.Style) { + require.NotNil(t, got.CustomNumFmt) + assert.Equal(t, `yyyy-mm-dd "date"`, *got.CustomNumFmt) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + buffer := workbookWithStyle(t, test.style) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + test.check(t, got) + reapplied := workbookWithStyle(t, &got) + test.check(t, mustGetStyle(t, reapplied, "Sheet1", 1, 1)) + }) + } +} + +func TestGetStyleInheritsNamedStyleXF(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func([]byte) []byte { + data := []byte(namedStyleStylesXML) + oldChild := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldChild)) + return bytes.Replace(data, oldChild, []byte(``), 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + require.Equal(t, 14, got.NumFmt) + wantFont := &excelize.Font{ + Bold: true, Italic: true, Underline: "single", Family: "Aptos", + Size: 14, Color: "#123456", + } + setPublicField(wantFont, "ColorIndexed", -1) + require.NotNil(t, got.Font) + assert.Equal(t, wantFont, got.Font) + assert.Equal(t, excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#315D3C"}}, got.Fill) + assert.ElementsMatch(t, []excelize.Border{ + {Type: "left", Style: 1, Color: "#111111"}, + {Type: "right", Style: 2, Color: "#222222"}, + }, got.Border) + assert.Equal(t, &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true}, got.Alignment) + assert.Equal(t, &excelize.Protection{Locked: false, Hidden: true}, got.Protection) + + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + assert.Equal(t, got.Alignment, reread.Alignment) + assert.Equal(t, got.Protection, reread.Protection) + assert.Equal(t, styleAlignmentXML(t, reapplied), alignmentStyleXML{ + Horizontal: "center", Vertical: "center", WrapText: "true", + }) + protection := styleProtectionXML(t, reapplied) + assertXMLBool(t, protection.Locked, false) + assertXMLBool(t, protection.Hidden, true) +} + +func TestGetStyleInheritsCustomizedFontZeroWhenChildDoesNotApplyFont(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func([]byte) []byte { + data := []byte(namedStyleStylesXML) + oldFont := []byte(``) + newFont := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldFont)) + data = bytes.Replace(data, oldFont, newFont, 1) + oldParent := []byte(`numFmtId="14" fontId="1"`) + require.Equal(t, 1, bytes.Count(data, oldParent)) + data = bytes.Replace(data, oldParent, []byte(`numFmtId="14" fontId="0"`), 1) + oldChild := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldChild)) + return bytes.Replace(data, oldChild, []byte(``), 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + require.NotNil(t, got.Font, "fontId=0 on the parent XF remains effective when the child explicitly inherits it") + assert.True(t, got.Font.Bold) + assert.Equal(t, float64(15), got.Font.Size) + assert.Equal(t, "InheritedZero", got.Font.Family) + + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + require.NotNil(t, reread.Font) + assert.True(t, reread.Font.Bold) + assert.Equal(t, float64(15), reread.Font.Size) + assert.Equal(t, "InheritedZero", reread.Font.Family) + assert.Contains(t, xlsxEntry(t, reapplied, "xl/styles.xml"), `name val="InheritedZero"`) +} + +func TestGetStyleAppliesImplicitFontZeroWhenApplyFontIsTrue(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func([]byte) []byte { + data := []byte(namedStyleStylesXML) + oldFont := []byte(``) + newFont := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldFont)) + data = bytes.Replace(data, oldFont, newFont, 1) + oldChild := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldChild)) + return bytes.Replace(data, oldChild, []byte(``), 1) + }) + assert.Contains(t, xlsxEntry(t, buffer, "xl/styles.xml"), ``) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + require.NotNil(t, got.Font, "applyFont=true with omitted fontId applies schema-default fontId=0") + assert.True(t, got.Font.Bold) + assert.Equal(t, float64(15), got.Font.Size) + assert.Equal(t, "ImplicitZero", got.Font.Family) + + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + require.NotNil(t, reread.Font) + assert.True(t, reread.Font.Bold) + assert.Equal(t, float64(15), reread.Font.Size) + assert.Equal(t, "ImplicitZero", reread.Font.Family) + assert.Contains(t, xlsxEntry(t, reapplied, "xl/styles.xml"), `name val="ImplicitZero"`) +} + +func TestGetStylePreservesLocalizedBuiltInNumberFormatID(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + old := []byte(``) + return bytes.Replace(data, []byte(""), append(colors, []byte("")...), 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Fill.Color + assert.Equal(t, []string{"#BEAD10"}, got) +} + +func TestGetStyleResolvesDefaultIndexedOOXMLColor(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{ + Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#123456"}}, + }) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + old := []byte(`rgb="FF123456"`) + require.Equal(t, 1, bytes.Count(data, old)) + return bytes.Replace(data, old, []byte(`indexed="5"`), 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Fill.Color + assert.Equal(t, []string{"#FFFF00"}, got) +} + +func TestGetStyleDefaultsOmittedUnderlineValueToSingle(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Font: &excelize.Font{Underline: "single"}}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + old := []byte(`val="single"`) + require.Equal(t, 1, bytes.Count(data, old)) + return bytes.Replace(data, old, nil, 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Font + require.NotNil(t, got) + assert.Equal(t, "single", got.Underline) +} + +func TestGetStylePreservesAllBorderSides(t *testing.T) { + want := []excelize.Border{ + {Type: "top", Style: 1, Color: "#111111"}, + {Type: "bottom", Style: 2, Color: "#222222"}, + {Type: "left", Style: 3, Color: "#333333"}, + {Type: "right", Style: 4, Color: "#444444"}, + {Type: "diagonalUp", Style: 6, Color: "#666666"}, + {Type: "diagonalDown", Style: 6, Color: "#666666"}, + } + buffer := workbookWithStyle(t, &excelize.Style{Border: want}) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Border + assert.ElementsMatch(t, want, got) + reapplied := workbookWithStyle(t, &excelize.Style{Border: got}) + assert.ElementsMatch(t, want, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Border) +} + +func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { + tests := []struct { + themeIndex int + want string + }{ + {themeIndex: 0, want: "#AABBCC"}, + {themeIndex: 1, want: "#112233"}, + {themeIndex: 2, want: "#DDEEFF"}, + {themeIndex: 3, want: "#445566"}, + {themeIndex: 4, want: "#5B9BD5"}, + {themeIndex: 5, want: "#ED7D31"}, + {themeIndex: 6, want: "#A5A5A5"}, + {themeIndex: 7, want: "#FFC000"}, + {themeIndex: 8, want: "#4472C4"}, + {themeIndex: 9, want: "#70AD47"}, + {themeIndex: 10, want: "#0563C1"}, + {themeIndex: 11, want: "#954F72"}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("theme-%d", test.themeIndex), func(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{ + Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#123456"}}, + }) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + old := []byte(`rgb="FF123456"`) + newColor := []byte(fmt.Sprintf(`theme="%d"`, test.themeIndex)) + require.Equal(t, 1, bytes.Count(data, old)) + return bytes.Replace(data, old, newColor, 1) + }) + buffer = rewriteXLSXEntry(t, buffer, "xl/theme/theme1.xml", func(data []byte) []byte { + replacements := [][2]string{ + {"dk1", ``}, + {"lt1", ``}, + {"dk2", ``}, + {"lt2", ``}, + } + for _, replacement := range replacements { + pattern := regexp.MustCompile(`(?s).*?`) + require.Equal(t, 1, len(pattern.FindAll(data, -1))) + data = pattern.ReplaceAll(data, []byte(``+replacement[1]+``)) + } + return data + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + require.Len(t, got.Fill.Color, 1) + assert.Equal(t, test.want, got.Fill.Color[0]) + }) + } +} + +func TestGetStyleClosesLargeWorkbookTemporaryFiles(t *testing.T) { + tempDir, err := os.MkdirTemp("", "excelizeutil-temp-") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.RemoveAll(tempDir)) }) + t.Setenv("TMPDIR", tempDir) + + f := excelize.NewFile() + sheet := f.GetSheetName(0) + longText := strings.Repeat("x", 30_000) + for row := 1; row <= 570; row++ { + value := fmt.Sprintf("%04d%s", row, longText) + axis := fmt.Sprintf("A%d", row) + require.NoError(t, f.SetCellValue(sheet, axis, value)) + } + buffer, err := f.WriteToBuffer() + require.NoError(t, err) + require.NoError(t, f.Close()) + + _ = mustGetStyle(t, *buffer, sheet, 1, 1) + + entries, err := os.ReadDir(tempDir) + require.NoError(t, err) + assert.Empty(t, entries, "GetStyle should remove Excelize temporary files") +} + +func rewriteXLSXEntry(t *testing.T, buffer bytes.Buffer, name string, rewrite func([]byte) []byte) bytes.Buffer { + t.Helper() + + reader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(buffer.Len())) + require.NoError(t, err) + + var output bytes.Buffer + writer := zip.NewWriter(&output) + found := false + for _, file := range reader.File { + contents, err := file.Open() + require.NoError(t, err) + data, err := io.ReadAll(contents) + require.NoError(t, err) + require.NoError(t, contents.Close()) + if file.Name == name { + data = rewrite(data) + found = true + } + + entry, err := writer.Create(file.Name) + require.NoError(t, err) + _, err = entry.Write(data) + require.NoError(t, err) + } + require.True(t, found, "XLSX archive did not contain %q", name) + require.NoError(t, writer.Close()) + return output +} + +func rewriteCellXFAttribute(t *testing.T, buffer bytes.Buffer, attribute string, value int) bytes.Buffer { + t.Helper() + return rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + cellXFsStart := bytes.Index(data, []byte("")) + require.NotEqual(t, -1, cellXFsEnd, "cellXfs element must be closed") + cellXFsEnd += cellXFsStart + cellXFs := data[cellXFsStart:cellXFsEnd] + + firstXFStart := bytes.Index(cellXFs, []byte("")) + require.NotEqual(t, -1, firstXFEnd, "default cell XF start tag must be closed") + secondXFStart := bytes.Index(cellXFs[firstXFStart+firstXFEnd+1:], []byte("")) + require.NotEqual(t, -1, secondXFEnd, "styled cell XF start tag must be closed") + secondXFEnd += secondXFStart + + startTag := cellXFs[secondXFStart : secondXFEnd+1] + attributePattern := regexp.MustCompile(`(^|\s)` + regexp.QuoteMeta(attribute) + `="[^"]*"`) + location := attributePattern.FindIndex(startTag) + require.NotNil(t, location, "%s attribute must exist in the styled cell XF", attribute) + absoluteStart := cellXFsStart + secondXFStart + location[0] + absoluteEnd := cellXFsStart + secondXFStart + location[1] + replacement := []byte(fmt.Sprintf(` %s="%d"`, attribute, value)) + result := make([]byte, 0, len(data)+len(replacement)-(absoluteEnd-absoluteStart)) + result = append(result, data[:absoluteStart]...) + result = append(result, replacement...) + result = append(result, data[absoluteEnd:]...) + return result + }) +} + +const namedStyleStylesXML = ` + + + + + + +` diff --git a/excelizeutil/style_enums_gen.go b/excelizeutil/style_enums_gen.go new file mode 100644 index 0000000..b2ca492 --- /dev/null +++ b/excelizeutil/style_enums_gen.go @@ -0,0 +1,39 @@ +// Code generated by go generate; DO NOT EDIT. + +package excelizeutil + +import "github.com/tomtwinkle/excelizeam/excelizestyle" + +type fillPattern = excelizestyle.FillPattern + +const fillPatternNone = excelizestyle.FillPatternNone + +func parseFillPattern(value string) (fillPattern, bool) { return excelizestyle.ParseFillPattern(value) } + +type borderStyle = excelizestyle.BorderStyle + +const borderStyleNone = excelizestyle.BorderStyleNone + +func parseBorderStyle(value string) (borderStyle, bool) { return excelizestyle.ParseBorderStyle(value) } + +func gradientGeometries() []gradientGeometry { + return []gradientGeometry{ + {typeName: "", degree: 90, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 270, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 90, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 0.5, 1}}, + {typeName: "", degree: 0, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 180, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 0, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 0.5, 1}}, + {typeName: "", degree: 45, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 255, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 45, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 0.5, 1}}, + {typeName: "", degree: 135, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 315, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "", degree: 135, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 0.5, 1}}, + {typeName: "path", degree: 0, left: 0, right: 0, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "path", degree: 0, left: 1, right: 1, top: 0, bottom: 0, positions: []float64{0, 1}}, + {typeName: "path", degree: 0, left: 0, right: 0, top: 1, bottom: 1, positions: []float64{0, 1}}, + {typeName: "path", degree: 0, left: 1, right: 1, top: 1, bottom: 1, positions: []float64{0, 1}}, + {typeName: "path", degree: 0, left: 0.5, right: 0.5, top: 0.5, bottom: 0.5, positions: []float64{0, 1}}, + } +} diff --git a/excelizeutil/style_roundtrip_test.go b/excelizeutil/style_roundtrip_test.go new file mode 100644 index 0000000..c6b8771 --- /dev/null +++ b/excelizeutil/style_roundtrip_test.go @@ -0,0 +1,671 @@ +package excelizeutil + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "io" + "reflect" + "regexp" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tomtwinkle/excelizeam/excelizestyle" + "github.com/xuri/excelize/v2" +) + +func TestExcelizeV2101StyleFieldInventory(t *testing.T) { + styleFields := publicFieldNames(reflect.TypeOf(excelize.Style{})) + fontFields := publicFieldNames(reflect.TypeOf(excelize.Font{})) + fillFields := publicFieldNames(reflect.TypeOf(excelize.Fill{})) + if !containsString(styleFields, "DecimalPlaces") || !containsString(fontFields, "ColorTheme") { + t.Skip("the linked Excelize version predates the v2.10.1 Style API") + } + + assert.Equal(t, []string{"Alignment", "Border", "CustomNumFmt", "DecimalPlaces", "Fill", "Font", "NegRed", "NumFmt", "Protection"}, styleFields) + assert.Equal(t, []string{"Bold", "Charset", "Color", "ColorIndexed", "ColorTheme", "ColorTint", "Family", "Italic", "Size", "Strike", "Underline", "VertAlign"}, fontFields) + assert.Equal(t, []string{"Color", "Pattern", "Shading", "Transparency", "Type"}, fillFields) + assert.Equal(t, []string{"Color", "Style", "Type"}, publicFieldNames(reflect.TypeOf(excelize.Border{}))) + assert.Equal(t, []string{"Hidden", "Locked"}, publicFieldNames(reflect.TypeOf(excelize.Protection{}))) + assert.Equal(t, []string{"Horizontal", "Indent", "JustifyLastLine", "ReadingOrder", "RelativeIndent", "ShrinkToFit", "TextRotation", "Vertical", "WrapText"}, publicFieldNames(reflect.TypeOf(excelize.Alignment{}))) +} + +func TestGetStyleRoundTripsAllGradientPresets(t *testing.T) { + for shading := 0; shading < int(excelizestyle.GradientShadingCount); shading++ { + t.Run(fmt.Sprintf("shading-%d", shading), func(t *testing.T) { + style := &excelize.Style{Fill: excelize.Fill{Type: "gradient", Shading: shading, Color: []string{"#123456", "#ABCDEF"}}} + original := workbookWithStyle(t, style) + got := mustGetStyle(t, original, "Sheet1", 1, 1) + require.Equal(t, "gradient", got.Fill.Type) + require.Equal(t, shading, got.Fill.Shading) + require.Equal(t, []string{"#123456", "#ABCDEF"}, got.Fill.Color) + + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + assert.Equal(t, got.Fill, reread.Fill) + assert.Equal(t, styleGradientXML(t, original), styleGradientXML(t, reapplied), "the saved OOXML gradient geometry and stops must survive reapplication") + }) + } +} + +func TestGetStyleRoundTripsEveryPublicFontField(t *testing.T) { + if !hasPublicField(reflect.TypeOf(excelize.Font{}), "ColorTheme") { + t.Skip("the linked Excelize version predates the v2.10.1 Font API") + } + tests := []struct { + name string + field string + value any + want any + xmlMarker string + }{ + {name: "Bold", field: "Bold", value: true, want: true, xmlMarker: `")) + require.NotEqual(t, -1, fontStart) + fontEnd := bytes.Index(data[fontStart:], []byte("")) + require.NotEqual(t, -1, fontEnd) + fontEnd += fontStart + len("") + font := append([]byte(nil), data[fontStart:fontEnd]...) + font = bytes.Replace(font, []byte(""), []byte(""), 1) + data = append(append(append([]byte(nil), data[:fontStart]...), font...), data[fontEnd:]...) + oldXF := []byte(`fontId="1"`) + require.Equal(t, 1, bytes.Count(data, oldXF)) + return bytes.Replace(data, oldXF, []byte(`fontId="0" applyFont="1"`), 1) + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + require.NotNil(t, got.Font, "an explicitly applied fontId=0 is a real style, not an absent font") + assert.True(t, got.Font.Bold, "an empty means true") + assert.True(t, got.Font.Italic, "an empty means true") + assert.True(t, got.Font.Strike, "an empty means true") + assert.Contains(t, xlsxEntry(t, buffer, "xl/styles.xml"), `applyFont="1"`) +} + +func TestGetStyleHandlesSparseBorderXML(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Border: []excelize.Border{{Type: "top", Style: 1, Color: "#112233"}}}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + data = bytes.Replace(data, []byte(``), nil, 1) + data = bytes.Replace(data, []byte(``), nil, 1) + return data + }) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + assert.Equal(t, []excelize.Border{{Type: "top", Style: 1, Color: "#112233"}}, got.Border) + assert.Equal(t, got.Border, mustGetStyle(t, workbookWithStyle(t, &got), "Sheet1", 1, 1).Border) +} + +func TestGetStyleUsesForegroundColorWhenPatternHasBothColors(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Fill: excelize.Fill{Type: "pattern", Pattern: 10, Color: []string{"#123456"}}}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + patternPosition := bytes.Index(data, []byte(`patternType="darkTrellis"`)) + require.NotEqual(t, -1, patternPosition) + fillClose := bytes.Index(data[patternPosition:], []byte(``)) + require.NotEqual(t, -1, fillClose) + fillClose += patternPosition + data = append(append(append([]byte(nil), data[:fillClose]...), []byte(``)...), data[fillClose:]...) + return data + }) + + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) + assert.Equal(t, []string{"#123456"}, got.Fill.Color) + reapplied := workbookWithStyle(t, &got) + assert.Equal(t, []string{"#123456"}, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Fill.Color) + assert.NotContains(t, xlsxEntry(t, reapplied, "xl/styles.xml"), `FFABCDEF`, "Style.Fill has only one color and cannot encode both pattern colors") +} + +func TestGetStyleRoundTripsEveryPatternIDAndBorderStyle(t *testing.T) { + for pattern := 0; pattern < int(excelizestyle.FillPatternCount); pattern++ { + t.Run(fmt.Sprintf("pattern-%d", pattern), func(t *testing.T) { + style := &excelize.Style{Fill: excelize.Fill{Type: "pattern", Pattern: pattern, Color: []string{"#315D3C"}}} + got := mustGetStyle(t, workbookWithStyle(t, style), "Sheet1", 1, 1) + require.Equal(t, "pattern", got.Fill.Type) + assert.Equal(t, pattern, got.Fill.Pattern) + assert.Equal(t, []string{"#315D3C"}, got.Fill.Color) + reapplied := workbookWithStyle(t, &got) + assert.Equal(t, got.Fill, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Fill) + }) + } + for borderStyle := 0; borderStyle < int(excelizestyle.BorderStyleCount); borderStyle++ { + t.Run(fmt.Sprintf("border-style-%d", borderStyle), func(t *testing.T) { + style := &excelize.Style{Border: []excelize.Border{{Type: "top", Style: borderStyle, Color: "#112233"}}} + got := mustGetStyle(t, workbookWithStyle(t, style), "Sheet1", 1, 1) + require.Equal(t, style.Border, got.Border) + reapplied := workbookWithStyle(t, &got) + assert.Equal(t, got.Border, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Border) + }) + } +} + +func TestGetStylePreservesNumberFormatIntentWithoutGuessingFormatTokens(t *testing.T) { + if !hasPublicField(reflect.TypeOf(excelize.Style{}), "DecimalPlaces") { + t.Skip("the linked Excelize version predates the v2.10.1 Style API") + } + + // A custom code contains the decimal count and negative-red section already. + // Keep its exact spelling; do not derive fields by scanning punctuation that + // could be quoted or escaped. + code := `"$"#,##0.000;[Red]("$"#,##0.000)` + style := &excelize.Style{NumFmt: 164, CustomNumFmt: &code, NegRed: true} + require.True(t, setPublicField(style, "DecimalPlaces", 3)) + original := workbookWithStyle(t, style) + got := mustGetStyle(t, original, "Sheet1", 1, 1) + require.NotNil(t, got.CustomNumFmt) + assert.Equal(t, code, *got.CustomNumFmt) + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + require.NotNil(t, reread.CustomNumFmt) + assert.Equal(t, code, *reread.CustomNumFmt) + assert.Contains(t, customNumberFormats(t, reapplied), code) +} + +func TestGetStyleMapsBuiltInDecimalPlacesField(t *testing.T) { + field, ok := reflect.TypeOf(excelize.Style{}).FieldByName("DecimalPlaces") + if !ok || field.Type.Kind() != reflect.Ptr { + t.Skip("the linked Excelize version predates the v2.10.1 DecimalPlaces API") + } + style := &excelize.Style{NumFmt: 44} + original := workbookWithStyle(t, style) + got := mustGetStyle(t, original, "Sheet1", 1, 1) + assert.Equal(t, 44, got.NumFmt) + assert.Equal(t, 2, publicFieldValue(&got, "DecimalPlaces")) + assert.Nil(t, got.CustomNumFmt) + + reapplied := workbookWithStyle(t, &got) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) + assert.Equal(t, 44, reread.NumFmt) + assert.Equal(t, 2, publicFieldValue(&reread, "DecimalPlaces")) +} + +type gradientFillXML struct { + Type string `xml:"type,attr"` + Degree float64 `xml:"degree,attr"` + Left float64 `xml:"left,attr"` + Right float64 `xml:"right,attr"` + Top float64 `xml:"top,attr"` + Bottom float64 `xml:"bottom,attr"` + Stops []gradientStopXML `xml:"stop"` +} + +type fontColorXML struct { + RGB string `xml:"rgb,attr"` + Auto bool `xml:"auto,attr"` + Indexed *int `xml:"indexed,attr"` + Theme *int `xml:"theme,attr"` + Tint float64 `xml:"tint,attr"` +} + +type alignmentStyleXML struct { + Horizontal string `xml:"horizontal,attr"` + Indent string `xml:"indent,attr"` + JustifyLastLine string `xml:"justifyLastLine,attr"` + ReadingOrder string `xml:"readingOrder,attr"` + RelativeIndent string `xml:"relativeIndent,attr"` + ShrinkToFit string `xml:"shrinkToFit,attr"` + TextRotation string `xml:"textRotation,attr"` + Vertical string `xml:"vertical,attr"` + WrapText string `xml:"wrapText,attr"` +} + +type protectionStyleXML struct { + Locked *string `xml:"locked,attr"` + Hidden *string `xml:"hidden,attr"` +} + +type cellXFStyleXML struct { + Alignment *alignmentStyleXML `xml:"alignment"` + Protection *protectionStyleXML `xml:"protection"` +} + +func cellXFStylesXML(t *testing.T, buffer bytes.Buffer) []cellXFStyleXML { + t.Helper() + var styles struct { + CellXFs struct { + XFs []cellXFStyleXML `xml:"xf"` + } `xml:"cellXfs"` + } + require.NoError(t, xml.Unmarshal([]byte(xlsxEntry(t, buffer, "xl/styles.xml")), &styles)) + return styles.CellXFs.XFs +} + +func styleAlignmentXML(t *testing.T, buffer bytes.Buffer) alignmentStyleXML { + t.Helper() + var alignments []alignmentStyleXML + for _, xf := range cellXFStylesXML(t, buffer) { + if xf.Alignment != nil { + alignments = append(alignments, *xf.Alignment) + } + } + require.Len(t, alignments, 1) + return alignments[0] +} + +func styleProtectionXML(t *testing.T, buffer bytes.Buffer) protectionStyleXML { + t.Helper() + var protections []protectionStyleXML + for _, xf := range cellXFStylesXML(t, buffer) { + if xf.Protection != nil { + protections = append(protections, *xf.Protection) + } + } + require.Len(t, protections, 1) + return protections[0] +} + +func assertXMLBool(t *testing.T, value *string, want bool) { + t.Helper() + require.NotNil(t, value) + var got bool + switch strings.ToLower(*value) { + case "1", "true": + got = true + case "0", "false": + got = false + default: + t.Fatalf("unsupported XML boolean value %q", *value) + } + assert.Equal(t, want, got) +} + +func stripProtectionAttributes(t *testing.T, data []byte) []byte { + t.Helper() + openingTag := regexp.MustCompile(`]*>`).Find(data) + require.NotEmpty(t, openingTag, "fixture must contain a protection element") + require.NotContains(t, string(openingTag), `/>`, "fixture must retain an explicit empty protection element") + stripped := regexp.MustCompile(`\s+(?:locked|hidden)="[^"]*"`).ReplaceAll(openingTag, nil) + require.NotEqual(t, string(openingTag), string(stripped), "fixture must contain explicit protection attributes") + return bytes.Replace(data, openingTag, []byte(``), 1) +} + +func assertProtectionAttrsOmitted(t *testing.T, protection protectionStyleXML) { + t.Helper() + assert.Nil(t, protection.Locked) + assert.Nil(t, protection.Hidden) +} + +func styleFontColors(t *testing.T, buffer bytes.Buffer) []fontColorXML { + t.Helper() + var styles struct { + Fonts struct { + Font []struct { + Color *fontColorXML `xml:"color"` + } `xml:"font"` + } `xml:"fonts"` + } + require.NoError(t, xml.Unmarshal([]byte(xlsxEntry(t, buffer, "xl/styles.xml")), &styles)) + colors := make([]fontColorXML, 0, len(styles.Fonts.Font)) + for _, font := range styles.Fonts.Font { + if font.Color != nil { + colors = append(colors, *font.Color) + } + } + return colors +} + +func resolvedFontColor(t *testing.T, buffer bytes.Buffer, fontIndex int) []string { + t.Helper() + colors := styleFontColors(t, buffer) + require.Greater(t, fontIndex, -1) + require.Less(t, fontIndex, len(colors)) + color := colors[fontIndex] + themeColors, _, indexedColors := readStyleMetadata(buffer) + selector := &xlsxColor{Auto: color.Auto, RGB: color.RGB, Tint: color.Tint} + if color.Indexed != nil { + selector.Indexed = *color.Indexed + } + if color.Theme != nil { + selector.Theme = color.Theme + } + return getCellFillColor(selector, themeColors, indexedColors) +} + +type gradientStopXML struct { + Position float64 `xml:"position,attr"` + Color struct { + RGB string `xml:"rgb,attr"` + Indexed int `xml:"indexed,attr"` + Theme *int `xml:"theme,attr"` + Tint float64 `xml:"tint,attr"` + } `xml:"color"` +} + +func styleGradientXML(t *testing.T, buffer bytes.Buffer) []gradientFillXML { + t.Helper() + data := []byte(xlsxEntry(t, buffer, "xl/styles.xml")) + var styles struct { + Fills struct { + Fill []struct { + Gradient *gradientFillXML `xml:"gradientFill"` + } `xml:"fill"` + } `xml:"fills"` + } + require.NoError(t, xml.Unmarshal(data, &styles)) + var result []gradientFillXML + for _, fill := range styles.Fills.Fill { + if fill.Gradient != nil { + result = append(result, *fill.Gradient) + } + } + require.Len(t, result, 1) + return result +} + +func xlsxEntry(t *testing.T, buffer bytes.Buffer, name string) string { + t.Helper() + reader, err := zip.NewReader(bytes.NewReader(buffer.Bytes()), int64(buffer.Len())) + require.NoError(t, err) + for _, file := range reader.File { + if file.Name != name { + continue + } + entry, err := file.Open() + require.NoError(t, err) + data, readErr := io.ReadAll(entry) + require.NoError(t, readErr) + require.NoError(t, entry.Close()) + return string(data) + } + t.Fatalf("XLSX archive did not contain %q", name) + return "" +} + +func customNumberFormats(t *testing.T, buffer bytes.Buffer) []string { + t.Helper() + var styles struct { + NumFmts struct { + Formats []struct { + Code string `xml:"formatCode,attr"` + } `xml:"numFmt"` + } `xml:"numFmts"` + } + require.NoError(t, xml.Unmarshal([]byte(xlsxEntry(t, buffer, "xl/styles.xml")), &styles)) + result := make([]string, 0, len(styles.NumFmts.Formats)) + for _, format := range styles.NumFmts.Formats { + result = append(result, format.Code) + } + return result +} + +func publicFieldNames(typ reflect.Type) []string { + result := make([]string, 0, typ.NumField()) + for i := 0; i < typ.NumField(); i++ { + if typ.Field(i).IsExported() { + result = append(result, typ.Field(i).Name) + } + } + sort.Strings(result) + return result +} + +func hasPublicField(typ reflect.Type, name string) bool { + _, ok := typ.FieldByName(name) + return ok +} + +func containsString(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} + +func setPublicField(target any, name string, value any) bool { + field := reflect.ValueOf(target).Elem().FieldByName(name) + if !field.IsValid() || !field.CanSet() { + return false + } + valueRef := reflect.ValueOf(value) + if field.Kind() == reflect.Pointer && valueRef.Type().AssignableTo(field.Type().Elem()) { + pointer := reflect.New(field.Type().Elem()) + pointer.Elem().Set(valueRef) + field.Set(pointer) + return true + } + if valueRef.Type().AssignableTo(field.Type()) { + field.Set(valueRef) + return true + } + if valueRef.Type().ConvertibleTo(field.Type()) { + field.Set(valueRef.Convert(field.Type())) + return true + } + return false +} + +func publicFieldValue(target any, name string) any { + field := reflect.ValueOf(target).Elem().FieldByName(name) + if !field.IsValid() { + return nil + } + if field.Kind() == reflect.Pointer { + if field.IsNil() { + return nil + } + field = field.Elem() + } + return field.Interface() +} + +func TestGetStyleDefinitionUsesTypedFields(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#123456"}}, Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "bottom"}}) + definition, err := GetStyleDefinition(buffer, "Sheet1", 1, 1) + require.NoError(t, err) + assert.Equal(t, excelizestyle.FillPatternSolid, definition.Fill.Pattern) + require.NotNil(t, definition.Alignment) + assert.Equal(t, excelizestyle.AlignmentVerticalBottom, definition.Alignment.Vertical) + expected, err := GetStyle(buffer, "Sheet1", 1, 1) + require.NoError(t, err) + assert.Equal(t, expected, definition.ToExcelize()) + _, err = GetStyleDefinition(bytes.Buffer{}, "Sheet1", 1, 1) + require.Error(t, err) +} diff --git a/go.mod b/go.mod index 5bec8e4..e1c1ae8 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,17 @@ module github.com/tomtwinkle/excelizeam go 1.25.0 require ( + github.com/stretchr/testify v1.11.1 github.com/xuri/excelize/v2 v2.10.1 golang.org/x/sync v0.20.0 gotest.tools v2.2.0+incompatible ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.6 // indirect github.com/richardlehane/msoleps v1.0.6 // indirect github.com/tiendc/go-deepcopy v1.7.2 // indirect @@ -19,4 +22,5 @@ require ( golang.org/x/crypto v0.48.0 // indirect golang.org/x/net v0.51.0 // indirect golang.org/x/text v0.34.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7b0aacd..de48c34 100644 --- a/go.sum +++ b/go.sum @@ -6,49 +6,32 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= -github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8= github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= -github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00= -github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo= -github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= -github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw= -github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s= github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0= github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc= -github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q= -github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=