From 9e2318cefa4ebaa7bf6b1dbc95b30ad7a32366b1 Mon Sep 17 00:00:00 2001 From: xuri Date: Tue, 10 Mar 2020 00:04:23 +0800 Subject: [PATCH] Resolve #470, export Style structs to allow create the style for cells by given JSON or structure --- excelize.go | 2 +- excelize_test.go | 40 ++++++++++- shape.go | 2 +- styles.go | 176 +++++++++++++++++++++++++---------------------- styles_test.go | 23 +++++++ xmlChart.go | 2 +- xmlDrawing.go | 4 +- xmlStyles.go | 84 ++++++++++++---------- 8 files changed, 207 insertions(+), 126 deletions(-) diff --git a/excelize.go b/excelize.go index 09621225..795120d8 100644 --- a/excelize.go +++ b/excelize.go @@ -137,7 +137,7 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error { return err } if s == 0 { - style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`) + style, _ := f.NewStyle(&Style{NumFmt: format}) _ = f.SetCellStyle(sheet, axis, axis, style) } return err diff --git a/excelize_test.go b/excelize_test.go index b78aac8e..1ce4fe95 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -580,7 +580,45 @@ func TestSetCellStyleBorder(t *testing.T) { assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) // Test set border and solid style pattern fill for a single cell. - style, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":8},{"type":"top","color":"00FF00","style":9},{"type":"bottom","color":"FFFF00","style":10},{"type":"right","color":"FF0000","style":11},{"type":"diagonalDown","color":"A020F0","style":12},{"type":"diagonalUp","color":"A020F0","style":13}],"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`) + style, err = f.NewStyle(&Style{ + Border: []Border{ + { + Type: "left", + Color: "0000FF", + Style: 8, + }, + { + Type: "top", + Color: "00FF00", + Style: 9, + }, + { + Type: "bottom", + Color: "FFFF00", + Style: 10, + }, + { + Type: "right", + Color: "FF0000", + Style: 11, + }, + { + Type: "diagonalDown", + Color: "A020F0", + Style: 12, + }, + { + Type: "diagonalUp", + Color: "A020F0", + Style: 13, + }, + }, + Fill: Fill{ + Type: "pattern", + Color: []string{"#E0EBF5"}, + Pattern: 1, + }, + }) if !assert.NoError(t, err) { t.FailNow() } diff --git a/shape.go b/shape.go index e9bdb42e..0455b220 100644 --- a/shape.go +++ b/shape.go @@ -378,7 +378,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format if len(formatSet.Paragraph) < 1 { formatSet.Paragraph = []formatShapeParagraph{ { - Font: formatFont{ + Font: Font{ Bold: false, Italic: false, Underline: "none", diff --git a/styles.go b/styles.go index caf2732b..175a17c4 100644 --- a/styles.go +++ b/styles.go @@ -1024,16 +1024,16 @@ func (f *File) styleSheetWriter() { // parseFormatStyleSet provides a function to parse the format settings of the // cells and conditional formats. -func parseFormatStyleSet(style string) (*formatStyle, error) { - format := formatStyle{ +func parseFormatStyleSet(style string) (*Style, error) { + format := Style{ DecimalPlaces: 2, } err := json.Unmarshal([]byte(style), &format) return &format, err } -// NewStyle provides a function to create style for cells by given style -// format. Note that the color field uses RGB color code. +// NewStyle provides a function to create the style for cells by given JSON or +// structure. Note that the color field uses RGB color code. // // The following shows the border styles sorted by excelize index number: // @@ -1888,18 +1888,26 @@ func parseFormatStyleSet(style string) (*formatStyle, error) { // // f := excelize.NewFile() // f.SetCellValue("Sheet1", "A6", 42920.5) -// style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) +// exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" +// style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp}) // err = f.SetCellStyle("Sheet1", "A6", "A6", style) // // Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 // -func (f *File) NewStyle(style string) (int, error) { +func (f *File) NewStyle(style interface{}) (int, error) { + var fs *Style + var err error var cellXfsID, fontID, borderID, fillID int - s := f.stylesReader() - fs, err := parseFormatStyleSet(style) - if err != nil { - return cellXfsID, err + switch v := style.(type) { + case string: + fs, err = parseFormatStyleSet(v) + if err != nil { + return cellXfsID, err + } + case *Style: + fs = v } + s := f.stylesReader() numFmtID := setNumFmt(s, fs) if fs.Font != nil { @@ -1978,34 +1986,34 @@ func (f *File) readDefaultFont() *xlsxFont { // setFont provides a function to add font style by given cell format // settings. -func (f *File) setFont(formatStyle *formatStyle) *xlsxFont { +func (f *File) setFont(style *Style) *xlsxFont { fontUnderlineType := map[string]string{"single": "single", "double": "double"} - if formatStyle.Font.Size < 1 { - formatStyle.Font.Size = 11 + if style.Font.Size < 1 { + style.Font.Size = 11 } - if formatStyle.Font.Color == "" { - formatStyle.Font.Color = "#000000" + if style.Font.Color == "" { + style.Font.Color = "#000000" } fnt := xlsxFont{ - Sz: &attrValFloat{Val: float64Ptr(formatStyle.Font.Size)}, - Color: &xlsxColor{RGB: getPaletteColor(formatStyle.Font.Color)}, - Name: &attrValString{Val: stringPtr(formatStyle.Font.Family)}, + Sz: &attrValFloat{Val: float64Ptr(style.Font.Size)}, + Color: &xlsxColor{RGB: getPaletteColor(style.Font.Color)}, + Name: &attrValString{Val: stringPtr(style.Font.Family)}, Family: &attrValInt{Val: intPtr(2)}, } - if formatStyle.Font.Bold { - fnt.B = &formatStyle.Font.Bold + if style.Font.Bold { + fnt.B = &style.Font.Bold } - if formatStyle.Font.Italic { - fnt.I = &formatStyle.Font.Italic + if style.Font.Italic { + fnt.I = &style.Font.Italic } if *fnt.Name.Val == "" { *fnt.Name.Val = f.GetDefaultFont() } - if formatStyle.Font.Strike { + if style.Font.Strike { strike := true fnt.Strike = &strike } - val, ok := fontUnderlineType[formatStyle.Font.Underline] + val, ok := fontUnderlineType[style.Font.Underline] if ok { fnt.U = &attrValString{Val: stringPtr(val)} } @@ -2014,36 +2022,36 @@ func (f *File) setFont(formatStyle *formatStyle) *xlsxFont { // setNumFmt provides a function to check if number format code in the range // of built-in values. -func setNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int { +func setNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { dp := "0." numFmtID := 164 // Default custom number format code from 164. - if formatStyle.DecimalPlaces < 0 || formatStyle.DecimalPlaces > 30 { - formatStyle.DecimalPlaces = 2 + if style.DecimalPlaces < 0 || style.DecimalPlaces > 30 { + style.DecimalPlaces = 2 } - for i := 0; i < formatStyle.DecimalPlaces; i++ { + for i := 0; i < style.DecimalPlaces; i++ { dp += "0" } - if formatStyle.CustomNumFmt != nil { - return setCustomNumFmt(style, formatStyle) + if style.CustomNumFmt != nil { + return setCustomNumFmt(styleSheet, style) } - _, ok := builtInNumFmt[formatStyle.NumFmt] + _, ok := builtInNumFmt[style.NumFmt] if !ok { - fc, currency := currencyNumFmt[formatStyle.NumFmt] + fc, currency := currencyNumFmt[style.NumFmt] if !currency { - return setLangNumFmt(style, formatStyle) + return setLangNumFmt(styleSheet, style) } fc = strings.Replace(fc, "0.00", dp, -1) - if formatStyle.NegRed { + if style.NegRed { fc = fc + ";[Red]" + fc } - if style.NumFmts != nil { - numFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1 + if styleSheet.NumFmts != nil { + numFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 nf := xlsxNumFmt{ FormatCode: fc, NumFmtID: numFmtID, } - style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf) - style.NumFmts.Count++ + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ } else { nf := xlsxNumFmt{ FormatCode: fc, @@ -2053,61 +2061,61 @@ func setNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int { NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } - style.NumFmts = &numFmts + styleSheet.NumFmts = &numFmts } return numFmtID } - return formatStyle.NumFmt + return style.NumFmt } // setCustomNumFmt provides a function to set custom number format code. -func setCustomNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int { - nf := xlsxNumFmt{FormatCode: *formatStyle.CustomNumFmt} - if style.NumFmts != nil { - nf.NumFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1 - style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf) - style.NumFmts.Count++ +func setCustomNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { + nf := xlsxNumFmt{FormatCode: *style.CustomNumFmt} + if styleSheet.NumFmts != nil { + nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ } else { nf.NumFmtID = 164 numFmts := xlsxNumFmts{ NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } - style.NumFmts = &numFmts + styleSheet.NumFmts = &numFmts } return nf.NumFmtID } // setLangNumFmt provides a function to set number format code with language. -func setLangNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int { - numFmts, ok := langNumFmt[formatStyle.Lang] +func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int { + numFmts, ok := langNumFmt[style.Lang] if !ok { return 0 } var fc string - fc, ok = numFmts[formatStyle.NumFmt] + fc, ok = numFmts[style.NumFmt] if !ok { return 0 } nf := xlsxNumFmt{FormatCode: fc} - if style.NumFmts != nil { - nf.NumFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1 - style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf) - style.NumFmts.Count++ + if styleSheet.NumFmts != nil { + nf.NumFmtID = styleSheet.NumFmts.NumFmt[len(styleSheet.NumFmts.NumFmt)-1].NumFmtID + 1 + styleSheet.NumFmts.NumFmt = append(styleSheet.NumFmts.NumFmt, &nf) + styleSheet.NumFmts.Count++ } else { - nf.NumFmtID = formatStyle.NumFmt + nf.NumFmtID = style.NumFmt numFmts := xlsxNumFmts{ NumFmt: []*xlsxNumFmt{&nf}, Count: 1, } - style.NumFmts = &numFmts + styleSheet.NumFmts = &numFmts } return nf.NumFmtID } // setFills provides a function to add fill elements in the styles.xml by // given cell format settings. -func setFills(formatStyle *formatStyle, fg bool) *xlsxFill { +func setFills(style *Style, fg bool) *xlsxFill { var patterns = []string{ "none", "solid", @@ -2138,15 +2146,15 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill { } var fill xlsxFill - switch formatStyle.Fill.Type { + switch style.Fill.Type { case "gradient": - if len(formatStyle.Fill.Color) != 2 { + if len(style.Fill.Color) != 2 { break } var gradient xlsxGradientFill - switch formatStyle.Fill.Shading { + switch style.Fill.Shading { case 0, 1, 2, 3: - gradient.Degree = variants[formatStyle.Fill.Shading] + gradient.Degree = variants[style.Fill.Shading] case 4: gradient.Type = "path" case 5: @@ -2159,7 +2167,7 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill { break } var stops []*xlsxGradientFillStop - for index, color := range formatStyle.Fill.Color { + for index, color := range style.Fill.Color { var stop xlsxGradientFillStop stop.Position = float64(index) stop.Color.RGB = getPaletteColor(color) @@ -2168,18 +2176,18 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill { gradient.Stop = stops fill.GradientFill = &gradient case "pattern": - if formatStyle.Fill.Pattern > 18 || formatStyle.Fill.Pattern < 0 { + if style.Fill.Pattern > 18 || style.Fill.Pattern < 0 { break } - if len(formatStyle.Fill.Color) < 1 { + if len(style.Fill.Color) < 1 { break } var pattern xlsxPatternFill - pattern.PatternType = patterns[formatStyle.Fill.Pattern] + pattern.PatternType = patterns[style.Fill.Pattern] if fg { - pattern.FgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0]) + pattern.FgColor.RGB = getPaletteColor(style.Fill.Color[0]) } else { - pattern.BgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0]) + pattern.BgColor.RGB = getPaletteColor(style.Fill.Color[0]) } fill.PatternFill = &pattern default: @@ -2192,36 +2200,36 @@ func setFills(formatStyle *formatStyle, fg bool) *xlsxFill { // text alignment in cells. There are a variety of choices for how text is // aligned both horizontally and vertically, as well as indentation settings, // and so on. -func setAlignment(formatStyle *formatStyle) *xlsxAlignment { +func setAlignment(style *Style) *xlsxAlignment { var alignment xlsxAlignment - if formatStyle.Alignment != nil { - alignment.Horizontal = formatStyle.Alignment.Horizontal - alignment.Indent = formatStyle.Alignment.Indent - alignment.JustifyLastLine = formatStyle.Alignment.JustifyLastLine - alignment.ReadingOrder = formatStyle.Alignment.ReadingOrder - alignment.RelativeIndent = formatStyle.Alignment.RelativeIndent - alignment.ShrinkToFit = formatStyle.Alignment.ShrinkToFit - alignment.TextRotation = formatStyle.Alignment.TextRotation - alignment.Vertical = formatStyle.Alignment.Vertical - alignment.WrapText = formatStyle.Alignment.WrapText + if style.Alignment != nil { + alignment.Horizontal = style.Alignment.Horizontal + alignment.Indent = style.Alignment.Indent + alignment.JustifyLastLine = style.Alignment.JustifyLastLine + alignment.ReadingOrder = style.Alignment.ReadingOrder + alignment.RelativeIndent = style.Alignment.RelativeIndent + alignment.ShrinkToFit = style.Alignment.ShrinkToFit + alignment.TextRotation = style.Alignment.TextRotation + alignment.Vertical = style.Alignment.Vertical + alignment.WrapText = style.Alignment.WrapText } return &alignment } // setProtection provides a function to set protection properties associated // with the cell. -func setProtection(formatStyle *formatStyle) *xlsxProtection { +func setProtection(style *Style) *xlsxProtection { var protection xlsxProtection - if formatStyle.Protection != nil { - protection.Hidden = formatStyle.Protection.Hidden - protection.Locked = formatStyle.Protection.Locked + if style.Protection != nil { + protection.Hidden = style.Protection.Hidden + protection.Locked = style.Protection.Locked } return &protection } // setBorders provides a function to add border elements in the styles.xml by // given borders format settings. -func setBorders(formatStyle *formatStyle) *xlsxBorder { +func setBorders(style *Style) *xlsxBorder { var styles = []string{ "none", "thin", @@ -2240,7 +2248,7 @@ func setBorders(formatStyle *formatStyle) *xlsxBorder { } var border xlsxBorder - for _, v := range formatStyle.Border { + for _, v := range style.Border { if 0 <= v.Style && v.Style < 14 { var color xlsxColor color.RGB = getPaletteColor(v.Color) diff --git a/styles_test.go b/styles_test.go index 4e9b4116..5a9a7715 100644 --- a/styles_test.go +++ b/styles_test.go @@ -191,6 +191,8 @@ func TestNewStyle(t *testing.T) { font := styles.Fonts.Font[fontID] assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name") assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles") + _, err = f.NewStyle(&Style{}) + assert.NoError(t, err) } func TestGetDefaultFont(t *testing.T) { @@ -207,3 +209,24 @@ func TestSetDefaultFont(t *testing.T) { assert.Equal(t, s, "Ariel", "Default font should change to Ariel") assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true) } + +func TestStylesReader(t *testing.T) { + f := NewFile() + // Test read styles with unsupport charset. + f.Styles = nil + f.XLSX["xl/styles.xml"] = MacintoshCyrillicCharset + assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader()) +} + +func TestThemeReader(t *testing.T) { + f := NewFile() + // Test read theme with unsupport charset. + f.XLSX["xl/theme/theme1.xml"] = MacintoshCyrillicCharset + assert.EqualValues(t, new(xlsxTheme), f.themeReader()) +} + +func TestSetCellStyle(t *testing.T) { + f := NewFile() + // Test set cell style on not exists worksheet. + assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN is not exist") +} diff --git a/xmlChart.go b/xmlChart.go index 8d245524..03b47a1f 100644 --- a/xmlChart.go +++ b/xmlChart.go @@ -604,7 +604,7 @@ type formatChart struct { type formatChartLegend struct { None bool `json:"none"` DeleteSeries []int `json:"delete_series"` - Font formatFont `json:"font"` + Font Font `json:"font"` Layout formatLayout `json:"layout"` Position string `json:"position"` ShowLegendEntry bool `json:"show_legend_entry"` diff --git a/xmlDrawing.go b/xmlDrawing.go index 5bb59775..2bad16a0 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -417,8 +417,8 @@ type formatShape struct { // formatShapeParagraph directly maps the format settings of the paragraph in // the shape. type formatShapeParagraph struct { - Font formatFont `json:"font"` - Text string `json:"text"` + Font Font `json:"font"` + Text string `json:"text"` } // formatShapeColor directly maps the color settings of the shape. diff --git a/xmlStyles.go b/xmlStyles.go index 0313008e..d6aa4f9f 100644 --- a/xmlStyles.go +++ b/xmlStyles.go @@ -313,8 +313,28 @@ type xlsxStyleColors struct { Color string `xml:",innerxml"` } -// formatFont directly maps the styles settings of the fonts. -type formatFont struct { +// Alignment directly maps the alignment settings of the cells. +type Alignment struct { + Horizontal string `json:"horizontal"` + Indent int `json:"indent"` + JustifyLastLine bool `json:"justify_last_line"` + ReadingOrder uint64 `json:"reading_order"` + RelativeIndent int `json:"relative_indent"` + ShrinkToFit bool `json:"shrink_to_fit"` + TextRotation int `json:"text_rotation"` + Vertical string `json:"vertical"` + WrapText bool `json:"wrap_text"` +} + +// Border directly maps the border settings of the cells. +type Border struct { + Type string `json:"type"` + Color string `json:"color"` + Style int `json:"style"` +} + +// Font directly maps the font settings of the fonts. +type Font struct { Bold bool `json:"bold"` Italic bool `json:"italic"` Underline string `json:"underline"` @@ -324,38 +344,30 @@ type formatFont struct { Color string `json:"color"` } -// formatStyle directly maps the styles settings of the cells. -type formatStyle struct { - Border []struct { - Type string `json:"type"` - Color string `json:"color"` - Style int `json:"style"` - } `json:"border"` - Fill struct { - Type string `json:"type"` - Pattern int `json:"pattern"` - Color []string `json:"color"` - Shading int `json:"shading"` - } `json:"fill"` - Font *formatFont `json:"font"` - Alignment *struct { - Horizontal string `json:"horizontal"` - Indent int `json:"indent"` - JustifyLastLine bool `json:"justify_last_line"` - ReadingOrder uint64 `json:"reading_order"` - RelativeIndent int `json:"relative_indent"` - ShrinkToFit bool `json:"shrink_to_fit"` - TextRotation int `json:"text_rotation"` - Vertical string `json:"vertical"` - WrapText bool `json:"wrap_text"` - } `json:"alignment"` - Protection *struct { - Hidden bool `json:"hidden"` - Locked bool `json:"locked"` - } `json:"protection"` - NumFmt int `json:"number_format"` - DecimalPlaces int `json:"decimal_places"` - CustomNumFmt *string `json:"custom_number_format"` - Lang string `json:"lang"` - NegRed bool `json:"negred"` +// Fill directly maps the fill settings of the cells. +type Fill struct { + Type string `json:"type"` + Pattern int `json:"pattern"` + Color []string `json:"color"` + Shading int `json:"shading"` +} + +// Protection directly maps the protection settings of the cells. +type Protection struct { + Hidden bool `json:"hidden"` + Locked bool `json:"locked"` +} + +// Style directly maps the style settings of the cells. +type Style struct { + Border []Border `json:"border"` + Fill Fill `json:"fill"` + Font *Font `json:"font"` + Alignment *Alignment `json:"alignment"` + Protection *Protection `json:"protection"` + NumFmt int `json:"number_format"` + DecimalPlaces int `json:"decimal_places"` + CustomNumFmt *string `json:"custom_number_format"` + Lang string `json:"lang"` + NegRed bool `json:"negred"` }