diff --git a/styles.go b/styles.go
index 3cab1e8a59..bfca4e7eb7 100644
--- a/styles.go
+++ b/styles.go
@@ -1374,6 +1374,26 @@ var (
"5Quarters": cfvo5,
"5Rating": cfvo5,
}
+
+ // cfvo3 defined the icon set conditional formatting rules.
+ x14cfvo3 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
+ {Type: "percent", Val: "0"},
+ {Type: "percent", Val: "33"},
+ {Type: "percent", Val: "67"},
+ }}}
+ // cfvo5 defined the icon set conditional formatting rules.
+ x14cfvo5 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
+ {Type: "percent", Val: "0"},
+ {Type: "percent", Val: "20"},
+ {Type: "percent", Val: "40"},
+ {Type: "percent", Val: "60"},
+ {Type: "percent", Val: "80"},
+ }}}
+ condFmtNewIconSetPresets = map[string]*xlsxX14CfRule{
+ "3Stars": x14cfvo3,
+ "3Triangles": x14cfvo3,
+ "5Boxes": x14cfvo5,
+ }
)
// colorChoice returns a hex color code from the actual color values.
@@ -2764,10 +2784,12 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
// 3ArrowsGray
// 3Flags
// 3Signs
+// 3Stars
// 3Symbols
// 3Symbols2
// 3TrafficLights1
// 3TrafficLights2
+// 3Triangles
// 4Arrows
// 4ArrowsGray
// 4Rating
@@ -2775,6 +2797,7 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
// 4TrafficLights
// 5Arrows
// 5ArrowsGray
+// 5Boxes
// 5Quarters
// 5Rating
//
@@ -2802,6 +2825,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
}
var (
cfRule []*xlsxCfRule
+ x14CfRule []*xlsxX14CfRule
noCriteriaTypes = []string{
"containsBlanks",
"notContainsBlanks",
@@ -2825,16 +2849,15 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
priority := rules + i
rule, x14rule := drawFunc(priority, ct, mastCell,
fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), priority), &opt)
- if rule == nil {
+ if rule == nil && x14rule == nil {
return ErrParameterInvalid
}
if x14rule != nil {
- if err = f.appendCfRule(ws, x14rule); err != nil {
- return err
- }
- f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
+ x14CfRule = append(x14CfRule, x14rule)
+ }
+ if rule != nil {
+ cfRule = append(cfRule, rule)
}
- cfRule = append(cfRule, rule)
continue
}
}
@@ -2843,10 +2866,19 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
return ErrParameterInvalid
}
- ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
- SQRef: SQRef,
- CfRule: cfRule,
- })
+ if len(cfRule) > 0 {
+ ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
+ SQRef: SQRef,
+ CfRule: cfRule,
+ })
+ }
+
+ if len(x14CfRule) > 0 {
+ if err = f.appendCfRule(ws, x14CfRule, SQRef); err != nil {
+ return err
+ }
+ f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
+ }
return err
}
@@ -2892,7 +2924,7 @@ func prepareConditionalFormatRange(rangeRef string) (string, string, error) {
}
// appendCfRule provides a function to append rules to conditional formatting.
-func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
+func (f *File) appendCfRule(ws *xlsxWorksheet, rules []*xlsxX14CfRule, SQRef string) error {
var (
err error
idx int
@@ -2904,7 +2936,7 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
condFmtBytes, condFmtsBytes, extLstBytes []byte
)
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{
- {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}},
+ {XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: rules, SQRef: SQRef},
})
if ws.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
@@ -3155,6 +3187,20 @@ func (f *File) extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalF
return format
}
+// extractCondFmtIconSetRule provides a function to extract conditional format
+// settings for icon set by given conditional formatting rule extension list.
+func (f *File) extractCondFmtIconSetRule(c *decodeX14CfRule) ConditionalFormatOptions {
+ format := ConditionalFormatOptions{Type: "icon_set"}
+ 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
+}
+
// extractCondFmtIconSet provides a function to extract conditional format
// settings for icon sets by given conditional formatting rule.
func (f *File) extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
@@ -3186,6 +3232,29 @@ func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalForm
}
conditionalFormats[cf.SQRef] = opts
}
+ if ws.ExtLst != nil {
+ decodeExtLst := new(decodeExtLst)
+ if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")).
+ Decode(decodeExtLst); err != nil && err != io.EOF {
+ return conditionalFormats, err
+ }
+ for _, ext := range decodeExtLst.Ext {
+ if ext.URI == ExtURIConditionalFormattings {
+ decodeCondFmts := new(decodeX14ConditionalFormattingRules)
+ _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
+ for _, condFmt := range decodeCondFmts.CondFmt {
+ var opts []ConditionalFormatOptions
+ for _, rule := range condFmt.CfRule {
+ if rule.Type == "iconSet" {
+ opts = append(opts, f.extractCondFmtIconSetRule(rule))
+ }
+ }
+ conditionalFormats[condFmt.SQRef] = append(conditionalFormats[condFmt.SQRef], opts...)
+ }
+ }
+ }
+ }
+
return conditionalFormats, err
}
@@ -3467,7 +3536,16 @@ func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatO
func drawCondFmtIconSet(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
cfRule, ok := condFmtIconSetPresets[format.IconStyle]
if !ok {
- return nil, nil
+ x14CfRule, ok := condFmtNewIconSetPresets[format.IconStyle]
+ if !ok {
+ return nil, nil
+ }
+ x14CfRule.Priority = p + 1
+ x14CfRule.IconSet.IconSet = format.IconStyle
+ x14CfRule.IconSet.Reverse = format.ReverseIcons
+ x14CfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly)
+ x14CfRule.Type = validType[format.Type]
+ return nil, x14CfRule
}
cfRule.Priority = p + 1
cfRule.IconSet.IconSet = format.IconStyle
diff --git a/styles_test.go b/styles_test.go
index 1b5d3a254e..0311d6c485 100644
--- a/styles_test.go
+++ b/styles_test.go
@@ -177,6 +177,15 @@ 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 a "new" icon set
+ f = NewFile()
+ condFmts = []ConditionalFormatOptions{
+ {Type: "icon_set", IconStyle: "3Triangles"},
+ }
+ for _, ref := range []string{"A1:A2", "B1:B2"} {
+ assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
+ }
+
f = NewFile()
// Test creating a conditional format without cell reference
assert.Equal(t, ErrParameterRequired, f.SetConditionalFormat("Sheet1", "", nil))
@@ -274,6 +283,7 @@ func TestGetConditionalFormats(t *testing.T) {
{{Type: "errors", Format: intPtr(1)}},
{{Type: "no_errors", Format: intPtr(1)}},
{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
+ {{Type: "icon_set", IconStyle: "3Triangles", ReverseIcons: true, IconsOnly: true}},
} {
f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A2:A1,B:B,2:2", format)
diff --git a/xmlWorksheet.go b/xmlWorksheet.go
index 666b13ba84..84f861502b 100644
--- a/xmlWorksheet.go
+++ b/xmlWorksheet.go
@@ -735,14 +735,17 @@ type decodeX14ConditionalFormattingRules struct {
type decodeX14ConditionalFormatting struct {
XMLName xml.Name `xml:"conditionalFormatting"`
CfRule []*decodeX14CfRule `xml:"cfRule"`
+ SQRef string `xml:"sqref"`
}
// decodeX14CfRule directly maps the cfRule element.
type decodeX14CfRule struct {
- XMLName xml.Name `xml:"cfRule"`
- Type string `xml:"type,attr,omitempty"`
- ID string `xml:"id,attr,omitempty"`
- DataBar *decodeX14DataBar `xml:"dataBar"`
+ XMLName xml.Name `xml:"cfRule"`
+ Type string `xml:"type,attr,omitempty"`
+ ID string `xml:"id,attr,omitempty"`
+ Priority int `xml:"priority,attr,omitempty"`
+ DataBar *decodeX14DataBar `xml:"dataBar"`
+ IconSet *decodeX14IconSet `xml:"iconSet"`
}
// decodeX14DataBar directly maps the dataBar element.
@@ -760,6 +763,23 @@ type decodeX14DataBar struct {
AxisColor *xlsxColor `xml:"axisColor"`
}
+// decodeX14IconSet directly maps the iconSet element.
+type decodeX14IconSet struct {
+ XMLName xml.Name `xml:"iconSet"`
+ Cfvo []*decodeX14Cfvo `xml:"cfvo"`
+ IconSet string `xml:"iconSet,attr,omitempty"`
+ ShowValue *bool `xml:"showValue,attr"`
+ Reverse bool `xml:"reverse,attr,omitempty"`
+}
+
+// decodeX14Cfvo directly maps the cfvo element.
+type decodeX14Cfvo struct {
+ XMLName xml.Name `xml:"cfvo"`
+ Gte bool `xml:"gte,attr,omitempty"`
+ Type string `xml:"type,attr,omitempty"`
+ Val string `xml:"f"`
+}
+
// xlsxX14ConditionalFormattings directly maps the conditionalFormattings
// element.
type xlsxX14ConditionalFormattings struct {
@@ -772,13 +792,16 @@ type xlsxX14ConditionalFormatting struct {
XMLName xml.Name `xml:"x14:conditionalFormatting"`
XMLNSXM string `xml:"xmlns:xm,attr"`
CfRule []*xlsxX14CfRule `xml:"x14:cfRule"`
+ SQRef string `xml:"xm:sqref"`
}
// xlsxX14CfRule directly maps the cfRule element.
type xlsxX14CfRule struct {
- Type string `xml:"type,attr,omitempty"`
- ID string `xml:"id,attr,omitempty"`
- DataBar *xlsx14DataBar `xml:"x14:dataBar"`
+ Type string `xml:"type,attr,omitempty"`
+ ID string `xml:"id,attr,omitempty"`
+ Priority int `xml:"priority,attr,omitempty"`
+ DataBar *xlsx14DataBar `xml:"x14:dataBar"`
+ IconSet *xlsx14IconSet `xml:"x14:iconSet"`
}
// xlsx14DataBar directly maps the dataBar element.
@@ -795,6 +818,22 @@ type xlsx14DataBar struct {
AxisColor *xlsxColor `xml:"x14:axisColor"`
}
+// xlsxIconSet (Icon Set) describes an icon set conditional formatting rule.
+type xlsx14IconSet struct {
+ Cfvo []*xlsx14Cfvo `xml:"x14:cfvo"`
+ IconSet string `xml:"iconSet,attr,omitempty"`
+ ShowValue *bool `xml:"showValue,attr"`
+ Reverse bool `xml:"reverse,attr,omitempty"`
+}
+
+// cfvo (Conditional Format Value Object) describes the values of the
+// interpolation points in a gradient scale.
+type xlsx14Cfvo struct {
+ Gte bool `xml:"gte,attr,omitempty"`
+ Type string `xml:"type,attr,omitempty"`
+ Val string `xml:"xm:f"`
+}
+
// xlsxX14SparklineGroups directly maps the sparklineGroups element.
type xlsxX14SparklineGroups struct {
XMLName xml.Name `xml:"x14:sparklineGroups"`