Skip to content

Commit 38c080f

Browse files
committed
WIP add support for new icon sets
1 parent 8e04909 commit 38c080f

File tree

3 files changed

+147
-20
lines changed

3 files changed

+147
-20
lines changed

styles.go

+91-13
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,26 @@ var (
13741374
"5Quarters": cfvo5,
13751375
"5Rating": cfvo5,
13761376
}
1377+
1378+
// cfvo3 defined the icon set conditional formatting rules.
1379+
x14cfvo3 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
1380+
{Type: "percent", Val: "0"},
1381+
{Type: "percent", Val: "33"},
1382+
{Type: "percent", Val: "67"},
1383+
}}}
1384+
// cfvo5 defined the icon set conditional formatting rules.
1385+
x14cfvo5 = &xlsxX14CfRule{IconSet: &xlsx14IconSet{Cfvo: []*xlsx14Cfvo{
1386+
{Type: "percent", Val: "0"},
1387+
{Type: "percent", Val: "20"},
1388+
{Type: "percent", Val: "40"},
1389+
{Type: "percent", Val: "60"},
1390+
{Type: "percent", Val: "80"},
1391+
}}}
1392+
condFmtNewIconSetPresets = map[string]*xlsxX14CfRule{
1393+
"3Stars": x14cfvo3,
1394+
"3Triangles": x14cfvo3,
1395+
"5Boxes": x14cfvo5,
1396+
}
13771397
)
13781398

13791399
// colorChoice returns a hex color code from the actual color values.
@@ -2764,17 +2784,20 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
27642784
// 3ArrowsGray
27652785
// 3Flags
27662786
// 3Signs
2787+
// 3Stars
27672788
// 3Symbols
27682789
// 3Symbols2
27692790
// 3TrafficLights1
27702791
// 3TrafficLights2
2792+
// 3Triangles
27712793
// 4Arrows
27722794
// 4ArrowsGray
27732795
// 4Rating
27742796
// 4RedToBlack
27752797
// 4TrafficLights
27762798
// 5Arrows
27772799
// 5ArrowsGray
2800+
// 5Boxes
27782801
// 5Quarters
27792802
// 5Rating
27802803
//
@@ -2802,6 +2825,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
28022825
}
28032826
var (
28042827
cfRule []*xlsxCfRule
2828+
x14CfRule []*xlsxX14CfRule
28052829
noCriteriaTypes = []string{
28062830
"containsBlanks",
28072831
"notContainsBlanks",
@@ -2825,16 +2849,15 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
28252849
priority := rules + i
28262850
rule, x14rule := drawFunc(priority, ct, mastCell,
28272851
fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), priority), &opt)
2828-
if rule == nil {
2852+
if rule == nil && x14rule == nil {
28292853
return ErrParameterInvalid
28302854
}
28312855
if x14rule != nil {
2832-
if err = f.appendCfRule(ws, x14rule); err != nil {
2833-
return err
2834-
}
2835-
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
2856+
x14CfRule = append(x14CfRule, x14rule)
2857+
}
2858+
if rule != nil {
2859+
cfRule = append(cfRule, rule)
28362860
}
2837-
cfRule = append(cfRule, rule)
28382861
continue
28392862
}
28402863
}
@@ -2843,10 +2866,19 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
28432866
return ErrParameterInvalid
28442867
}
28452868

2846-
ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
2847-
SQRef: SQRef,
2848-
CfRule: cfRule,
2849-
})
2869+
if len(cfRule) > 0 {
2870+
ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
2871+
SQRef: SQRef,
2872+
CfRule: cfRule,
2873+
})
2874+
}
2875+
2876+
if len(x14CfRule) > 0 {
2877+
if err = f.appendCfRule(ws, x14CfRule, SQRef); err != nil {
2878+
return err
2879+
}
2880+
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
2881+
}
28502882
return err
28512883
}
28522884

@@ -2892,7 +2924,7 @@ func prepareConditionalFormatRange(rangeRef string) (string, string, error) {
28922924
}
28932925

28942926
// appendCfRule provides a function to append rules to conditional formatting.
2895-
func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
2927+
func (f *File) appendCfRule(ws *xlsxWorksheet, rules []*xlsxX14CfRule, SQRef string) error {
28962928
var (
28972929
err error
28982930
idx int
@@ -2904,7 +2936,7 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
29042936
condFmtBytes, condFmtsBytes, extLstBytes []byte
29052937
)
29062938
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{
2907-
{XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}},
2939+
{XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: rules, SQRef: SQRef},
29082940
})
29092941
if ws.ExtLst != nil { // append mode ext
29102942
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
@@ -3155,6 +3187,20 @@ func (f *File) extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalF
31553187
return format
31563188
}
31573189

3190+
// extractCondFmtIconSetRule provides a function to extract conditional format
3191+
// settings for icon set by given conditional formatting rule extension list.
3192+
func (f *File) extractCondFmtIconSetRule(c *decodeX14CfRule) ConditionalFormatOptions {
3193+
format := ConditionalFormatOptions{Type: "icon_set"}
3194+
if c.IconSet != nil {
3195+
if c.IconSet.ShowValue != nil {
3196+
format.IconsOnly = !*c.IconSet.ShowValue
3197+
}
3198+
format.IconStyle = c.IconSet.IconSet
3199+
format.ReverseIcons = c.IconSet.Reverse
3200+
}
3201+
return format
3202+
}
3203+
31583204
// extractCondFmtIconSet provides a function to extract conditional format
31593205
// settings for icon sets by given conditional formatting rule.
31603206
func (f *File) extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
@@ -3186,6 +3232,29 @@ func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalForm
31863232
}
31873233
conditionalFormats[cf.SQRef] = opts
31883234
}
3235+
if ws.ExtLst != nil {
3236+
decodeExtLst := new(decodeExtLst)
3237+
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
3238+
Decode(decodeExtLst); err != nil && err != io.EOF {
3239+
return conditionalFormats, err
3240+
}
3241+
for _, ext := range decodeExtLst.Ext {
3242+
if ext.URI == ExtURIConditionalFormattings {
3243+
decodeCondFmts := new(decodeX14ConditionalFormattingRules)
3244+
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
3245+
for _, condFmt := range decodeCondFmts.CondFmt {
3246+
var opts []ConditionalFormatOptions
3247+
for _, rule := range condFmt.CfRule {
3248+
if rule.Type == "iconSet" {
3249+
opts = append(opts, f.extractCondFmtIconSetRule(rule))
3250+
}
3251+
}
3252+
conditionalFormats[condFmt.SQRef] = append(conditionalFormats[condFmt.SQRef], opts...)
3253+
}
3254+
}
3255+
}
3256+
}
3257+
31893258
return conditionalFormats, err
31903259
}
31913260

@@ -3467,7 +3536,16 @@ func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatO
34673536
func drawCondFmtIconSet(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
34683537
cfRule, ok := condFmtIconSetPresets[format.IconStyle]
34693538
if !ok {
3470-
return nil, nil
3539+
x14CfRule, ok := condFmtNewIconSetPresets[format.IconStyle]
3540+
if !ok {
3541+
return nil, nil
3542+
}
3543+
x14CfRule.Priority = p + 1
3544+
x14CfRule.IconSet.IconSet = format.IconStyle
3545+
x14CfRule.IconSet.Reverse = format.ReverseIcons
3546+
x14CfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly)
3547+
x14CfRule.Type = validType[format.Type]
3548+
return nil, x14CfRule
34713549
}
34723550
cfRule.Priority = p + 1
34733551
cfRule.IconSet.IconSet = format.IconStyle

styles_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ func TestSetConditionalFormat(t *testing.T) {
177177
for _, ref := range []string{"A1:A2", "B1:B2"} {
178178
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
179179
}
180+
// Test creating a conditional format with a "new" icon set
181+
f = NewFile()
182+
condFmts = []ConditionalFormatOptions{
183+
{Type: "icon_set", IconStyle: "3Triangles"},
184+
}
185+
for _, ref := range []string{"A1:A2", "B1:B2"} {
186+
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
187+
}
188+
180189
f = NewFile()
181190
// Test creating a conditional format without cell reference
182191
assert.Equal(t, ErrParameterRequired, f.SetConditionalFormat("Sheet1", "", nil))
@@ -274,6 +283,7 @@ func TestGetConditionalFormats(t *testing.T) {
274283
{{Type: "errors", Format: intPtr(1)}},
275284
{{Type: "no_errors", Format: intPtr(1)}},
276285
{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
286+
{{Type: "icon_set", IconStyle: "3Triangles", ReverseIcons: true, IconsOnly: true}},
277287
} {
278288
f := NewFile()
279289
err := f.SetConditionalFormat("Sheet1", "A2:A1,B:B,2:2", format)

xmlWorksheet.go

+46-7
Original file line numberDiff line numberDiff line change
@@ -735,14 +735,17 @@ type decodeX14ConditionalFormattingRules struct {
735735
type decodeX14ConditionalFormatting struct {
736736
XMLName xml.Name `xml:"conditionalFormatting"`
737737
CfRule []*decodeX14CfRule `xml:"cfRule"`
738+
SQRef string `xml:"sqref"`
738739
}
739740

740741
// decodeX14CfRule directly maps the cfRule element.
741742
type decodeX14CfRule struct {
742-
XMLName xml.Name `xml:"cfRule"`
743-
Type string `xml:"type,attr,omitempty"`
744-
ID string `xml:"id,attr,omitempty"`
745-
DataBar *decodeX14DataBar `xml:"dataBar"`
743+
XMLName xml.Name `xml:"cfRule"`
744+
Type string `xml:"type,attr,omitempty"`
745+
ID string `xml:"id,attr,omitempty"`
746+
Priority int `xml:"priority,attr,omitempty"`
747+
DataBar *decodeX14DataBar `xml:"dataBar"`
748+
IconSet *decodeX14IconSet `xml:"iconSet"`
746749
}
747750

748751
// decodeX14DataBar directly maps the dataBar element.
@@ -760,6 +763,23 @@ type decodeX14DataBar struct {
760763
AxisColor *xlsxColor `xml:"axisColor"`
761764
}
762765

766+
// decodeX14IconSet directly maps the iconSet element.
767+
type decodeX14IconSet struct {
768+
XMLName xml.Name `xml:"iconSet"`
769+
Cfvo []*decodeX14Cfvo `xml:"cfvo"`
770+
IconSet string `xml:"iconSet,attr,omitempty"`
771+
ShowValue *bool `xml:"showValue,attr"`
772+
Reverse bool `xml:"reverse,attr,omitempty"`
773+
}
774+
775+
// decodeX14Cfvo directly maps the cfvo element.
776+
type decodeX14Cfvo struct {
777+
XMLName xml.Name `xml:"cfvo"`
778+
Gte bool `xml:"gte,attr,omitempty"`
779+
Type string `xml:"type,attr,omitempty"`
780+
Val string `xml:"f"`
781+
}
782+
763783
// xlsxX14ConditionalFormattings directly maps the conditionalFormattings
764784
// element.
765785
type xlsxX14ConditionalFormattings struct {
@@ -772,13 +792,16 @@ type xlsxX14ConditionalFormatting struct {
772792
XMLName xml.Name `xml:"x14:conditionalFormatting"`
773793
XMLNSXM string `xml:"xmlns:xm,attr"`
774794
CfRule []*xlsxX14CfRule `xml:"x14:cfRule"`
795+
SQRef string `xml:"xm:sqref"`
775796
}
776797

777798
// xlsxX14CfRule directly maps the cfRule element.
778799
type xlsxX14CfRule struct {
779-
Type string `xml:"type,attr,omitempty"`
780-
ID string `xml:"id,attr,omitempty"`
781-
DataBar *xlsx14DataBar `xml:"x14:dataBar"`
800+
Type string `xml:"type,attr,omitempty"`
801+
ID string `xml:"id,attr,omitempty"`
802+
Priority int `xml:"priority,attr,omitempty"`
803+
DataBar *xlsx14DataBar `xml:"x14:dataBar"`
804+
IconSet *xlsx14IconSet `xml:"x14:iconSet"`
782805
}
783806

784807
// xlsx14DataBar directly maps the dataBar element.
@@ -795,6 +818,22 @@ type xlsx14DataBar struct {
795818
AxisColor *xlsxColor `xml:"x14:axisColor"`
796819
}
797820

821+
// xlsxIconSet (Icon Set) describes an icon set conditional formatting rule.
822+
type xlsx14IconSet struct {
823+
Cfvo []*xlsx14Cfvo `xml:"x14:cfvo"`
824+
IconSet string `xml:"iconSet,attr,omitempty"`
825+
ShowValue *bool `xml:"showValue,attr"`
826+
Reverse bool `xml:"reverse,attr,omitempty"`
827+
}
828+
829+
// cfvo (Conditional Format Value Object) describes the values of the
830+
// interpolation points in a gradient scale.
831+
type xlsx14Cfvo struct {
832+
Gte bool `xml:"gte,attr,omitempty"`
833+
Type string `xml:"type,attr,omitempty"`
834+
Val string `xml:"xm:f"`
835+
}
836+
798837
// xlsxX14SparklineGroups directly maps the sparklineGroups element.
799838
type xlsxX14SparklineGroups struct {
800839
XMLName xml.Name `xml:"x14:sparklineGroups"`

0 commit comments

Comments
 (0)