From 753969dc4efee833d14c2fb537200ef14849571f Mon Sep 17 00:00:00 2001 From: xuri Date: Wed, 8 Feb 2023 00:03:45 +0800 Subject: [PATCH] Support creating a conditional format with an "icon sets" rule - Improvement compatibility for the worksheet extension lists - Update unit test --- sparkline.go | 105 ++++++++++++-------------- sparkline_test.go | 24 +++--- styles.go | 183 +++++++++++++++++++++++++++++++++++++--------- styles_test.go | 19 ++++- xmlDrawing.go | 12 +++ xmlWorksheet.go | 10 ++- 6 files changed, 245 insertions(+), 108 deletions(-) diff --git a/sparkline.go b/sparkline.go index 43a827e..b9879ac 100644 --- a/sparkline.go +++ b/sparkline.go @@ -14,6 +14,7 @@ package excelize import ( "encoding/xml" "io" + "sort" "strings" ) @@ -389,15 +390,14 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup { // Axis | Show sparkline axis func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error { var ( - err error - ws *xlsxWorksheet - sparkType string - sparkTypes map[string]string - specifiedSparkTypes string - ok bool - group *xlsxX14SparklineGroup - groups *xlsxX14SparklineGroups - sparklineGroupsBytes, extBytes []byte + err error + ws *xlsxWorksheet + sparkType string + sparkTypes map[string]string + specifiedSparkTypes string + ok bool + group *xlsxX14SparklineGroup + groups *xlsxX14SparklineGroups ) // parameter validation @@ -434,25 +434,8 @@ func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error { group.RightToLeft = opts.Reverse } f.addSparkline(opts, group) - if ws.ExtLst.Ext != "" { // append mode ext - if err = f.appendSparkline(ws, group, groups); err != nil { - return err - } - } else { - groups = &xlsxX14SparklineGroups{ - XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, - SparklineGroups: []*xlsxX14SparklineGroup{group}, - } - if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { - return err - } - if extBytes, err = xml.Marshal(&xlsxWorksheetExt{ - URI: ExtURISparklineGroups, - Content: string(sparklineGroupsBytes), - }); err != nil { - return err - } - ws.ExtLst.Ext = string(extBytes) + if err = f.appendSparkline(ws, group, groups); err != nil { + return err } f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) return err @@ -504,42 +487,50 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, var ( err error idx int - decodeExtLst *decodeWorksheetExt + appendMode bool + decodeExtLst = new(decodeWorksheetExt) decodeSparklineGroups *decodeX14SparklineGroups ext *xlsxWorksheetExt sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte ) - decodeExtLst = new(decodeWorksheetExt) - if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). - Decode(decodeExtLst); err != nil && err != io.EOF { - return err - } - for idx, ext = range decodeExtLst.Ext { - if ext.URI == ExtURISparklineGroups { - decodeSparklineGroups = new(decodeX14SparklineGroups) - if err = f.xmlNewDecoder(strings.NewReader(ext.Content)). - Decode(decodeSparklineGroups); err != nil && err != io.EOF { - return err - } - if sparklineGroupBytes, err = xml.Marshal(group); err != nil { - return err - } - if groups == nil { - groups = &xlsxX14SparklineGroups{} - } - groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value - groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes) - if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { - return err + sparklineGroupBytes, _ = xml.Marshal(group) + if ws.ExtLst != nil { // append mode ext + if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). + Decode(decodeExtLst); err != nil && err != io.EOF { + return err + } + for idx, ext = range decodeExtLst.Ext { + if ext.URI == ExtURISparklineGroups { + decodeSparklineGroups = new(decodeX14SparklineGroups) + if err = f.xmlNewDecoder(strings.NewReader(ext.Content)). + Decode(decodeSparklineGroups); err != nil && err != io.EOF { + return err + } + if groups == nil { + groups = &xlsxX14SparklineGroups{} + } + groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value + groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes) + sparklineGroupsBytes, _ = xml.Marshal(groups) + decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) + appendMode = true } - decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) } } - if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil { - return err - } - ws.ExtLst = &xlsxExtLst{ - Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), ""), + if !appendMode { + sparklineGroupsBytes, _ = xml.Marshal(&xlsxX14SparklineGroups{ + XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, + SparklineGroups: []*xlsxX14SparklineGroup{group}, + }) + decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{ + URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes), + }) } + sort.Slice(decodeExtLst.Ext, func(i, j int) bool { + return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) < + inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false) + }) + extLstBytes, err = xml.Marshal(decodeExtLst) + ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } diff --git a/sparkline_test.go b/sparkline_test.go index e6bca25..b1d3d18 100644 --- a/sparkline_test.go +++ b/sparkline_test.go @@ -264,23 +264,23 @@ func TestAddSparkline(t *testing.T) { Range: []string{"Sheet2!A3:E3"}, Style: -1, }), ErrSparklineStyle.Error()) - + // Test creating a conditional format with existing extension lists ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) - ws.(*xlsxWorksheet).ExtLst.Ext = ` - - - - - - - - ` + ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: ` + + `} + assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{ + Location: []string{"A3"}, + Range: []string{"Sheet3!A2:J2"}, + Type: "column", + })) + // Test creating a conditional format with invalid extension list characters + ws.(*xlsxWorksheet).ExtLst.Ext = `` assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{ Location: []string{"A2"}, Range: []string{"Sheet3!A1:J1"}, - }), "XML syntax error on line 6: element closed by ") + }), "XML syntax error on line 1: element closed by ") } func TestAppendSparkline(t *testing.T) { diff --git a/styles.go b/styles.go index b5752fc..78083f2 100644 --- a/styles.go +++ b/styles.go @@ -18,6 +18,7 @@ import ( "io" "math" "reflect" + "sort" "strconv" "strings" ) @@ -807,6 +808,7 @@ var validType = map[string]string{ "3_color_scale": "3_color_scale", "data_bar": "dataBar", "formula": "expression", + "iconSet": "iconSet", } // criteriaType defined the list of valid criteria types. @@ -2880,7 +2882,14 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // | MaxType // | MinValue // | MaxValue +// | BarBorderColor // | BarColor +// | BarDirection +// | BarOnly +// | BarSolid +// iconSet | IconStyle +// | ReverseIcons +// | IconsOnly // formula | Criteria // // The 'Criteria' parameter is used to set the criteria by which the cell data @@ -3037,7 +3046,8 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // }, // ) // -// type: duplicate - The duplicate type is used to highlight duplicate cells in a range: +// type: duplicate - The duplicate type is used to highlight duplicate cells in +// a range: // // // Highlight cells rules: Duplicate Values... // err := f.SetConditionalFormat("Sheet1", "A1:A10", @@ -3055,7 +3065,8 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // }, // ) // -// type: top - The top type is used to specify the top n values by number or percentage in a range: +// type: top - The top type is used to specify the top n values by number or +// percentage in a range: // // // Top/Bottom rules: Top 10. // err := f.SetConditionalFormat("Sheet1", "H1:H10", @@ -3129,7 +3140,10 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // style conditional format. // -// MinType - The MinType and MaxType properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The MidType is available for 3_color_scale. The properties are used as follows: +// MinType - The MinType and MaxType properties are available when the +// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. +// The MidType is available for 3_color_scale. The properties are used as +// follows: // // // Data Bars: Gradient Fill. // err := f.SetConditionalFormat("Sheet1", "K1:K10", @@ -3194,11 +3208,41 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // BarBorderColor - Used for sets the color for the border line of a data bar, // this is only visible in Excel 2010 and later. // -// BarOnly - Used for displays a bar data but not the data in the cells. +// BarDirection - sets the direction for data bars. The available options are: +// +// context - Data bar direction is set by spreadsheet application based on the context of the data displayed. +// leftToRight - Data bar direction is from right to left. +// rightToLeft - Data bar direction is from left to right. +// +// BarOnly - Used for set displays a bar data but not the data in the cells. // // BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this // is only visible in Excel 2010 and later. // +// IconStyle - The available options are: +// +// 3Arrows +// 3ArrowsGray +// 3Flags +// 3Signs +// 3Symbols +// 3Symbols2 +// 3TrafficLights1 +// 3TrafficLights2 +// 4Arrows +// 4ArrowsGray +// 4Rating +// 4RedToBlack +// 4TrafficLights +// 5Arrows +// 5ArrowsGray +// 5Quarters +// 5Rating +// +// ReverseIcons - Used for set reversed icons sets. +// +// IconsOnly - Used for set displayed without the cell value. +// // StopIfTrue - used to set the "stop if true" feature of a conditional // formatting rule when more than one rule is applied to a cell or a range of // cells. When this parameter is set then subsequent rules are not evaluated @@ -3214,6 +3258,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo "3_color_scale": drawCondFmtColorScale, "dataBar": drawCondFmtDataBar, "expression": drawCondFmtExp, + "iconSet": drawCondFmtIconSet, } ws, err := f.workSheetReader(sheet) @@ -3235,10 +3280,13 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo if ok { // Check for valid criteria types. ct, ok = criteriaType[v.Criteria] - if ok || vt == "expression" { + if ok || vt == "expression" || vt == "iconSet" { drawFunc, ok := drawContFmtFunc[vt] if ok { rule, x14rule := drawFunc(p, ct, GUID, &v) + if rule == nil { + return ErrParameterInvalid + } if x14rule != nil { if err = f.appendCfRule(ws, x14rule); err != nil { return err @@ -3261,16 +3309,19 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo // appendCfRule provides a function to append rules to conditional formatting. func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error { var ( - err error - idx int - decodeExtLst *decodeWorksheetExt - condFmts *xlsxX14ConditionalFormattings - decodeCondFmts *decodeX14ConditionalFormattings - ext *xlsxWorksheetExt - condFmtBytes, condFmtsBytes, extLstBytes, extBytes []byte + err error + idx int + appendMode bool + decodeExtLst = new(decodeWorksheetExt) + condFmts *xlsxX14ConditionalFormattings + decodeCondFmts *decodeX14ConditionalFormattings + ext *xlsxWorksheetExt + condFmtBytes, condFmtsBytes, extLstBytes []byte ) + condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ + {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}}, + }) if ws.ExtLst != nil { // append mode ext - decodeExtLst = new(decodeWorksheetExt) if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). Decode(decodeExtLst); err != nil && err != io.EOF { return err @@ -3279,35 +3330,28 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts = new(decodeX14ConditionalFormattings) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) - condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ - { - XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, - CfRule: []*xlsxX14CfRule{rule}, - }, - }) if condFmts == nil { condFmts = &xlsxX14ConditionalFormattings{} } condFmts.Content = decodeCondFmts.Content + string(condFmtBytes) condFmtsBytes, _ = xml.Marshal(condFmts) decodeExtLst.Ext[idx].Content = string(condFmtsBytes) + appendMode = true } } - extLstBytes, _ = xml.Marshal(decodeExtLst) - ws.ExtLst = &xlsxExtLst{ - Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), ""), - } - return err } - condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ - {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}}, - }) - condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) - extBytes, err = xml.Marshal(&xlsxWorksheetExt{ - URI: ExtURIConditionalFormattings, - Content: string(condFmtsBytes), + if !appendMode { + condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) + decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{ + URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes), + }) + } + sort.Slice(decodeExtLst.Ext, func(i, j int) bool { + return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) < + inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false) }) - ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extBytes), ""), "")} + extLstBytes, err = xml.Marshal(decodeExtLst) + ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} return err } @@ -3424,6 +3468,7 @@ func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO for _, rule := range condFmt.CfRule { if rule.DataBar != nil { format.BarSolid = !rule.DataBar.Gradient + format.BarDirection = rule.DataBar.Direction if rule.DataBar.BorderColor != nil { format.BarBorderColor = "#" + strings.TrimPrefix(strings.ToUpper(rule.DataBar.BorderColor.RGB), "FF") } @@ -3466,6 +3511,20 @@ func extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptio return format } +// extractCondFmtIconSet provides a function to extract conditional format +// settings for icon sets by given conditional formatting rule. +func extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions { + format := ConditionalFormatOptions{Type: "iconSet"} + if c.IconSet != nil { + if c.IconSet.ShowValue != nil { + format.IconsOnly = !*c.IconSet.ShowValue + } + format.IconStyle = c.IconSet.IconSet + format.ReverseIcons = c.IconSet.Reverse + } + return format +} + // GetConditionalFormats returns conditional format settings by given worksheet // name. func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) { @@ -3478,6 +3537,7 @@ func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalForm "colorScale": extractCondFmtColorScale, "dataBar": extractCondFmtDataBar, "expression": extractCondFmtExp, + "iconSet": extractCondFmtIconSet, } conditionalFormats := make(map[string][]ConditionalFormatOptions) @@ -3622,19 +3682,22 @@ func drawCondFmtColorScale(p int, ct, GUID string, format *ConditionalFormatOpti func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { var x14CfRule *xlsxX14CfRule var extLst *xlsxExtLst - if format.BarSolid { + if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" { extLst = &xlsxExtLst{Ext: fmt.Sprintf(`%s`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)} x14CfRule = &xlsxX14CfRule{ Type: validType[format.Type], ID: GUID, DataBar: &xlsx14DataBar{ MaxLength: 100, + Border: format.BarBorderColor != "", + Gradient: !format.BarSolid, + Direction: format.BarDirection, Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}}, NegativeFillColor: &xlsxColor{RGB: "FFFF0000"}, AxisColor: &xlsxColor{RGB: "FFFF0000"}, }, } - if format.BarBorderColor != "" { + if x14CfRule.DataBar.Border { x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)} } } @@ -3663,6 +3726,58 @@ func drawCondFmtExp(p int, ct, GUID string, format *ConditionalFormatOptions) (* }, nil } +// drawCondFmtIconSet provides a function to create conditional formatting rule +// for icon set by given priority, criteria type and format settings. +func drawCondFmtIconSet(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { + cfvo3 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ + {Type: "percent", Val: "0"}, + {Type: "percent", Val: "33"}, + {Type: "percent", Val: "67"}, + }}} + cfvo4 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ + {Type: "percent", Val: "0"}, + {Type: "percent", Val: "25"}, + {Type: "percent", Val: "50"}, + {Type: "percent", Val: "75"}, + }}} + cfvo5 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{ + {Type: "percent", Val: "0"}, + {Type: "percent", Val: "20"}, + {Type: "percent", Val: "40"}, + {Type: "percent", Val: "60"}, + {Type: "percent", Val: "80"}, + }}} + presets := map[string]*xlsxCfRule{ + "3Arrows": cfvo3, + "3ArrowsGray": cfvo3, + "3Flags": cfvo3, + "3Signs": cfvo3, + "3Symbols": cfvo3, + "3Symbols2": cfvo3, + "3TrafficLights1": cfvo3, + "3TrafficLights2": cfvo3, + "4Arrows": cfvo4, + "4ArrowsGray": cfvo4, + "4Rating": cfvo4, + "4RedToBlack": cfvo4, + "4TrafficLights": cfvo4, + "5Arrows": cfvo5, + "5ArrowsGray": cfvo5, + "5Quarters": cfvo5, + "5Rating": cfvo5, + } + cfRule, ok := presets[format.IconStyle] + if !ok { + return nil, nil + } + cfRule.Priority = p + 1 + cfRule.IconSet.IconSet = format.IconStyle + cfRule.IconSet.Reverse = format.ReverseIcons + cfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly) + cfRule.Type = format.Type + return cfRule, nil +} + // getPaletteColor provides a function to convert the RBG color by given // string. func getPaletteColor(color string) string { diff --git a/styles_test.go b/styles_test.go index cd90a3c..864f14f 100644 --- a/styles_test.go +++ b/styles_test.go @@ -176,11 +176,22 @@ func TestSetConditionalFormat(t *testing.T) { for _, ref := range []string{"A1:A2", "B1:B2"} { assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts)) } - // Test creating a conditional format with invalid extension list characters + f = NewFile() + // Test creating a conditional format with existing extension lists ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) - ws.(*xlsxWorksheet).ExtLst.Ext = "" + ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: ` + + `} + assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}})) + f = NewFile() + // Test creating a conditional format with invalid extension list characters + ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: ""} assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element closed by ") + // Test creating a conditional format with invalid icon set style + assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "iconSet", IconStyle: "unknown"}}), ErrParameterInvalid.Error()) } func TestGetConditionalFormats(t *testing.T) { @@ -194,8 +205,10 @@ func TestGetConditionalFormats(t *testing.T) { {{Type: "unique", Format: 1, Criteria: "="}}, {{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}}, {{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}}, - {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", BarBorderColor: "#0000FF", BarOnly: true, BarSolid: true, StopIfTrue: true}}, + {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}}, + {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}}, {{Type: "formula", Format: 1, Criteria: "="}}, + {{Type: "iconSet", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}}, } { f := NewFile() err := f.SetConditionalFormat("Sheet1", "A1:A2", format) diff --git a/xmlDrawing.go b/xmlDrawing.go index 56ba3eb..cc9585a 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -107,6 +107,18 @@ const ( ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" ) +// extensionURIPriority is the priority of URI in the extension lists. +var extensionURIPriority = []string{ + ExtURIConditionalFormattings, + ExtURIDataValidations, + ExtURISparklineGroups, + ExtURISlicerListX14, + ExtURIProtectedRanges, + ExtURIIgnoredErrors, + ExtURIWebExtensions, + ExtURITimelineRefs, +} + // Excel specifications and limits const ( MaxCellStyles = 65430 diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 07000bd..0974733 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -777,8 +777,10 @@ type decodeX14DataBar struct { XNLName xml.Name `xml:"dataBar"` MaxLength int `xml:"maxLength,attr"` MinLength int `xml:"minLength,attr"` + Border bool `xml:"border,attr,omitempty"` Gradient bool `xml:"gradient,attr"` ShowValue bool `xml:"showValue,attr,omitempty"` + Direction string `xml:"direction,attr,omitempty"` Cfvo []*xlsxCfvo `xml:"cfvo"` BorderColor *xlsxColor `xml:"borderColor"` NegativeFillColor *xlsxColor `xml:"negativeFillColor"` @@ -801,7 +803,6 @@ type xlsxX14ConditionalFormatting struct { // xlsxX14CfRule directly maps the cfRule element. type xlsxX14CfRule struct { - XNLName xml.Name `xml:"x14:cfRule"` Type string `xml:"type,attr,omitempty"` ID string `xml:"id,attr,omitempty"` DataBar *xlsx14DataBar `xml:"x14:dataBar"` @@ -809,11 +810,12 @@ type xlsxX14CfRule struct { // xlsx14DataBar directly maps the dataBar element. type xlsx14DataBar struct { - XNLName xml.Name `xml:"x14:dataBar"` MaxLength int `xml:"maxLength,attr"` MinLength int `xml:"minLength,attr"` + Border bool `xml:"border,attr"` Gradient bool `xml:"gradient,attr"` ShowValue bool `xml:"showValue,attr,omitempty"` + Direction string `xml:"direction,attr,omitempty"` Cfvo []*xlsxCfvo `xml:"x14:cfvo"` BorderColor *xlsxColor `xml:"x14:borderColor"` NegativeFillColor *xlsxColor `xml:"x14:negativeFillColor"` @@ -942,8 +944,12 @@ type ConditionalFormatOptions struct { MaxLength string BarColor string BarBorderColor string + BarDirection string BarOnly bool BarSolid bool + IconStyle string + ReverseIcons bool + IconsOnly bool StopIfTrue bool }