Skip to content

Commit

Permalink
WIP add support for new icon sets
Browse files Browse the repository at this point in the history
  • Loading branch information
imirkin committed Dec 14, 2024
1 parent 8e04909 commit 38c080f
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 20 deletions.
104 changes: 91 additions & 13 deletions styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -2764,17 +2784,20 @@ func (f *File) SetCellStyle(sheet, topLeftCell, bottomRightCell string, styleID
// 3ArrowsGray
// 3Flags
// 3Signs
// 3Stars
// 3Symbols
// 3Symbols2
// 3TrafficLights1
// 3TrafficLights2
// 3Triangles
// 4Arrows
// 4ArrowsGray
// 4Rating
// 4RedToBlack
// 4TrafficLights
// 5Arrows
// 5ArrowsGray
// 5Boxes
// 5Quarters
// 5Rating
//
Expand Down Expand Up @@ -2802,6 +2825,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
}
var (
cfRule []*xlsxCfRule
x14CfRule []*xlsxX14CfRule
noCriteriaTypes = []string{
"containsBlanks",
"notContainsBlanks",
Expand All @@ -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
}
}
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return conditionalFormats, err
}

Check warning on line 3240 in styles.go

View check run for this annotation

Codecov / codecov/patch

styles.go#L3239-L3240

Added lines #L3239 - L3240 were not covered by tests
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
}

Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions styles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
53 changes: 46 additions & 7 deletions xmlWorksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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.
Expand All @@ -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"`
Expand Down

0 comments on commit 38c080f

Please sign in to comment.