From bd3cd0484029043d164ddbd75a80e81f9b78603f Mon Sep 17 00:00:00 2001 From: tom twinkle Date: Fri, 19 Aug 2022 13:49:15 +0900 Subject: [PATCH 01/10] feat: get excelize style util draft --- excelizeutil/excelizeutil.go | 156 +++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 excelizeutil/excelizeutil.go diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go new file mode 100644 index 0000000..e3a9f2e --- /dev/null +++ b/excelizeutil/excelizeutil.go @@ -0,0 +1,156 @@ +package excelizeutil + +import ( + "bytes" + "strings" + + "github.com/xuri/excelize/v2" +) + +func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) excelize.Style { + f, err := excelize.OpenReader(&excelBuffer) + if err != nil { + panic(err) + } + cell, err := excelize.CoordinatesToCellName(corIdx, rowIdx) + if err != nil { + panic(err) + } + styleID, err := f.GetCellStyle(sheetName, cell) + if err != nil { + panic(err) + } + return excelize.Style{ + Border: getCellBorder(f, styleID), + Fill: getCellFill(f, styleID), + Font: nil, + Alignment: getCellAlignment(f, styleID), + Protection: nil, + NumFmt: 0, + DecimalPlaces: 0, + CustomNumFmt: nil, + Lang: "", + NegRed: false, + } +} + +type xlsxLine struct { + Style string `xml:"style,attr,omitempty"` + Color *xlsxColor `xml:"color,omitempty"` +} + +func getCellBorder(f *excelize.File, styleID int) []excelize.Border { + borderID := f.Styles.CellXfs.Xf[styleID].BorderID + if borderID == nil { + return nil + } + + getStyleID := func(style string) int { + var styles = []string{ + "none", + "thin", + "medium", + "dashed", + "dotted", + "thick", + "double", + "hair", + "mediumDashed", + "dashDot", + "mediumDashDot", + "dashDotDot", + "mediumDashDotDot", + "slantDashDot", + } + for i, v := range styles { + if v == style { + return i + } + } + return 0 + } + getBorder := func(style string, color *xlsxColor) excelize.Border { + b := excelize.Border{ + Type: "top", + Style: getStyleID(style), + } + if color != nil { + b.Color = getCellFillColor(f, color)[0] + } + return b + } + + borders := make([]excelize.Border, 0, 4) + + if topStyle := f.Styles.Borders.Border[*borderID].Top; topStyle.Style != "" { + borders = append(borders, getBorder(topStyle.Style, (*xlsxColor)(topStyle.Color))) + } + + return borders +} + +func getCellFill(f *excelize.File, styleID int) excelize.Fill { + fillID := f.Styles.CellXfs.Xf[styleID].FillID + if fillID == nil { + return excelize.Fill{} + } + patternFill := *f.Styles.Fills.Fill[*fillID].PatternFill + + fgColor := f.Styles.Fills.Fill[*fillID].PatternFill.FgColor + + return excelize.Fill{ + Type: patternFill.PatternType, + Pattern: 0, + Color: getCellFillColor(f, (*xlsxColor)(fgColor)), + } +} + +func getCellAlignment(f *excelize.File, styleID int) *excelize.Alignment { + alignment := f.Styles.CellXfs.Xf[styleID].Alignment + if alignment == nil { + return nil + } + return &excelize.Alignment{ + Horizontal: "", + Indent: 0, + JustifyLastLine: false, + ReadingOrder: 0, + RelativeIndent: 0, + ShrinkToFit: false, + TextRotation: 0, + Vertical: "", + WrapText: alignment.WrapText, + } +} + +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(f *excelize.File, color *xlsxColor) []string { + if color != nil { + if color.Theme != nil { + children := f.Theme.ThemeElements.ClrScheme.Children + if *color.Theme < 4 { + dklt := map[int]string{ + 0: children[1].SysClr.LastClr, + 1: children[0].SysClr.LastClr, + 2: *children[3].SrgbClr.Val, + 3: *children[2].SrgbClr.Val, + } + return []string{ + strings.TrimPrefix( + excelize.ThemeColor(dklt[*color.Theme], color.Tint), "FF"), + } + } + srgbClr := *children[*color.Theme].SrgbClr.Val + return []string{strings.TrimPrefix(excelize.ThemeColor(srgbClr, color.Tint), "FF")} + } + return []string{strings.TrimPrefix(color.RGB, "FF")} + } + return nil +} From f80a567904dff17ab156d00b8807d9c071aa3aa6 Mon Sep 17 00:00:00 2001 From: tom twinkle Date: Thu, 8 Oct 2026 17:11:16 +0900 Subject: [PATCH 02/10] fix(excelizeutil): preserve cell styles --- excelizeutil/excelizeutil.go | 286 +++++++++++++++++++++--------- excelizeutil/excelizeutil_test.go | 242 +++++++++++++++++++++++++ 2 files changed, 445 insertions(+), 83 deletions(-) create mode 100644 excelizeutil/excelizeutil_test.go diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index e3a9f2e..e1d9b50 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -7,11 +7,52 @@ import ( "github.com/xuri/excelize/v2" ) +var fillPatterns = []string{ + "none", + "solid", + "mediumGray", + "darkGray", + "lightGray", + "darkHorizontal", + "darkVertical", + "darkDown", + "darkUp", + "darkGrid", + "darkTrellis", + "lightHorizontal", + "lightVertical", + "lightDown", + "lightUp", + "lightGrid", + "lightTrellis", + "gray125", + "gray0625", +} + +var borderStyles = []string{ + "none", + "thin", + "medium", + "dashed", + "dotted", + "thick", + "double", + "hair", + "mediumDashed", + "dashDot", + "mediumDashDot", + "dashDotDot", + "mediumDashDotDot", + "slantDashDot", +} + func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) excelize.Style { f, err := excelize.OpenReader(&excelBuffer) if err != nil { panic(err) } + defer func() { _ = f.Close() }() + cell, err := excelize.CoordinatesToCellName(corIdx, rowIdx) if err != nil { panic(err) @@ -20,89 +61,114 @@ func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) ex if err != nil { panic(err) } + + numFmt, customNumFmt := getCellNumberFormat(f, styleID) return excelize.Style{ - Border: getCellBorder(f, styleID), - Fill: getCellFill(f, styleID), - Font: nil, - Alignment: getCellAlignment(f, styleID), - Protection: nil, - NumFmt: 0, - DecimalPlaces: 0, - CustomNumFmt: nil, - Lang: "", - NegRed: false, + Border: getCellBorder(f, styleID), + Fill: getCellFill(f, styleID), + Font: getCellFont(f, styleID), + Alignment: getCellAlignment(f, styleID), + Protection: getCellProtection(f, styleID), + NumFmt: numFmt, + CustomNumFmt: customNumFmt, } } -type xlsxLine struct { - Style string `xml:"style,attr,omitempty"` - Color *xlsxColor `xml:"color,omitempty"` -} - func getCellBorder(f *excelize.File, styleID int) []excelize.Border { borderID := f.Styles.CellXfs.Xf[styleID].BorderID - if borderID == nil { + if borderID == nil || *borderID < 0 || *borderID >= len(f.Styles.Borders.Border) { return nil } - getStyleID := func(style string) int { - var styles = []string{ - "none", - "thin", - "medium", - "dashed", - "dotted", - "thick", - "double", - "hair", - "mediumDashed", - "dashDot", - "mediumDashDot", - "dashDotDot", - "mediumDashDotDot", - "slantDashDot", - } - for i, v := range styles { - if v == style { - return i - } + definition := f.Styles.Borders.Border[*borderID] + borders := make([]excelize.Border, 0, 6) + appendBorder := func(side, style string, color *xlsxColor) { + if style == "" { + return } - return 0 - } - getBorder := func(style string, color *xlsxColor) excelize.Border { - b := excelize.Border{ - Type: "top", - Style: getStyleID(style), + border := excelize.Border{ + Type: side, + Style: borderStyleID(style), } - if color != nil { - b.Color = getCellFillColor(f, color)[0] + if colors := getCellFillColor(f, color); len(colors) > 0 { + border.Color = colors[0] } - return b + borders = append(borders, border) } - borders := make([]excelize.Border, 0, 4) - - if topStyle := f.Styles.Borders.Border[*borderID].Top; topStyle.Style != "" { - borders = append(borders, getBorder(topStyle.Style, (*xlsxColor)(topStyle.Color))) + appendBorder("top", definition.Top.Style, (*xlsxColor)(definition.Top.Color)) + appendBorder("bottom", definition.Bottom.Style, (*xlsxColor)(definition.Bottom.Color)) + appendBorder("left", definition.Left.Style, (*xlsxColor)(definition.Left.Color)) + appendBorder("right", definition.Right.Style, (*xlsxColor)(definition.Right.Color)) + if definition.DiagonalUp { + appendBorder("diagonalUp", definition.Diagonal.Style, (*xlsxColor)(definition.Diagonal.Color)) + } + if definition.DiagonalDown { + appendBorder("diagonalDown", definition.Diagonal.Style, (*xlsxColor)(definition.Diagonal.Color)) } - return borders } +func borderStyleID(style string) int { + for i, candidate := range borderStyles { + if candidate == style { + return i + } + } + return 0 +} + func getCellFill(f *excelize.File, styleID int) excelize.Fill { fillID := f.Styles.CellXfs.Xf[styleID].FillID - if fillID == nil { + if fillID == nil || *fillID < 0 || *fillID >= len(f.Styles.Fills.Fill) { return excelize.Fill{} } patternFill := *f.Styles.Fills.Fill[*fillID].PatternFill - fgColor := f.Styles.Fills.Fill[*fillID].PatternFill.FgColor + pattern := 0 + for i, name := range fillPatterns { + if name == patternFill.PatternType { + pattern = i + break + } + } return excelize.Fill{ - Type: patternFill.PatternType, - Pattern: 0, - Color: getCellFillColor(f, (*xlsxColor)(fgColor)), + Type: "pattern", + Pattern: pattern, + Color: getCellFillColor(f, (*xlsxColor)(patternFill.FgColor)), + } +} + +func getCellFont(f *excelize.File, styleID int) *excelize.Font { + fontID := f.Styles.CellXfs.Xf[styleID].FontID + if fontID == nil || *fontID <= 0 || *fontID >= len(f.Styles.Fonts.Font) { + return nil + } + font := f.Styles.Fonts.Font[*fontID] + result := &excelize.Font{} + if font.B != nil && font.B.Val != nil { + result.Bold = *font.B.Val + } + if font.I != nil && font.I.Val != nil { + result.Italic = *font.I.Val + } + if font.U != nil && font.U.Val != nil { + 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 + } + if font.Strike != nil && font.Strike.Val != nil { + result.Strike = *font.Strike.Val + } + if colors := getCellFillColor(f, (*xlsxColor)(font.Color)); len(colors) > 0 { + result.Color = colors[0] + } + return result } func getCellAlignment(f *excelize.File, styleID int) *excelize.Alignment { @@ -111,18 +177,53 @@ func getCellAlignment(f *excelize.File, styleID int) *excelize.Alignment { return nil } return &excelize.Alignment{ - Horizontal: "", - Indent: 0, - JustifyLastLine: false, - ReadingOrder: 0, - RelativeIndent: 0, - ShrinkToFit: false, - TextRotation: 0, - Vertical: "", + 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, } } +func getCellProtection(f *excelize.File, styleID int) *excelize.Protection { + protection := f.Styles.CellXfs.Xf[styleID].Protection + if protection == nil { + return nil + } + result := &excelize.Protection{Locked: true} + if protection.Locked != nil { + result.Locked = *protection.Locked + } + if protection.Hidden != nil { + result.Hidden = *protection.Hidden + } + return result +} + +func getCellNumberFormat(f *excelize.File, styleID int) (int, *string) { + numFmtID := f.Styles.CellXfs.Xf[styleID].NumFmtID + if numFmtID == nil { + return 0, nil + } + if *numFmtID < 164 { + return *numFmtID, nil + } + if f.Styles.NumFmts == nil { + return 0, nil + } + for _, numFmt := range f.Styles.NumFmts.NumFmt { + if numFmt.NumFmtID == *numFmtID { + format := numFmt.FormatCode + return 0, &format + } + } + return 0, nil +} + type xlsxColor struct { Auto bool `xml:"auto,attr,omitempty"` RGB string `xml:"rgb,attr,omitempty"` @@ -132,25 +233,44 @@ type xlsxColor struct { } func getCellFillColor(f *excelize.File, color *xlsxColor) []string { - if color != nil { - if color.Theme != nil { - children := f.Theme.ThemeElements.ClrScheme.Children - if *color.Theme < 4 { - dklt := map[int]string{ - 0: children[1].SysClr.LastClr, - 1: children[0].SysClr.LastClr, - 2: *children[3].SrgbClr.Val, - 3: *children[2].SrgbClr.Val, - } - return []string{ - strings.TrimPrefix( - excelize.ThemeColor(dklt[*color.Theme], color.Tint), "FF"), - } - } - srgbClr := *children[*color.Theme].SrgbClr.Val - return []string{strings.TrimPrefix(excelize.ThemeColor(srgbClr, color.Tint), "FF")} + if color == nil { + return nil + } + + rgb := color.RGB + if color.Theme != nil { + if f.Theme == nil { + return nil + } + children := f.Theme.ThemeElements.ClrScheme.Children + index := *color.Theme + if index < 0 || index >= len(children) { + return nil + } + if index < 4 { + // Theme slots are lt1/dk1/lt2/dk2, while XML stores dk1/lt1/dk2/lt2. + index = []int{1, 0, 3, 2}[index] } - return []string{strings.TrimPrefix(color.RGB, "FF")} + child := children[index] + switch { + case child.SysClr != nil: + rgb = child.SysClr.LastClr + case child.SrgbClr != nil && child.SrgbClr.Val != nil: + rgb = *child.SrgbClr.Val + default: + return nil + } + } + + rgb = strings.TrimPrefix(strings.ToUpper(rgb), "#") + if len(rgb) == 8 { + rgb = rgb[2:] + } + if len(rgb) != 6 { + return nil + } + if color.Theme != nil && color.Tint != 0 { + rgb = strings.TrimPrefix(excelize.ThemeColor(rgb, color.Tint), "FF") } - return nil + return []string{"#" + rgb} } diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go new file mode 100644 index 0000000..b491b91 --- /dev/null +++ b/excelizeutil/excelizeutil_test.go @@ -0,0 +1,242 @@ +package excelizeutil + +import ( + "archive/zip" + "bytes" + "fmt" + "io" + "os" + "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 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 := GetStyle(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) + assert.Equal(t, &excelize.Font{ + Bold: true, Italic: true, Underline: "single", Family: "Aptos", + Size: 11, Strike: true, Color: "#123456", + }, 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) + test.check(t, GetStyle(buffer, "Sheet1", 1, 1)) + }) + } +} + +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 := GetStyle(buffer, "Sheet1", 1, 1).Border + assert.ElementsMatch(t, want, got) +} + +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"}, + } + + 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{ + {``, ``}, + {``, ``}, + {``, ``}, + {``, ``}, + } + for _, replacement := range replacements { + old := []byte(replacement[0]) + require.Equal(t, 1, bytes.Count(data, old)) + data = bytes.Replace(data, old, []byte(replacement[1]), 1) + } + return data + }) + + var got excelize.Style + if !assert.NotPanics(t, func() { got = GetStyle(buffer, "Sheet1", 1, 1) }) { + return + } + 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()) + + _ = GetStyle(*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 +} From c8d6142d87569e8c39d2312b575b1f152f7b1606 Mon Sep 17 00:00:00 2001 From: "tomoki.harada" Date: Thu, 8 Oct 2026 08:42:15 +0000 Subject: [PATCH 03/10] fix(excelizeutil): handle current theme color schema --- excelizeutil/excelizeutil.go | 43 +++++++++++++++++++++++++++---- excelizeutil/excelizeutil_test.go | 17 ++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index e1d9b50..f395ec8 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -2,6 +2,7 @@ package excelizeutil import ( "bytes" + "encoding/xml" "strings" "github.com/xuri/excelize/v2" @@ -232,6 +233,30 @@ type xlsxColor struct { Tint float64 `xml:"tint,attr,omitempty"` } +type xlsxThemeColor struct { + SysClr *struct { + LastClr string `xml:"lastClr,attr"` + } `xml:"sysClr"` + SrgbClr *struct { + Val *string `xml:"val,attr"` + } `xml:"srgbClr"` +} + +type xlsxThemeColorScheme struct { + Dk1 xlsxThemeColor `xml:"dk1"` + Lt1 xlsxThemeColor `xml:"lt1"` + Dk2 xlsxThemeColor `xml:"dk2"` + Lt2 xlsxThemeColor `xml:"lt2"` + Accent1 xlsxThemeColor `xml:"accent1"` + Accent2 xlsxThemeColor `xml:"accent2"` + Accent3 xlsxThemeColor `xml:"accent3"` + Accent4 xlsxThemeColor `xml:"accent4"` + Accent5 xlsxThemeColor `xml:"accent5"` + Accent6 xlsxThemeColor `xml:"accent6"` + Hlink xlsxThemeColor `xml:"hlink"` + FolHlink xlsxThemeColor `xml:"folHlink"` +} + func getCellFillColor(f *excelize.File, color *xlsxColor) []string { if color == nil { return nil @@ -242,15 +267,23 @@ func getCellFillColor(f *excelize.File, color *xlsxColor) []string { if f.Theme == nil { return nil } - children := f.Theme.ThemeElements.ClrScheme.Children + schemeXML, err := xml.Marshal(f.Theme.ThemeElements.ClrScheme) + if err != nil { + return nil + } + var scheme xlsxThemeColorScheme + if err := xml.Unmarshal(schemeXML, &scheme); err != nil { + return nil + } + children := [...]xlsxThemeColor{ + scheme.Lt1, scheme.Dk1, scheme.Lt2, scheme.Dk2, + scheme.Accent1, scheme.Accent2, scheme.Accent3, scheme.Accent4, + scheme.Accent5, scheme.Accent6, scheme.Hlink, scheme.FolHlink, + } index := *color.Theme if index < 0 || index >= len(children) { return nil } - if index < 4 { - // Theme slots are lt1/dk1/lt2/dk2, while XML stores dk1/lt1/dk2/lt2. - index = []int{1, 0, 3, 2}[index] - } child := children[index] switch { case child.SysClr != nil: diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go index b491b91..89f8b8a 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -148,6 +148,14 @@ func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { {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 { @@ -170,6 +178,15 @@ func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { } for _, replacement := range replacements { old := []byte(replacement[0]) + if bytes.Count(data, old) == 0 { + for _, colorElement := range []string{"sysClr", "srgbClr"} { + expanded := []byte(strings.Replace(replacement[0], "/>", ">", 1)) + if bytes.Count(data, expanded) == 1 { + old = expanded + break + } + } + } require.Equal(t, 1, bytes.Count(data, old)) data = bytes.Replace(data, old, []byte(replacement[1]), 1) } From f587bb2135bc3b9defc6a23851cf38e29a78a4f6 Mon Sep 17 00:00:00 2001 From: tom twinkle Date: Thu, 8 Oct 2026 17:41:37 +0900 Subject: [PATCH 04/10] fix(excelizeutil): resolve OOXML styles --- excelizeutil/excelizeutil.go | 414 +++++++++++++++++++++--------- excelizeutil/excelizeutil_test.go | 138 ++++++++-- 2 files changed, 411 insertions(+), 141 deletions(-) diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index f395ec8..ca81ac8 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -1,8 +1,10 @@ package excelizeutil import ( + "archive/zip" "bytes" "encoding/xml" + "io" "strings" "github.com/xuri/excelize/v2" @@ -48,6 +50,7 @@ var borderStyles = []string{ } func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) excelize.Style { + themeColors, numberFormats, indexedColors := readStyleMetadata(excelBuffer) f, err := excelize.OpenReader(&excelBuffer) if err != nil { panic(err) @@ -63,25 +66,111 @@ func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) ex panic(err) } - numFmt, customNumFmt := getCellNumberFormat(f, styleID) + format := resolveCellFormat(f, styleID) + numFmt, customNumFmt := getCellNumberFormat(format.NumFmtID, numberFormats) return excelize.Style{ - Border: getCellBorder(f, styleID), - Fill: getCellFill(f, styleID), - Font: getCellFont(f, styleID), - Alignment: getCellAlignment(f, styleID), - Protection: getCellProtection(f, styleID), + Border: getCellBorder(f, format.BorderID, themeColors, indexedColors), + Fill: getCellFill(f, format.FillID, themeColors, indexedColors), + Font: getCellFont(f, format.FontID, themeColors, indexedColors), + Alignment: format.Alignment, + Protection: format.Protection, NumFmt: numFmt, CustomNumFmt: customNumFmt, } } -func getCellBorder(f *excelize.File, styleID int) []excelize.Border { - borderID := f.Styles.CellXfs.Xf[styleID].BorderID - if borderID == nil || *borderID < 0 || *borderID >= len(f.Styles.Borders.Border) { +type resolvedCellFormat struct { + FontID int + FillID int + BorderID int + NumFmtID int + Alignment *excelize.Alignment + Protection *excelize.Protection +} + +func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { + cellXF := f.Styles.CellXfs.Xf[styleID] + resolved := resolvedCellFormat{ + FontID: integerValue(cellXF.FontID), + FillID: integerValue(cellXF.FillID), + BorderID: integerValue(cellXF.BorderID), + NumFmtID: integerValue(cellXF.NumFmtID), + } + alignment := cellXF.Alignment + protection := cellXF.Protection + if cellXF.XfID != nil && f.Styles.CellStyleXfs != nil && *cellXF.XfID >= 0 && *cellXF.XfID < len(f.Styles.CellStyleXfs.Xf) { + baseXF := f.Styles.CellStyleXfs.Xf[*cellXF.XfID] + resolved.FontID = inheritedStyleID(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 +} + +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) { return nil } - definition := f.Styles.Borders.Border[*borderID] + definition := f.Styles.Borders.Border[borderID] borders := make([]excelize.Border, 0, 6) appendBorder := func(side, style string, color *xlsxColor) { if style == "" { @@ -91,7 +180,7 @@ func getCellBorder(f *excelize.File, styleID int) []excelize.Border { Type: side, Style: borderStyleID(style), } - if colors := getCellFillColor(f, color); len(colors) > 0 { + if colors := getCellFillColor(color, themeColors, indexedColors); len(colors) > 0 { border.Color = colors[0] } borders = append(borders, border) @@ -119,12 +208,11 @@ func borderStyleID(style string) int { return 0 } -func getCellFill(f *excelize.File, styleID int) excelize.Fill { - fillID := f.Styles.CellXfs.Xf[styleID].FillID - if fillID == nil || *fillID < 0 || *fillID >= len(f.Styles.Fills.Fill) { +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) { return excelize.Fill{} } - patternFill := *f.Styles.Fills.Fill[*fillID].PatternFill + patternFill := *f.Styles.Fills.Fill[fillID].PatternFill pattern := 0 for i, name := range fillPatterns { @@ -137,16 +225,15 @@ func getCellFill(f *excelize.File, styleID int) excelize.Fill { return excelize.Fill{ Type: "pattern", Pattern: pattern, - Color: getCellFillColor(f, (*xlsxColor)(patternFill.FgColor)), + Color: getCellFillColor((*xlsxColor)(patternFill.FgColor), themeColors, indexedColors), } } -func getCellFont(f *excelize.File, styleID int) *excelize.Font { - fontID := f.Styles.CellXfs.Xf[styleID].FontID - if fontID == nil || *fontID <= 0 || *fontID >= len(f.Styles.Fonts.Font) { +func getCellFont(f *excelize.File, fontID int, themeColors []string, indexedColors map[int]string) *excelize.Font { + if f.Styles.Fonts == nil || fontID <= 0 || fontID >= len(f.Styles.Fonts.Font) { return nil } - font := f.Styles.Fonts.Font[*fontID] + font := f.Styles.Fonts.Font[fontID] result := &excelize.Font{} if font.B != nil && font.B.Val != nil { result.Bold = *font.B.Val @@ -154,8 +241,11 @@ func getCellFont(f *excelize.File, styleID int) *excelize.Font { if font.I != nil && font.I.Val != nil { result.Italic = *font.I.Val } - if font.U != nil && font.U.Val != nil { - result.Underline = *font.U.Val + 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 @@ -166,61 +256,18 @@ func getCellFont(f *excelize.File, styleID int) *excelize.Font { if font.Strike != nil && font.Strike.Val != nil { result.Strike = *font.Strike.Val } - if colors := getCellFillColor(f, (*xlsxColor)(font.Color)); len(colors) > 0 { + if colors := getCellFillColor((*xlsxColor)(font.Color), themeColors, indexedColors); len(colors) > 0 { result.Color = colors[0] } return result } -func getCellAlignment(f *excelize.File, styleID int) *excelize.Alignment { - alignment := f.Styles.CellXfs.Xf[styleID].Alignment - if alignment == nil { - return nil - } - return &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, - } -} - -func getCellProtection(f *excelize.File, styleID int) *excelize.Protection { - protection := f.Styles.CellXfs.Xf[styleID].Protection - if protection == nil { - return nil - } - result := &excelize.Protection{Locked: true} - if protection.Locked != nil { - result.Locked = *protection.Locked - } - if protection.Hidden != nil { - result.Hidden = *protection.Hidden - } - return result -} - -func getCellNumberFormat(f *excelize.File, styleID int) (int, *string) { - numFmtID := f.Styles.CellXfs.Xf[styleID].NumFmtID - if numFmtID == nil { - return 0, nil - } - if *numFmtID < 164 { - return *numFmtID, nil +func getCellNumberFormat(numFmtID int, numberFormats map[int]string) (int, *string) { + if format, ok := numberFormats[numFmtID]; ok { + return 0, &format } - if f.Styles.NumFmts == nil { - return 0, nil - } - for _, numFmt := range f.Styles.NumFmts.NumFmt { - if numFmt.NumFmtID == *numFmtID { - format := numFmt.FormatCode - return 0, &format - } + if numFmtID >= 0 && numFmtID < 164 { + return numFmtID, nil } return 0, nil } @@ -233,77 +280,194 @@ type xlsxColor struct { Tint float64 `xml:"tint,attr,omitempty"` } -type xlsxThemeColor struct { - SysClr *struct { - LastClr string `xml:"lastClr,attr"` - } `xml:"sysClr"` - SrgbClr *struct { - Val *string `xml:"val,attr"` - } `xml:"srgbClr"` -} - -type xlsxThemeColorScheme struct { - Dk1 xlsxThemeColor `xml:"dk1"` - Lt1 xlsxThemeColor `xml:"lt1"` - Dk2 xlsxThemeColor `xml:"dk2"` - Lt2 xlsxThemeColor `xml:"lt2"` - Accent1 xlsxThemeColor `xml:"accent1"` - Accent2 xlsxThemeColor `xml:"accent2"` - Accent3 xlsxThemeColor `xml:"accent3"` - Accent4 xlsxThemeColor `xml:"accent4"` - Accent5 xlsxThemeColor `xml:"accent5"` - Accent6 xlsxThemeColor `xml:"accent6"` - Hlink xlsxThemeColor `xml:"hlink"` - FolHlink xlsxThemeColor `xml:"folHlink"` -} - -func getCellFillColor(f *excelize.File, color *xlsxColor) []string { - if color == nil { +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 { - if f.Theme == nil { + index := *color.Theme + if index < 0 || index >= len(themeColors) { return nil } - schemeXML, err := xml.Marshal(f.Theme.ThemeElements.ClrScheme) - if err != nil { - 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 } - var scheme xlsxThemeColorScheme - if err := xml.Unmarshal(schemeXML, &scheme); err != nil { - return nil + file, err := entry.Open() + if err != nil { + continue } - children := [...]xlsxThemeColor{ - scheme.Lt1, scheme.Dk1, scheme.Lt2, scheme.Dk2, - scheme.Accent1, scheme.Accent2, scheme.Accent3, scheme.Accent4, - scheme.Accent5, scheme.Accent6, scheme.Hlink, scheme.FolHlink, + data, readErr := io.ReadAll(file) + _ = file.Close() + if readErr != nil { + continue } - index := *color.Theme - if index < 0 || index >= len(children) { - return nil + switch entry.Name { + case "xl/theme/theme1.xml": + themeColors = parseThemeColors(data) + case "xl/styles.xml": + numberFormats = parseNumberFormats(data) + indexedColors = parseIndexedColors(data) } - child := children[index] + } + 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 child.SysClr != nil: - rgb = child.SysClr.LastClr - case child.SrgbClr != nil && child.SrgbClr.Val != nil: - rgb = *child.SrgbClr.Val - default: - return nil + case choice.SysColor != nil: + colors[index] = choice.SysColor.LastColor + case choice.SRGBColor != nil: + colors[index] = choice.SRGBColor.Value } } + return colors +} - rgb = strings.TrimPrefix(strings.ToUpper(rgb), "#") - if len(rgb) == 8 { - rgb = rgb[2:] +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 } - if len(rgb) != 6 { + 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 } - if color.Theme != nil && color.Tint != 0 { - rgb = strings.TrimPrefix(excelize.ThemeColor(rgb, color.Tint), "FF") + colors := make(map[int]string, len(styles.Colors.IndexedColors.RGBColors)) + for index, color := range styles.Colors.IndexedColors.RGBColors { + colors[index] = color.RGB } - return []string{"#" + 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 index 89f8b8a..69418f8 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "regexp" "strings" "testing" @@ -124,6 +125,111 @@ func TestGetStylePreservesFullStyle(t *testing.T) { } } +func TestGetStyleInheritsNamedStyleXF(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func([]byte) []byte { + return []byte(namedStyleStylesXML) + }) + + got := GetStyle(buffer, "Sheet1", 1, 1) + require.Equal(t, 14, got.NumFmt) + require.NotNil(t, got.Font) + assert.Equal(t, &excelize.Font{ + Bold: true, Italic: true, Underline: "single", Family: "Aptos", + Size: 14, Color: "#123456", + }, 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) +} + +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 := GetStyle(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 := GetStyle(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 := GetStyle(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"}, @@ -171,24 +277,15 @@ func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { }) buffer = rewriteXLSXEntry(t, buffer, "xl/theme/theme1.xml", func(data []byte) []byte { replacements := [][2]string{ - {``, ``}, - {``, ``}, - {``, ``}, - {``, ``}, + {"dk1", ``}, + {"lt1", ``}, + {"dk2", ``}, + {"lt2", ``}, } for _, replacement := range replacements { - old := []byte(replacement[0]) - if bytes.Count(data, old) == 0 { - for _, colorElement := range []string{"sysClr", "srgbClr"} { - expanded := []byte(strings.Replace(replacement[0], "/>", ">", 1)) - if bytes.Count(data, expanded) == 1 { - old = expanded - break - } - } - } - require.Equal(t, 1, bytes.Count(data, old)) - data = bytes.Replace(data, old, []byte(replacement[1]), 1) + pattern := regexp.MustCompile(`(?s).*?`) + require.Equal(t, 1, len(pattern.FindAll(data, -1))) + data = pattern.ReplaceAll(data, []byte(``+replacement[1]+``)) } return data }) @@ -257,3 +354,12 @@ func rewriteXLSXEntry(t *testing.T, buffer bytes.Buffer, name string, rewrite fu require.NoError(t, writer.Close()) return output } + +const namedStyleStylesXML = ` + + + + + + +` From 08023ac469d44fe9343a594f32aed42c31fcddac Mon Sep 17 00:00:00 2001 From: "tomoki.harada" Date: Thu, 8 Oct 2026 09:23:27 +0000 Subject: [PATCH 05/10] fix(excelizeutil): preserve complete style fields --- excelizeutil/STYLE_COVERAGE.md | 43 +++ excelizeutil/excelizeutil.go | 439 +++++++++++++++++++++-- excelizeutil/excelizeutil_test.go | 21 +- excelizeutil/style_roundtrip_test.go | 512 +++++++++++++++++++++++++++ 4 files changed, 985 insertions(+), 30 deletions(-) create mode 100644 excelizeutil/STYLE_COVERAGE.md create mode 100644 excelizeutil/style_roundtrip_test.go diff --git a/excelizeutil/STYLE_COVERAGE.md b/excelizeutil/STYLE_COVERAGE.md new file mode 100644 index 0000000..8b6ae64 --- /dev/null +++ b/excelizeutil/STYLE_COVERAGE.md @@ -0,0 +1,43 @@ +# `excelize.Style` の取得・再適用範囲 + +## 対象バージョンと契約 + +この表は、PR の統合ツリーで使われる Excelize **v2.10.1** の `excelize.Style` を基準にしています。対応する公開型の全40フィールドを棚卸しし、セルの実効書式を取得して `NewStyle` で再適用できる範囲を定義します。元の `NewStyle` 入力を復元することや、任意の OOXML を無損失で複製することは契約に含めません。 + +PR ブランチ自身の `go.mod` は v2.6.0 です。v2.6.0 には `Style.Lang` があり、`DecimalPlaces` は `int`、`Fill.Transparency` や Font の色参照フィールドはありません。v2.10.1 では `Style.Lang` がなく、`DecimalPlaces` は `*int` です。v2.6.0 固有の `Lang` 指定は取得・再適用対象外です。実装は両方でビルドできるよう、バージョン固有フィールドを実行時に扱います。gradient の shading 番号もバージョン間で異なるため、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項目を全て設定し、返却値と保存・再読込後の値を検証します。 | +| `Protection` | `Hidden`, `Locked` | 両方の値を保持します。XMLで `locked` が省略された場合は Excel の既定である `true` として扱います。保存・再読込を検証します。 | + +## 再適用できない入力 + +- 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 数、明示適用された Font ID 0、空 boolean 属性、疎な border、pattern の2色制約を確認します。gradient や色参照などの焦点項目は、独自 extractor の戻り値だけでなく、保存後の `xl/styles.xml` を XML として読み直して比較します。 diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index ca81ac8..cdd96cc 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/xml" "io" + "reflect" "strings" "github.com/xuri/excelize/v2" @@ -68,19 +69,22 @@ func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) ex format := resolveCellFormat(f, styleID) numFmt, customNumFmt := getCellNumberFormat(format.NumFmtID, numberFormats) - return excelize.Style{ + style := excelize.Style{ Border: getCellBorder(f, format.BorderID, themeColors, indexedColors), Fill: getCellFill(f, format.FillID, themeColors, indexedColors), - Font: getCellFont(f, format.FontID, 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 } type resolvedCellFormat struct { FontID int + HasFont bool FillID int BorderID int NumFmtID int @@ -92,6 +96,7 @@ func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { 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), @@ -101,6 +106,7 @@ func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { if cellXF.XfID != nil && f.Styles.CellStyleXfs != nil && *cellXF.XfID >= 0 && *cellXF.XfID < len(f.Styles.CellStyleXfs.Xf) { 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) @@ -145,6 +151,16 @@ func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { return resolved } +func inheritedStylePresent(child, parent *int, apply *bool) bool { + if apply != nil { + return *apply + } + if parent != nil { + return true + } + return child != nil && *child != 0 +} + func integerValue(value *int) int { if value == nil { return 0 @@ -166,7 +182,7 @@ func inheritedStyleID(child, parent *int, apply *bool) int { } 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) { + if f.Styles.Borders == nil || borderID < 0 || borderID >= len(f.Styles.Borders.Border) || f.Styles.Borders.Border[borderID] == nil { return nil } @@ -186,19 +202,46 @@ func getCellBorder(f *excelize.File, borderID int, themeColors []string, indexed borders = append(borders, border) } - appendBorder("top", definition.Top.Style, (*xlsxColor)(definition.Top.Color)) - appendBorder("bottom", definition.Bottom.Style, (*xlsxColor)(definition.Bottom.Color)) - appendBorder("left", definition.Left.Style, (*xlsxColor)(definition.Left.Color)) - appendBorder("right", definition.Right.Style, (*xlsxColor)(definition.Right.Color)) + 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 { - appendBorder("diagonalUp", definition.Diagonal.Style, (*xlsxColor)(definition.Diagonal.Color)) + diagonalStyle, diagonalColor := borderLine(definition.Diagonal) + appendBorder("diagonalUp", diagonalStyle, diagonalColor) } if definition.DiagonalDown { - appendBorder("diagonalDown", definition.Diagonal.Style, (*xlsxColor)(definition.Diagonal.Color)) + 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 { for i, candidate := range borderStyles { if candidate == style { @@ -209,10 +252,17 @@ func borderStyleID(style string) int { } 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) { + 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 := *f.Styles.Fills.Fill[fillID].PatternFill + patternFill := definition.PatternFill pattern := 0 for i, name := range fillPatterns { @@ -222,25 +272,187 @@ func getCellFill(f *excelize.File, fillID int, themeColors []string, indexedColo } } + color := patternFill.FgColor + if color == nil { + color = patternFill.BgColor + } return excelize.Fill{ Type: "pattern", Pattern: pattern, - Color: getCellFillColor((*xlsxColor)(patternFill.FgColor), themeColors, indexedColors), + Color: getCellFillColor((*xlsxColor)(color), themeColors, indexedColors), } } -func getCellFont(f *excelize.File, fontID int, themeColors []string, indexedColors map[int]string) *excelize.Font { - if f.Styles.Fonts == nil || fontID <= 0 || fontID >= len(f.Styles.Fonts.Font) { +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 gradientGeometries() []gradientGeometry { + if !hasExcelizeField(reflect.TypeOf(excelize.Fill{}), "Transparency") { + return []gradientGeometry{ + {degree: 90, positions: []float64{0, 1}}, + {positions: []float64{0, 1}}, + {degree: 45, positions: []float64{0, 1}}, + {degree: 135, positions: []float64{0, 1}}, + {typeName: "path", positions: []float64{0, 1}}, + {typeName: "path", left: 0.5, right: 0.5, top: 0.5, bottom: 0.5, positions: []float64{0, 1}}, + } + } + return []gradientGeometry{ + {degree: 90, positions: []float64{0, 1}}, + {degree: 270, positions: []float64{0, 1}}, + {degree: 90, positions: []float64{0, 0.5, 1}}, + {positions: []float64{0, 1}}, + {degree: 180, positions: []float64{0, 1}}, + {positions: []float64{0, 0.5, 1}}, + {degree: 45, positions: []float64{0, 1}}, + {degree: 255, positions: []float64{0, 1}}, + {degree: 45, positions: []float64{0, 0.5, 1}}, + {degree: 135, positions: []float64{0, 1}}, + {degree: 315, positions: []float64{0, 1}}, + {degree: 135, positions: []float64{0, 0.5, 1}}, + {typeName: "path", positions: []float64{0, 1}}, + {typeName: "path", left: 1, right: 1, positions: []float64{0, 1}}, + {typeName: "path", bottom: 1, top: 1, positions: []float64{0, 1}}, + {typeName: "path", left: 1, right: 1, top: 1, bottom: 1, positions: []float64{0, 1}}, + {typeName: "path", left: 0.5, right: 0.5, top: 0.5, bottom: 0.5, positions: []float64{0, 1}}, + } +} + +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{} - if font.B != nil && font.B.Val != nil { - result.Bold = *font.B.Val - } - if font.I != nil && font.I.Val != nil { - result.Italic = *font.I.Val - } + // 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 != "" { @@ -253,15 +465,140 @@ func getCellFont(f *excelize.File, fontID int, themeColors []string, indexedColo if font.Sz != nil && font.Sz.Val != nil { result.Size = *font.Sz.Val } - if font.Strike != nil && font.Strike.Val != nil { - result.Strike = *font.Strike.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 colors := getCellFillColor((*xlsxColor)(font.Color), themeColors, indexedColors); len(colors) > 0 { - result.Color = colors[0] + 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 @@ -272,6 +609,60 @@ func getCellNumberFormat(numFmtID int, numberFormats map[int]string) (int, *stri 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"` diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go index 69418f8..ec8bfa8 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -91,10 +91,12 @@ func TestGetStylePreservesFullStyle(t *testing.T) { }, check: func(t *testing.T, got excelize.Style) { require.NotNil(t, got.Font) - assert.Equal(t, &excelize.Font{ + wantFont := &excelize.Font{ Bold: true, Italic: true, Underline: "single", Family: "Aptos", Size: 11, Strike: true, Color: "#123456", - }, got.Font) + } + 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, @@ -120,7 +122,10 @@ func TestGetStylePreservesFullStyle(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { buffer := workbookWithStyle(t, test.style) - test.check(t, GetStyle(buffer, "Sheet1", 1, 1)) + got := GetStyle(buffer, "Sheet1", 1, 1) + test.check(t, got) + reapplied := workbookWithStyle(t, &got) + test.check(t, GetStyle(reapplied, "Sheet1", 1, 1)) }) } } @@ -133,11 +138,13 @@ func TestGetStyleInheritsNamedStyleXF(t *testing.T) { got := GetStyle(buffer, "Sheet1", 1, 1) require.Equal(t, 14, got.NumFmt) - require.NotNil(t, got.Font) - assert.Equal(t, &excelize.Font{ + wantFont := &excelize.Font{ Bold: true, Italic: true, Underline: "single", Family: "Aptos", Size: 14, Color: "#123456", - }, got.Font) + } + 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"}, @@ -243,6 +250,8 @@ func TestGetStylePreservesAllBorderSides(t *testing.T) { got := GetStyle(buffer, "Sheet1", 1, 1).Border assert.ElementsMatch(t, want, got) + reapplied := workbookWithStyle(t, &excelize.Style{Border: got}) + assert.ElementsMatch(t, want, GetStyle(reapplied, "Sheet1", 1, 1).Border) } func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { diff --git a/excelizeutil/style_roundtrip_test.go b/excelizeutil/style_roundtrip_test.go new file mode 100644 index 0000000..49612ff --- /dev/null +++ b/excelizeutil/style_roundtrip_test.go @@ -0,0 +1,512 @@ +package excelizeutil + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "io" + "reflect" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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) { + maxShading := 16 + if !hasPublicField(reflect.TypeOf(excelize.Fill{}), "Transparency") { + maxShading = 5 + } + for shading := 0; shading <= maxShading; 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 := GetStyle(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 := GetStyle(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 := GetStyle(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 + }) + var got excelize.Style + require.NotPanics(t, func() { got = GetStyle(buffer, "Sheet1", 1, 1) }) + assert.Equal(t, []excelize.Border{{Type: "top", Style: 1, Color: "#112233"}}, got.Border) + assert.Equal(t, got.Border, GetStyle(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 := GetStyle(buffer, "Sheet1", 1, 1) + assert.Equal(t, []string{"#123456"}, got.Fill.Color) + reapplied := workbookWithStyle(t, &got) + assert.Equal(t, []string{"#123456"}, GetStyle(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 <= 18; 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 := GetStyle(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, GetStyle(reapplied, "Sheet1", 1, 1).Fill) + }) + } + for borderStyle := 0; borderStyle <= 13; 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 := GetStyle(workbookWithStyle(t, style), "Sheet1", 1, 1) + require.Equal(t, style.Border, got.Border) + reapplied := workbookWithStyle(t, &got) + assert.Equal(t, got.Border, GetStyle(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 := GetStyle(original, "Sheet1", 1, 1) + require.NotNil(t, got.CustomNumFmt) + assert.Equal(t, code, *got.CustomNumFmt) + reapplied := workbookWithStyle(t, &got) + reread := GetStyle(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 := GetStyle(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 := GetStyle(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"` +} + +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() +} From 8faaf58b6b5b6d6662507e12315f4adeabb833ee Mon Sep 17 00:00:00 2001 From: "tomoki.harada" Date: Thu, 8 Oct 2026 09:37:08 +0000 Subject: [PATCH 06/10] fix(excelizeutil): preserve inherited font and style fields --- excelizeutil/STYLE_COVERAGE.md | 8 +- excelizeutil/excelizeutil.go | 9 +- excelizeutil/excelizeutil_test.go | 76 +++++++++++++- excelizeutil/style_roundtrip_test.go | 149 +++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 6 deletions(-) diff --git a/excelizeutil/STYLE_COVERAGE.md b/excelizeutil/STYLE_COVERAGE.md index 8b6ae64..fca0c91 100644 --- a/excelizeutil/STYLE_COVERAGE.md +++ b/excelizeutil/STYLE_COVERAGE.md @@ -23,8 +23,10 @@ PR ブランチ自身の `go.mod` は v2.6.0 です。v2.6.0 には `Style.Lang` | `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項目を全て設定し、返却値と保存・再読込後の値を検証します。 | -| `Protection` | `Hidden`, `Locked` | 両方の値を保持します。XMLで `locked` が省略された場合は Excel の既定である `true` として扱います。保存・再読込を検証します。 | +| `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))。 ## 再適用できない入力 @@ -40,4 +42,4 @@ PR ブランチ自身の `go.mod` は v2.6.0 です。v2.6.0 には `Style.Lang` ## 検証 -`style_roundtrip_test.go` のテストは v2.10.1 の40フィールド棚卸し、Font fieldごとの再適用、全 gradient preset、pattern 0〜18、border style 0〜13、theme/indexed identity と RGB、組み込み decimal 数、明示適用された Font ID 0、空 boolean 属性、疎な border、pattern の2色制約を確認します。gradient や色参照などの焦点項目は、独自 extractor の戻り値だけでなく、保存後の `xl/styles.xml` を XML として読み直して比較します。 +`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 として読み直して比較します。 diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index cdd96cc..4be1b39 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -152,12 +152,17 @@ func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { } func inheritedStylePresent(child, parent *int, apply *bool) bool { - if apply != nil { - return *apply + 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 } diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go index ec8bfa8..180718f 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -133,7 +133,10 @@ func TestGetStylePreservesFullStyle(t *testing.T) { func TestGetStyleInheritsNamedStyleXF(t *testing.T) { buffer := workbookWithStyle(t, &excelize.Style{NumFmt: 14}) buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func([]byte) []byte { - return []byte(namedStyleStylesXML) + data := []byte(namedStyleStylesXML) + oldChild := []byte(``) + require.Equal(t, 1, bytes.Count(data, oldChild)) + return bytes.Replace(data, oldChild, []byte(``), 1) }) got := GetStyle(buffer, "Sheet1", 1, 1) @@ -152,6 +155,77 @@ func TestGetStyleInheritsNamedStyleXF(t *testing.T) { }, 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 := GetStyle(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 := GetStyle(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 := GetStyle(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 := GetStyle(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 := GetStyle(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) { diff --git a/excelizeutil/style_roundtrip_test.go b/excelizeutil/style_roundtrip_test.go index 49612ff..f10390d 100644 --- a/excelizeutil/style_roundtrip_test.go +++ b/excelizeutil/style_roundtrip_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "reflect" + "regexp" "sort" "strings" "testing" @@ -99,6 +100,66 @@ func TestGetStyleRoundTripsEveryPublicFontField(t *testing.T) { } } +func TestGetStyleRoundTripsEveryAlignmentFieldInXML(t *testing.T) { + alignment := &excelize.Alignment{ + Horizontal: "center", + Indent: 2, + JustifyLastLine: true, + ReadingOrder: 1, + RelativeIndent: 3, + ShrinkToFit: true, + TextRotation: 45, + Vertical: "center", + WrapText: true, + } + original := workbookWithStyle(t, &excelize.Style{Alignment: alignment}) + got := GetStyle(original, "Sheet1", 1, 1) + assert.Equal(t, alignment, got.Alignment) + assert.Equal(t, alignmentStyleXML{ + Horizontal: "center", Indent: "2", JustifyLastLine: "true", ReadingOrder: "1", + RelativeIndent: "3", ShrinkToFit: "true", TextRotation: "45", Vertical: "center", WrapText: "true", + }, styleAlignmentXML(t, original)) + + reapplied := workbookWithStyle(t, &got) + reread := GetStyle(reapplied, "Sheet1", 1, 1) + assert.Equal(t, alignment, reread.Alignment) + assert.Equal(t, styleAlignmentXML(t, original), styleAlignmentXML(t, reapplied), "all nine alignment attributes must survive in saved OOXML") +} + +func TestGetStylePreservesProtectionDefaultAndExplicitRelease(t *testing.T) { + t.Run("omitted attributes use OOXML defaults", func(t *testing.T) { + buffer := workbookWithStyle(t, &excelize.Style{Protection: &excelize.Protection{Hidden: true, Locked: false}}) + buffer = rewriteXLSXEntry(t, buffer, "xl/styles.xml", func(data []byte) []byte { + return stripProtectionAttributes(t, data) + }) + assertProtectionAttrsOmitted(t, styleProtectionXML(t, buffer)) + + got := GetStyle(buffer, "Sheet1", 1, 1) + assert.Equal(t, &excelize.Protection{Locked: true, Hidden: false}, got.Protection) + reapplied := workbookWithStyle(t, &got) + reread := GetStyle(reapplied, "Sheet1", 1, 1) + assert.Equal(t, got.Protection, reread.Protection) + attrs := styleProtectionXML(t, reapplied) + assertXMLBool(t, attrs.Locked, true) + assertXMLBool(t, attrs.Hidden, false) + }) + + t.Run("explicit unlock survives", func(t *testing.T) { + protection := &excelize.Protection{Hidden: true, Locked: false} + original := workbookWithStyle(t, &excelize.Style{Protection: protection}) + got := GetStyle(original, "Sheet1", 1, 1) + assert.Equal(t, protection, got.Protection) + assertXMLBool(t, styleProtectionXML(t, original).Locked, false) + assertXMLBool(t, styleProtectionXML(t, original).Hidden, true) + + reapplied := workbookWithStyle(t, &got) + reread := GetStyle(reapplied, "Sheet1", 1, 1) + assert.Equal(t, protection, reread.Protection) + assertXMLBool(t, styleProtectionXML(t, reapplied).Locked, false) + assertXMLBool(t, styleProtectionXML(t, reapplied).Hidden, true) + }) +} + func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T) { if !hasPublicField(reflect.TypeOf(excelize.Font{}), "ColorTheme") { t.Skip("the linked Excelize version predates the v2.10.1 Font API") @@ -346,6 +407,94 @@ type fontColorXML struct { 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 { From e30424e4d6f677a8cb310c4b6cfa06db7f1ef76c Mon Sep 17 00:00:00 2001 From: tom twinkle Date: Fri, 9 Oct 2026 09:59:18 +0900 Subject: [PATCH 07/10] fix(excelizeutil): return lookup errors --- excelizeutil/excelizeutil.go | 25 ++++++--- excelizeutil/excelizeutil_test.go | 82 ++++++++++++++++++++-------- excelizeutil/style_roundtrip_test.go | 63 +++++++++++---------- 3 files changed, 108 insertions(+), 62 deletions(-) diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index 4be1b39..aa44089 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "encoding/xml" + "fmt" "io" "reflect" "strings" @@ -50,24 +51,29 @@ var borderStyles = []string{ "slantDashDot", } -func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) excelize.Style { +// 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 { - panic(err) + return excelize.Style{}, fmt.Errorf("open workbook: %w", err) } defer func() { _ = f.Close() }() cell, err := excelize.CoordinatesToCellName(corIdx, rowIdx) if err != nil { - panic(err) + return excelize.Style{}, fmt.Errorf("convert cell coordinates: %w", err) } styleID, err := f.GetCellStyle(sheetName, cell) if err != nil { - panic(err) + return excelize.Style{}, fmt.Errorf("get cell style: %w", err) } - format := resolveCellFormat(f, styleID) + format, err := resolveCellFormat(f, styleID) + if 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), @@ -79,7 +85,7 @@ func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) ex CustomNumFmt: customNumFmt, } applyNativeNumberFormatMetadata(&style, f, styleID, format, customNumFmt) - return style + return style, nil } type resolvedCellFormat struct { @@ -92,7 +98,10 @@ type resolvedCellFormat struct { Protection *excelize.Protection } -func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { +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("invalid cell style ID %d", styleID) + } cellXF := f.Styles.CellXfs.Xf[styleID] resolved := resolvedCellFormat{ FontID: integerValue(cellXF.FontID), @@ -148,7 +157,7 @@ func resolveCellFormat(f *excelize.File, styleID int) resolvedCellFormat { resolved.Protection.Hidden = *protection.Hidden } } - return resolved + return resolved, nil } func inheritedStylePresent(child, parent *int, apply *bool) bool { diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go index 180718f..5f34dd7 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -30,6 +30,47 @@ func workbookWithStyle(t *testing.T, style *excelize.Style) bytes.Buffer { 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 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.Error(t, err) +} + func TestGetStylePreservesPatternFill(t *testing.T) { tests := []struct { name string @@ -49,7 +90,7 @@ func TestGetStylePreservesPatternFill(t *testing.T) { }, }) - got := GetStyle(buffer, "Sheet1", 1, 1).Fill + 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) @@ -122,10 +163,10 @@ func TestGetStylePreservesFullStyle(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { buffer := workbookWithStyle(t, test.style) - got := GetStyle(buffer, "Sheet1", 1, 1) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) test.check(t, got) reapplied := workbookWithStyle(t, &got) - test.check(t, GetStyle(reapplied, "Sheet1", 1, 1)) + test.check(t, mustGetStyle(t, reapplied, "Sheet1", 1, 1)) }) } } @@ -139,7 +180,7 @@ func TestGetStyleInheritsNamedStyleXF(t *testing.T) { return bytes.Replace(data, oldChild, []byte(``), 1) }) - got := GetStyle(buffer, "Sheet1", 1, 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", @@ -157,7 +198,7 @@ func TestGetStyleInheritsNamedStyleXF(t *testing.T) { assert.Equal(t, &excelize.Protection{Locked: false, Hidden: true}, got.Protection) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + 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{ @@ -184,14 +225,14 @@ func TestGetStyleInheritsCustomizedFontZeroWhenChildDoesNotApplyFont(t *testing. return bytes.Replace(data, oldChild, []byte(``), 1) }) - got := GetStyle(buffer, "Sheet1", 1, 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 := GetStyle(reapplied, "Sheet1", 1, 1) + 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) @@ -213,14 +254,14 @@ func TestGetStyleAppliesImplicitFontZeroWhenApplyFontIsTrue(t *testing.T) { }) assert.Contains(t, xlsxEntry(t, buffer, "xl/styles.xml"), ``) - got := GetStyle(buffer, "Sheet1", 1, 1) + 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 := GetStyle(reapplied, "Sheet1", 1, 1) + 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) @@ -236,7 +277,7 @@ func TestGetStylePreservesLocalizedBuiltInNumberFormatID(t *testing.T) { return bytes.Replace(data, old, []byte(`"), append(colors, []byte("")...), 1) }) - got := GetStyle(buffer, "Sheet1", 1, 1).Fill.Color + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Fill.Color assert.Equal(t, []string{"#BEAD10"}, got) } @@ -294,7 +335,7 @@ func TestGetStyleResolvesDefaultIndexedOOXMLColor(t *testing.T) { return bytes.Replace(data, old, []byte(`indexed="5"`), 1) }) - got := GetStyle(buffer, "Sheet1", 1, 1).Fill.Color + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Fill.Color assert.Equal(t, []string{"#FFFF00"}, got) } @@ -306,7 +347,7 @@ func TestGetStyleDefaultsOmittedUnderlineValueToSingle(t *testing.T) { return bytes.Replace(data, old, nil, 1) }) - got := GetStyle(buffer, "Sheet1", 1, 1).Font + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Font require.NotNil(t, got) assert.Equal(t, "single", got.Underline) } @@ -322,10 +363,10 @@ func TestGetStylePreservesAllBorderSides(t *testing.T) { } buffer := workbookWithStyle(t, &excelize.Style{Border: want}) - got := GetStyle(buffer, "Sheet1", 1, 1).Border + got := mustGetStyle(t, buffer, "Sheet1", 1, 1).Border assert.ElementsMatch(t, want, got) reapplied := workbookWithStyle(t, &excelize.Style{Border: got}) - assert.ElementsMatch(t, want, GetStyle(reapplied, "Sheet1", 1, 1).Border) + assert.ElementsMatch(t, want, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Border) } func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { @@ -373,10 +414,7 @@ func TestGetStyleResolvesThemeColorChoiceKinds(t *testing.T) { return data }) - var got excelize.Style - if !assert.NotPanics(t, func() { got = GetStyle(buffer, "Sheet1", 1, 1) }) { - return - } + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) require.Len(t, got.Fill.Color, 1) assert.Equal(t, test.want, got.Fill.Color[0]) }) @@ -401,7 +439,7 @@ func TestGetStyleClosesLargeWorkbookTemporaryFiles(t *testing.T) { require.NoError(t, err) require.NoError(t, f.Close()) - _ = GetStyle(*buffer, sheet, 1, 1) + _ = mustGetStyle(t, *buffer, sheet, 1, 1) entries, err := os.ReadDir(tempDir) require.NoError(t, err) diff --git a/excelizeutil/style_roundtrip_test.go b/excelizeutil/style_roundtrip_test.go index f10390d..f6f7ef7 100644 --- a/excelizeutil/style_roundtrip_test.go +++ b/excelizeutil/style_roundtrip_test.go @@ -42,13 +42,13 @@ func TestGetStyleRoundTripsAllGradientPresets(t *testing.T) { 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 := GetStyle(original, "Sheet1", 1, 1) + 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 := GetStyle(reapplied, "Sheet1", 1, 1) + 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") }) @@ -85,13 +85,13 @@ func TestGetStyleRoundTripsEveryPublicFontField(t *testing.T) { require.True(t, setPublicField(font, test.field, test.value), "field %s must exist", test.field) style := &excelize.Style{Font: font} original := workbookWithStyle(t, style) - got := GetStyle(original, "Sheet1", 1, 1) + got := mustGetStyle(t, original, "Sheet1", 1, 1) require.NotNil(t, got.Font) gotValue := publicFieldValue(got.Font, test.field) assert.Equal(t, test.want, gotValue) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) require.NotNil(t, reread.Font) rereadValue := publicFieldValue(reread.Font, test.field) assert.Equal(t, test.want, rereadValue) @@ -113,7 +113,7 @@ func TestGetStyleRoundTripsEveryAlignmentFieldInXML(t *testing.T) { WrapText: true, } original := workbookWithStyle(t, &excelize.Style{Alignment: alignment}) - got := GetStyle(original, "Sheet1", 1, 1) + got := mustGetStyle(t, original, "Sheet1", 1, 1) assert.Equal(t, alignment, got.Alignment) assert.Equal(t, alignmentStyleXML{ Horizontal: "center", Indent: "2", JustifyLastLine: "true", ReadingOrder: "1", @@ -121,7 +121,7 @@ func TestGetStyleRoundTripsEveryAlignmentFieldInXML(t *testing.T) { }, styleAlignmentXML(t, original)) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) assert.Equal(t, alignment, reread.Alignment) assert.Equal(t, styleAlignmentXML(t, original), styleAlignmentXML(t, reapplied), "all nine alignment attributes must survive in saved OOXML") } @@ -134,10 +134,10 @@ func TestGetStylePreservesProtectionDefaultAndExplicitRelease(t *testing.T) { }) assertProtectionAttrsOmitted(t, styleProtectionXML(t, buffer)) - got := GetStyle(buffer, "Sheet1", 1, 1) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) assert.Equal(t, &excelize.Protection{Locked: true, Hidden: false}, got.Protection) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) assert.Equal(t, got.Protection, reread.Protection) attrs := styleProtectionXML(t, reapplied) assertXMLBool(t, attrs.Locked, true) @@ -147,13 +147,13 @@ func TestGetStylePreservesProtectionDefaultAndExplicitRelease(t *testing.T) { t.Run("explicit unlock survives", func(t *testing.T) { protection := &excelize.Protection{Hidden: true, Locked: false} original := workbookWithStyle(t, &excelize.Style{Protection: protection}) - got := GetStyle(original, "Sheet1", 1, 1) + got := mustGetStyle(t, original, "Sheet1", 1, 1) assert.Equal(t, protection, got.Protection) assertXMLBool(t, styleProtectionXML(t, original).Locked, false) assertXMLBool(t, styleProtectionXML(t, original).Hidden, true) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) assert.Equal(t, protection, reread.Protection) assertXMLBool(t, styleProtectionXML(t, reapplied).Locked, false) assertXMLBool(t, styleProtectionXML(t, reapplied).Hidden, true) @@ -170,7 +170,7 @@ func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T require.True(t, setPublicField(themeFont, "ColorTheme", themeIndex)) require.True(t, setPublicField(themeFont, "ColorTint", float64(0.25))) source := workbookWithStyle(t, &excelize.Style{Font: themeFont}) - got := GetStyle(source, "Sheet1", 1, 1) + got := mustGetStyle(t, source, "Sheet1", 1, 1) require.NotNil(t, got.Font) assert.Equal(t, themeIndex, publicFieldValue(got.Font, "ColorTheme")) assert.Equal(t, float64(0.25), publicFieldValue(got.Font, "ColorTint")) @@ -188,7 +188,7 @@ func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T require.NotNil(t, themeColor.Theme) assert.Equal(t, themeIndex, *themeColor.Theme) assert.Equal(t, 0.25, themeColor.Tint) - assert.Equal(t, got.Font.Color, GetStyle(sameTheme, "Sheet1", 1, 1).Font.Color) + assert.Equal(t, got.Font.Color, mustGetStyle(t, sameTheme, "Sheet1", 1, 1).Font.Color) assert.NotEmpty(t, wantThemeRGB, "resolved appearance color is computed separately from selector identity") // A caller can choose appearance-only preservation by applying the resolved @@ -197,7 +197,7 @@ func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T require.True(t, setPublicField(&rgbFont, "ColorIndexed", -1)) rgbOnly := &excelize.Style{Font: &rgbFont} rgbWorkbook := workbookWithStyle(t, rgbOnly) - rgbReread := GetStyle(rgbWorkbook, "Sheet1", 1, 1) + rgbReread := mustGetStyle(t, rgbWorkbook, "Sheet1", 1, 1) assert.Equal(t, wantThemeRGB, rgbReread.Font.Color) rgbColor := styleFontColors(t, rgbWorkbook)[1] assert.Equal(t, "FF"+strings.TrimPrefix(wantThemeRGB, "#"), rgbColor.RGB) @@ -208,7 +208,7 @@ func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T require.True(t, setPublicField(indexedFont, "ColorIndexed", 7)) require.True(t, setPublicField(indexedFont, "ColorTint", float64(0.25))) indexedSource := workbookWithStyle(t, &excelize.Style{Font: indexedFont}) - indexed := GetStyle(indexedSource, "Sheet1", 1, 1) + indexed := mustGetStyle(t, indexedSource, "Sheet1", 1, 1) require.NotNil(t, indexed.Font) assert.Equal(t, 7, publicFieldValue(indexed.Font, "ColorIndexed")) assert.Equal(t, float64(0.25), publicFieldValue(indexed.Font, "ColorTint")) @@ -220,7 +220,7 @@ func TestGetStylePreservesFontColorIdentityAndResolvedRGBSeparately(t *testing.T assert.Equal(t, 7, *indexedColor.Indexed) assert.Nil(t, indexedColor.Theme) assert.Equal(t, 0.25, indexedColor.Tint) - assert.Equal(t, indexed.Font.Color, GetStyle(indexedTarget, "Sheet1", 1, 1).Font.Color) + assert.Equal(t, indexed.Font.Color, mustGetStyle(t, indexedTarget, "Sheet1", 1, 1).Font.Color) wantIndexedRGB := normalizeRGB(excelize.ThemeColor("00FFFF", 0.25)) assert.Equal(t, []string{wantIndexedRGB}, resolvedFontColor(t, indexedSource, 1)) assert.Equal(t, []string{wantIndexedRGB}, resolvedFontColor(t, indexedTarget, 1), "the indexed palette appearance remains the same after selector round-trip") @@ -234,7 +234,7 @@ func TestGetStylePreservesRGBBaseAndFontTintOnReapply(t *testing.T) { require.True(t, setPublicField(font, "ColorIndexed", -1)) require.True(t, setPublicField(font, "ColorTint", float64(0.25))) original := workbookWithStyle(t, &excelize.Style{Font: font}) - got := GetStyle(original, "Sheet1", 1, 1) + got := mustGetStyle(t, original, "Sheet1", 1, 1) require.NotNil(t, got.Font) assert.Equal(t, "#123456", got.Font.Color) assert.Equal(t, -1, publicFieldValue(got.Font, "ColorIndexed")) @@ -256,7 +256,7 @@ func TestGetStyleDoesNotTurnAutomaticFontColorIntoIndexedBlack(t *testing.T) { return bytes.Replace(data, old, []byte(`auto="1"`), 1) }) - got := GetStyle(buffer, "Sheet1", 1, 1) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) require.NotNil(t, got.Font) assert.Empty(t, got.Font.Color) if hasPublicField(reflect.TypeOf(excelize.Font{}), "ColorIndexed") { @@ -266,7 +266,7 @@ func TestGetStyleDoesNotTurnAutomaticFontColorIntoIndexedBlack(t *testing.T) { reapplied := workbookWithStyle(t, &got) assert.NotContains(t, xlsxEntry(t, reapplied, "xl/styles.xml"), `indexed="0"`, "the API cannot represent automatic color, but must not silently turn it black") if hasPublicField(reflect.TypeOf(excelize.Font{}), "ColorIndexed") { - assert.Equal(t, -1, publicFieldValue(GetStyle(reapplied, "Sheet1", 1, 1).Font, "ColorIndexed")) + assert.Equal(t, -1, publicFieldValue(mustGetStyle(t, reapplied, "Sheet1", 1, 1).Font, "ColorIndexed")) } } @@ -286,7 +286,7 @@ func TestGetStylePreservesExplicitDefaultFontAndEmptyBooleanValues(t *testing.T) return bytes.Replace(data, oldXF, []byte(`fontId="0" applyFont="1"`), 1) }) - got := GetStyle(buffer, "Sheet1", 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") @@ -301,10 +301,9 @@ func TestGetStyleHandlesSparseBorderXML(t *testing.T) { data = bytes.Replace(data, []byte(``), nil, 1) return data }) - var got excelize.Style - require.NotPanics(t, func() { got = GetStyle(buffer, "Sheet1", 1, 1) }) + 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, GetStyle(workbookWithStyle(t, &got), "Sheet1", 1, 1).Border) + assert.Equal(t, got.Border, mustGetStyle(t, workbookWithStyle(t, &got), "Sheet1", 1, 1).Border) } func TestGetStyleUsesForegroundColorWhenPatternHasBothColors(t *testing.T) { @@ -319,10 +318,10 @@ func TestGetStyleUsesForegroundColorWhenPatternHasBothColors(t *testing.T) { return data }) - got := GetStyle(buffer, "Sheet1", 1, 1) + got := mustGetStyle(t, buffer, "Sheet1", 1, 1) assert.Equal(t, []string{"#123456"}, got.Fill.Color) reapplied := workbookWithStyle(t, &got) - assert.Equal(t, []string{"#123456"}, GetStyle(reapplied, "Sheet1", 1, 1).Fill.Color) + 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") } @@ -330,21 +329,21 @@ func TestGetStyleRoundTripsEveryPatternIDAndBorderStyle(t *testing.T) { for pattern := 0; pattern <= 18; 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 := GetStyle(workbookWithStyle(t, style), "Sheet1", 1, 1) + 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, GetStyle(reapplied, "Sheet1", 1, 1).Fill) + assert.Equal(t, got.Fill, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Fill) }) } for borderStyle := 0; borderStyle <= 13; 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 := GetStyle(workbookWithStyle(t, style), "Sheet1", 1, 1) + 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, GetStyle(reapplied, "Sheet1", 1, 1).Border) + assert.Equal(t, got.Border, mustGetStyle(t, reapplied, "Sheet1", 1, 1).Border) }) } } @@ -361,11 +360,11 @@ func TestGetStylePreservesNumberFormatIntentWithoutGuessingFormatTokens(t *testi style := &excelize.Style{NumFmt: 164, CustomNumFmt: &code, NegRed: true} require.True(t, setPublicField(style, "DecimalPlaces", 3)) original := workbookWithStyle(t, style) - got := GetStyle(original, "Sheet1", 1, 1) + got := mustGetStyle(t, original, "Sheet1", 1, 1) require.NotNil(t, got.CustomNumFmt) assert.Equal(t, code, *got.CustomNumFmt) reapplied := workbookWithStyle(t, &got) - reread := GetStyle(reapplied, "Sheet1", 1, 1) + 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) @@ -378,13 +377,13 @@ func TestGetStyleMapsBuiltInDecimalPlacesField(t *testing.T) { } style := &excelize.Style{NumFmt: 44} original := workbookWithStyle(t, style) - got := GetStyle(original, "Sheet1", 1, 1) + 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 := GetStyle(reapplied, "Sheet1", 1, 1) + reread := mustGetStyle(t, reapplied, "Sheet1", 1, 1) assert.Equal(t, 44, reread.NumFmt) assert.Equal(t, 2, publicFieldValue(&reread, "DecimalPlaces")) } From 12350a051938468f39449b28d5dfb14c934f786b Mon Sep 17 00:00:00 2001 From: tom twinkle Date: Fri, 9 Oct 2026 10:08:55 +0900 Subject: [PATCH 08/10] fix(excelizeutil): validate style references --- excelizeutil/excelizeutil.go | 81 +++++++++++++++++++++++++++++- excelizeutil/excelizeutil_test.go | 82 ++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 3 deletions(-) diff --git a/excelizeutil/excelizeutil.go b/excelizeutil/excelizeutil.go index aa44089..29aa451 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "encoding/xml" + "errors" "fmt" "io" "reflect" @@ -51,6 +52,8 @@ var borderStyles = []string{ "slantDashDot", } +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) { @@ -74,6 +77,9 @@ func GetStyle(excelBuffer bytes.Buffer, sheetName string, corIdx, rowIdx int) (e 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), @@ -100,7 +106,7 @@ type resolvedCellFormat struct { 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("invalid cell style ID %d", styleID) + return resolvedCellFormat{}, fmt.Errorf("%w: cell style ID %d is out of range", errInvalidStyleReference, styleID) } cellXF := f.Styles.CellXfs.Xf[styleID] resolved := resolvedCellFormat{ @@ -112,7 +118,10 @@ func resolveCellFormat(f *excelize.File, styleID int) (resolvedCellFormat, error } alignment := cellXF.Alignment protection := cellXF.Protection - if cellXF.XfID != nil && f.Styles.CellStyleXfs != nil && *cellXF.XfID >= 0 && *cellXF.XfID < len(f.Styles.CellStyleXfs.Xf) { + 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) @@ -160,6 +169,74 @@ func resolveCellFormat(f *excelize.File, styleID int) (resolvedCellFormat, error 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 diff --git a/excelizeutil/excelizeutil_test.go b/excelizeutil/excelizeutil_test.go index 5f34dd7..16644d5 100644 --- a/excelizeutil/excelizeutil_test.go +++ b/excelizeutil/excelizeutil_test.go @@ -3,6 +3,7 @@ package excelizeutil import ( "archive/zip" "bytes" + "errors" "fmt" "io" "os" @@ -43,6 +44,16 @@ func TestGetStyleReturnsOpenReaderError(t *testing.T) { 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}} { @@ -68,7 +79,40 @@ func TestGetStyleReturnsInvalidCellStyleIDError(t *testing.T) { }) _, err := GetStyle(buffer, "Sheet1", 1, 1) - require.Error(t, err) + 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) { @@ -476,6 +520,42 @@ func rewriteXLSXEntry(t *testing.T, buffer bytes.Buffer, name string, rewrite fu 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 = ` From d982bdc1390417d6aed87b9f3bd2d62441f64039 Mon Sep 17 00:00:00 2001 From: tom twinkle <47764757+tomtwinkle@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:40:21 +0900 Subject: [PATCH 09/10] feat(excelizeutil): generate style enums --- excelizeutil/STYLE_COVERAGE.md | 4 + excelizeutil/cmd/gen-style-enums/main.go | 258 +++++++++++++++++++++++ excelizeutil/excelizeutil.go | 58 +---- excelizeutil/style_enums_gen.go | 208 ++++++++++++++++++ 4 files changed, 478 insertions(+), 50 deletions(-) create mode 100644 excelizeutil/cmd/gen-style-enums/main.go create mode 100644 excelizeutil/style_enums_gen.go diff --git a/excelizeutil/STYLE_COVERAGE.md b/excelizeutil/STYLE_COVERAGE.md index fca0c91..28ef565 100644 --- a/excelizeutil/STYLE_COVERAGE.md +++ b/excelizeutil/STYLE_COVERAGE.md @@ -43,3 +43,7 @@ PR ブランチ自身の `go.mod` は v2.6.0 です。v2.6.0 には `Style.Lang` ## 検証 `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 として読み直して比較します。 + +## Excelize の書式名 enum の更新 + +fill pattern と border style は Excelize の公開 enum ではありません。`go generate ./excelizeutil` は、依存中の Excelize `styles.go` を Go AST で解析し、順序付きの型付き定数と XML 名の変換関数を `style_enums_gen.go` に生成します。Excelize v2.6.0 の関数内 slice と v2.9 以降の package-level slice の両方を読み取ります。Excelize を更新した場合はこのコマンドを再実行してください。上流で定義の構造や場所が変わった場合は、生成器がエラーにして見落としを防ぎます。 diff --git a/excelizeutil/cmd/gen-style-enums/main.go b/excelizeutil/cmd/gen-style-enums/main.go new file mode 100644 index 0000000..33df43a --- /dev/null +++ b/excelizeutil/cmd/gen-style-enums/main.go @@ -0,0 +1,258 @@ +package main + +import ( + "bytes" + "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 +} + +func main() { + if err := generate(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func generate() error { + moduleDir, err := excelizeModuleDir() + if err != nil { + return err + } + filename := filepath.Join(moduleDir, "styles.go") + file, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.AllErrors) + if err != nil { + return 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\n") + for _, spec := range specs { + values, err := findEnumValues(file, spec) + if err != nil { + return err + } + if err := writeEnum(&output, spec, values); err != nil { + return err + } + } + + formatted, err := format.Source(output.Bytes()) + if err != nil { + return fmt.Errorf("format generated enums: %w", err) + } + if err := os.WriteFile("style_enums_gen.go", formatted, 0o644); err != nil { + return fmt.Errorf("write generated enums: %w", err) + } + return 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 + } + fmt.Fprintf(output, "func parse%s(value string) (%s, bool) {\n\tswitch value {\n", 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 index 29aa451..b0b0c2f 100644 --- a/excelizeutil/excelizeutil.go +++ b/excelizeutil/excelizeutil.go @@ -1,3 +1,5 @@ +//go:generate go run ./cmd/gen-style-enums + package excelizeutil import ( @@ -13,45 +15,6 @@ import ( "github.com/xuri/excelize/v2" ) -var fillPatterns = []string{ - "none", - "solid", - "mediumGray", - "darkGray", - "lightGray", - "darkHorizontal", - "darkVertical", - "darkDown", - "darkUp", - "darkGrid", - "darkTrellis", - "lightHorizontal", - "lightVertical", - "lightDown", - "lightUp", - "lightGrid", - "lightTrellis", - "gray125", - "gray0625", -} - -var borderStyles = []string{ - "none", - "thin", - "medium", - "dashed", - "dotted", - "thick", - "double", - "hair", - "mediumDashed", - "dashDot", - "mediumDashDot", - "dashDotDot", - "mediumDashDotDot", - "slantDashDot", -} - var errInvalidStyleReference = errors.New("invalid style reference") // GetStyle returns the effective style for a cell in an XLSX workbook. @@ -334,12 +297,10 @@ func borderLine(line any) (string, *xlsxColor) { } func borderStyleID(style string) int { - for i, candidate := range borderStyles { - if candidate == style { - return i - } + if value, ok := parseBorderStyle(style); ok { + return int(value) } - return 0 + return int(borderStyleNone) } func getCellFill(f *excelize.File, fillID int, themeColors []string, indexedColors map[int]string) excelize.Fill { @@ -355,12 +316,9 @@ func getCellFill(f *excelize.File, fillID int, themeColors []string, indexedColo } patternFill := definition.PatternFill - pattern := 0 - for i, name := range fillPatterns { - if name == patternFill.PatternType { - pattern = i - break - } + pattern := int(fillPatternNone) + if value, ok := parseFillPattern(patternFill.PatternType); ok { + pattern = int(value) } color := patternFill.FgColor diff --git a/excelizeutil/style_enums_gen.go b/excelizeutil/style_enums_gen.go new file mode 100644 index 0000000..9f16bf9 --- /dev/null +++ b/excelizeutil/style_enums_gen.go @@ -0,0 +1,208 @@ +// Code generated by go generate; DO NOT EDIT. + +package excelizeutil + +type fillPattern int + +const ( + fillPatternNone fillPattern = iota + fillPatternSolid + fillPatternMediumGray + fillPatternDarkGray + fillPatternLightGray + fillPatternDarkHorizontal + fillPatternDarkVertical + fillPatternDarkDown + fillPatternDarkUp + fillPatternDarkGrid + fillPatternDarkTrellis + fillPatternLightHorizontal + fillPatternLightVertical + fillPatternLightDown + fillPatternLightUp + fillPatternLightGrid + fillPatternLightTrellis + fillPatternGray125 + fillPatternGray0625 + 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 + } +} + +type borderStyle int + +const ( + borderStyleNone borderStyle = iota + borderStyleThin + borderStyleMedium + borderStyleDashed + borderStyleDotted + borderStyleThick + borderStyleDouble + borderStyleHair + borderStyleMediumDashed + borderStyleDashDot + borderStyleMediumDashDot + borderStyleDashDotDot + borderStyleMediumDashDotDot + borderStyleSlantDashDot + 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 + } +} From 5ad332d0dfcff54869d47a964bca18924f5f6237 Mon Sep 17 00:00:00 2001 From: tom twinkle <47764757+tomtwinkle@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:41:24 +0900 Subject: [PATCH 10/10] docs(excelizestyle): document style enums --- excelizestyle/style.go | 43 +- excelizestyle/style_enums_gen.go | 2837 +++++++++++++++-- .../cmd/gen-style-enums/additional.go | 9 +- excelizeutil/cmd/gen-style-enums/godoc.go | 237 ++ .../cmd/gen-style-enums/godoc_test.go | 90 + excelizeutil/cmd/gen-style-enums/main.go | 8 + 6 files changed, 2985 insertions(+), 239 deletions(-) create mode 100644 excelizeutil/cmd/gen-style-enums/godoc.go create mode 100644 excelizeutil/cmd/gen-style-enums/godoc_test.go diff --git a/excelizestyle/style.go b/excelizestyle/style.go index 347acda..1890d5c 100644 --- a/excelizestyle/style.go +++ b/excelizestyle/style.go @@ -6,24 +6,55 @@ import ( // Preserve the existing border style names as aliases of the generated enums. const ( + // 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 = BorderStyleDashed - BorderStyleDash2 = BorderStyleMediumDashed - BorderStyleDot = BorderStyleDotted - BorderStyleDashDot1 = BorderStyleDashDot - BorderStyleDashDot2 = BorderStyleMediumDashDot + // 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 = BorderStyleSlantDashDot + // 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" ) diff --git a/excelizestyle/style_enums_gen.go b/excelizestyle/style_enums_gen.go index 4fad110..a9fb852 100644 --- a/excelizestyle/style_enums_gen.go +++ b/excelizestyle/style_enums_gen.go @@ -4,28 +4,91 @@ 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 ) @@ -119,23 +182,71 @@ func ParseFillPattern(value string) (FillPattern, bool) { } } +// 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 ) @@ -209,14 +320,35 @@ func ParseBorderStyle(value string) (BorderStyle, bool) { } } +// BorderPosition selects the edge or diagonal used by excelize.Border.Type. +// +// See https://xuri.me/excelize/en/style.html type BorderPosition string const ( - BorderPositionLeft BorderPosition = "left" - BorderPositionRight BorderPosition = "right" - BorderPositionTop BorderPosition = "top" - BorderPositionBottom BorderPosition = "bottom" - BorderPositionDiagonalUp BorderPosition = "diagonalUp" + // 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" ) @@ -224,622 +356,2343 @@ 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 FillType = "pattern" - FillTypeUnset FillType = "" + // 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 AlignmentHorizontal = "left" - AlignmentHorizontalCenter AlignmentHorizontal = "center" - AlignmentHorizontalRight AlignmentHorizontal = "right" - AlignmentHorizontalFill AlignmentHorizontal = "fill" - AlignmentHorizontalJustify AlignmentHorizontal = "justify" + // 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 AlignmentHorizontal = "distributed" - AlignmentHorizontalGeneral AlignmentHorizontal = "general" + // 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 AlignmentVertical = "top" - AlignmentVerticalCenter AlignmentVertical = "center" - AlignmentVerticalJustify AlignmentVertical = "justify" + // 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 AlignmentVertical = "bottom" + // 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 FontUnderline = "none" + // 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 FontUnderline = "" + // 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 FontVerticalAlign = "baseline" + // 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 FontVerticalAlign = "subscript" + // 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 follows the OOXML readingOrder attribute. +// 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 NumberFormat = 0 - NumberFormatID1 NumberFormat = 1 - NumberFormatID2 NumberFormat = 2 - NumberFormatID3 NumberFormat = 3 - NumberFormatID4 NumberFormat = 4 - NumberFormatID9 NumberFormat = 9 - NumberFormatID10 NumberFormat = 10 - NumberFormatID11 NumberFormat = 11 - NumberFormatID12 NumberFormat = 12 - NumberFormatID13 NumberFormat = 13 - NumberFormatID14 NumberFormat = 14 - NumberFormatID15 NumberFormat = 15 - NumberFormatID16 NumberFormat = 16 - NumberFormatID17 NumberFormat = 17 - NumberFormatID18 NumberFormat = 18 - NumberFormatID19 NumberFormat = 19 - NumberFormatID20 NumberFormat = 20 - NumberFormatID21 NumberFormat = 21 - NumberFormatID22 NumberFormat = 22 - NumberFormatID27 NumberFormat = 27 - NumberFormatID28 NumberFormat = 28 - NumberFormatID29 NumberFormat = 29 - NumberFormatID30 NumberFormat = 30 - NumberFormatID31 NumberFormat = 31 - NumberFormatID32 NumberFormat = 32 - NumberFormatID33 NumberFormat = 33 - NumberFormatID34 NumberFormat = 34 - NumberFormatID35 NumberFormat = 35 - NumberFormatID36 NumberFormat = 36 - NumberFormatID37 NumberFormat = 37 - NumberFormatID38 NumberFormat = 38 - NumberFormatID39 NumberFormat = 39 - NumberFormatID40 NumberFormat = 40 - NumberFormatID41 NumberFormat = 41 - NumberFormatID42 NumberFormat = 42 - NumberFormatID43 NumberFormat = 43 - NumberFormatID44 NumberFormat = 44 - NumberFormatID45 NumberFormat = 45 - NumberFormatID46 NumberFormat = 46 - NumberFormatID47 NumberFormat = 47 - NumberFormatID48 NumberFormat = 48 - NumberFormatID49 NumberFormat = 49 - NumberFormatID50 NumberFormat = 50 - NumberFormatID51 NumberFormat = 51 - NumberFormatID52 NumberFormat = 52 - NumberFormatID53 NumberFormat = 53 - NumberFormatID54 NumberFormat = 54 - NumberFormatID55 NumberFormat = 55 - NumberFormatID56 NumberFormat = 56 - NumberFormatID57 NumberFormat = 57 - NumberFormatID58 NumberFormat = 58 - NumberFormatID59 NumberFormat = 59 - NumberFormatID60 NumberFormat = 60 - NumberFormatID61 NumberFormat = 61 - NumberFormatID62 NumberFormat = 62 - NumberFormatID67 NumberFormat = 67 - NumberFormatID68 NumberFormat = 68 - NumberFormatID69 NumberFormat = 69 - NumberFormatID70 NumberFormat = 70 - NumberFormatID71 NumberFormat = 71 - NumberFormatID72 NumberFormat = 72 - NumberFormatID73 NumberFormat = 73 - NumberFormatID74 NumberFormat = 74 - NumberFormatID75 NumberFormat = 75 - NumberFormatID76 NumberFormat = 76 - NumberFormatID77 NumberFormat = 77 - NumberFormatID78 NumberFormat = 78 - NumberFormatID79 NumberFormat = 79 - NumberFormatID80 NumberFormat = 80 - NumberFormatID81 NumberFormat = 81 + // 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 ) @@ -1856,13 +3709,31 @@ func (value NumberFormat) Code() (string, bool) { } } +// 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" ) @@ -2081,62 +3952,161 @@ func (value NumberFormat) CodeForLanguage(language StyleLanguage) (string, bool) 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 AlignmentField = "Horizontal" - AlignmentFieldIndent AlignmentField = "Indent" + // 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 AlignmentField = "ReadingOrder" - AlignmentFieldRelativeIndent AlignmentField = "RelativeIndent" - AlignmentFieldShrinkToFit AlignmentField = "ShrinkToFit" - AlignmentFieldTextRotation AlignmentField = "TextRotation" - AlignmentFieldVertical AlignmentField = "Vertical" - AlignmentFieldWrapText AlignmentField = "WrapText" + // 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 BorderField = "Type" + // 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 FontField = "Bold" - FontFieldItalic FontField = "Italic" - FontFieldUnderline FontField = "Underline" - FontFieldFamily FontField = "Family" - FontFieldSize FontField = "Size" - FontFieldStrike FontField = "Strike" - FontFieldColor FontField = "Color" + // 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 FontField = "ColorTheme" - FontFieldColorTint FontField = "ColorTint" - FontFieldVertAlign FontField = "VertAlign" - FontFieldCharset FontField = "Charset" + // 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 FillField = "Type" - FillFieldPattern FillField = "Pattern" - FillFieldColor FillField = "Color" - FillFieldShading FillField = "Shading" + // 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" ) @@ -2144,146 +4114,494 @@ 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 StyleField = "Border" - StyleFieldFill StyleField = "Fill" - StyleFieldFont StyleField = "Font" - StyleFieldAlignment StyleField = "Alignment" - StyleFieldProtection StyleField = "Protection" - StyleFieldNumFmt StyleField = "NumFmt" + // 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 StyleField = "CustomNumFmt" - StyleFieldNegRed StyleField = "NegRed" + // 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 IndexedColor = -1 - IndexedColorIndex0 IndexedColor = 0 - IndexedColorIndex1 IndexedColor = 1 - IndexedColorIndex2 IndexedColor = 2 - IndexedColorIndex3 IndexedColor = 3 - IndexedColorIndex4 IndexedColor = 4 - IndexedColorIndex5 IndexedColor = 5 - IndexedColorIndex6 IndexedColor = 6 - IndexedColorIndex7 IndexedColor = 7 - IndexedColorIndex8 IndexedColor = 8 - IndexedColorIndex9 IndexedColor = 9 - IndexedColorIndex10 IndexedColor = 10 - IndexedColorIndex11 IndexedColor = 11 - IndexedColorIndex12 IndexedColor = 12 - IndexedColorIndex13 IndexedColor = 13 - IndexedColorIndex14 IndexedColor = 14 - IndexedColorIndex15 IndexedColor = 15 - IndexedColorIndex16 IndexedColor = 16 - IndexedColorIndex17 IndexedColor = 17 - IndexedColorIndex18 IndexedColor = 18 - IndexedColorIndex19 IndexedColor = 19 - IndexedColorIndex20 IndexedColor = 20 - IndexedColorIndex21 IndexedColor = 21 - IndexedColorIndex22 IndexedColor = 22 - IndexedColorIndex23 IndexedColor = 23 - IndexedColorIndex24 IndexedColor = 24 - IndexedColorIndex25 IndexedColor = 25 - IndexedColorIndex26 IndexedColor = 26 - IndexedColorIndex27 IndexedColor = 27 - IndexedColorIndex28 IndexedColor = 28 - IndexedColorIndex29 IndexedColor = 29 - IndexedColorIndex30 IndexedColor = 30 - IndexedColorIndex31 IndexedColor = 31 - IndexedColorIndex32 IndexedColor = 32 - IndexedColorIndex33 IndexedColor = 33 - IndexedColorIndex34 IndexedColor = 34 - IndexedColorIndex35 IndexedColor = 35 - IndexedColorIndex36 IndexedColor = 36 - IndexedColorIndex37 IndexedColor = 37 - IndexedColorIndex38 IndexedColor = 38 - IndexedColorIndex39 IndexedColor = 39 - IndexedColorIndex40 IndexedColor = 40 - IndexedColorIndex41 IndexedColor = 41 - IndexedColorIndex42 IndexedColor = 42 - IndexedColorIndex43 IndexedColor = 43 - IndexedColorIndex44 IndexedColor = 44 - IndexedColorIndex45 IndexedColor = 45 - IndexedColorIndex46 IndexedColor = 46 - IndexedColorIndex47 IndexedColor = 47 - IndexedColorIndex48 IndexedColor = 48 - IndexedColorIndex49 IndexedColor = 49 - IndexedColorIndex50 IndexedColor = 50 - IndexedColorIndex51 IndexedColor = 51 - IndexedColorIndex52 IndexedColor = 52 - IndexedColorIndex53 IndexedColor = 53 - IndexedColorIndex54 IndexedColor = 54 - IndexedColorIndex55 IndexedColor = 55 - IndexedColorIndex56 IndexedColor = 56 - IndexedColorIndex57 IndexedColor = 57 - IndexedColorIndex58 IndexedColor = 58 - IndexedColorIndex59 IndexedColor = 59 - IndexedColorIndex60 IndexedColor = 60 - IndexedColorIndex61 IndexedColor = 61 - IndexedColorIndex62 IndexedColor = 62 - IndexedColorIndex63 IndexedColor = 63 - IndexedColorIndex64 IndexedColor = 64 - IndexedColorIndex65 IndexedColor = 65 + // 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 ThemeColor = 0 - ThemeColorDark1 ThemeColor = 1 - ThemeColorLight2 ThemeColor = 2 - ThemeColorDark2 ThemeColor = 3 - ThemeColorAccent1 ThemeColor = 4 - ThemeColorAccent2 ThemeColor = 5 - ThemeColorAccent3 ThemeColor = 6 - ThemeColorAccent4 ThemeColor = 7 - ThemeColorAccent5 ThemeColor = 8 - ThemeColorAccent6 ThemeColor = 9 - ThemeColorHyperlink ThemeColor = 10 + // 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 FontCharset = 0 - FontCharsetDefault FontCharset = 1 - FontCharsetSymbol FontCharset = 2 - FontCharsetMac FontCharset = 77 - FontCharsetShiftJIS FontCharset = 128 - FontCharsetHangul FontCharset = 129 - FontCharsetJohab FontCharset = 130 - FontCharsetGB2312 FontCharset = 134 - FontCharsetBig5 FontCharset = 136 - FontCharsetGreek FontCharset = 161 - FontCharsetTurkish FontCharset = 162 + // 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 FontCharset = 177 - FontCharsetArabic FontCharset = 178 - FontCharsetBaltic FontCharset = 186 - FontCharsetRussian FontCharset = 204 - FontCharsetThai FontCharset = 222 + // 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 FontCharset = 255 + // 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. @@ -2524,25 +4842,82 @@ func StyleDefinitionFromExcelize(in excelize.Style) StyleDefinition { 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 GradientShading = 0 - GradientShadingPreset1 GradientShading = 1 - GradientShadingPreset2 GradientShading = 2 - GradientShadingPreset3 GradientShading = 3 - GradientShadingPreset4 GradientShading = 4 - GradientShadingPreset5 GradientShading = 5 - GradientShadingPreset6 GradientShading = 6 - GradientShadingPreset7 GradientShading = 7 - GradientShadingPreset8 GradientShading = 8 - GradientShadingPreset9 GradientShading = 9 + // 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 GradientShading = 17 + // 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/excelizeutil/cmd/gen-style-enums/additional.go b/excelizeutil/cmd/gen-style-enums/additional.go index d44df3b..f43f36b 100644 --- a/excelizeutil/cmd/gen-style-enums/additional.go +++ b/excelizeutil/cmd/gen-style-enums/additional.go @@ -257,7 +257,7 @@ func additionalEnums(dir string, styles *ast.File, specs []enumSpec) ([]byte, [] return nil, nil, err } } - public.WriteString("// AlignmentReadingOrder follows the OOXML readingOrder attribute.\ntype AlignmentReadingOrder uint64\nconst (AlignmentReadingOrderContext AlignmentReadingOrder = iota; AlignmentReadingOrderLeftToRight; AlignmentReadingOrderRightToLeft)\n\n") + 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) @@ -282,7 +282,12 @@ func additionalEnums(dir string, styles *ast.File, specs []enumSpec) ([]byte, [] return nil, nil, err } fmt.Fprintln(&public, "type GradientShading int\nconst (") - for i := range presets { + 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)) 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 index 4ca9ee7..2d6c88c 100644 --- a/excelizeutil/cmd/gen-style-enums/main.go +++ b/excelizeutil/cmd/gen-style-enums/main.go @@ -94,6 +94,14 @@ func generatedSources(moduleDir string) ([]byte, []byte, error) { } 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) }