From c2f09dab08918c9790f571921456d31cb140d33b 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 | 67 +++++++++++++++++++++++++++++++++++++++---------- styles_test.go | 10 ++++++++ xmlWorksheet.go | 25 +++++++++++++++--- 3 files changed, 86 insertions(+), 16 deletions(-) diff --git a/styles.go b/styles.go index c534dd4a2c..940dfc82aa 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 + "")). @@ -3467,7 +3499,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..3b66d817b8 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}}, + // TODO: {{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 e9b3dc5aa9..879ed7e458 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -772,13 +772,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 +798,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"`