From 38c080ffd2f4b55ae9bccefd581aa60952926835 Mon Sep 17 00:00:00 2001 From: Ilia Mirkin Date: Wed, 11 Dec 2024 15:56:50 -0500 Subject: [PATCH] WIP add support for new icon sets --- styles.go | 104 ++++++++++++++++++++++++++++++++++++++++++------ styles_test.go | 10 +++++ xmlWorksheet.go | 53 ++++++++++++++++++++---- 3 files changed, 147 insertions(+), 20 deletions(-) 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"`