Support creating a conditional format with an "icon sets" rule

- Improvement compatibility for the worksheet extension lists
- Update unit test
pull/2/head
xuri 2 years ago
parent 3e2406096f
commit 753969dc4e
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -14,6 +14,7 @@ package excelize
import ( import (
"encoding/xml" "encoding/xml"
"io" "io"
"sort"
"strings" "strings"
) )
@ -397,7 +398,6 @@ func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error {
ok bool ok bool
group *xlsxX14SparklineGroup group *xlsxX14SparklineGroup
groups *xlsxX14SparklineGroups groups *xlsxX14SparklineGroups
sparklineGroupsBytes, extBytes []byte
) )
// parameter validation // parameter validation
@ -434,26 +434,9 @@ func (f *File) AddSparkline(sheet string, opts *SparklineOptions) error {
group.RightToLeft = opts.Reverse group.RightToLeft = opts.Reverse
} }
f.addSparkline(opts, group) f.addSparkline(opts, group)
if ws.ExtLst.Ext != "" { // append mode ext
if err = f.appendSparkline(ws, group, groups); err != nil { if err = f.appendSparkline(ws, group, groups); err != nil {
return err return err
} }
} else {
groups = &xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
SparklineGroups: []*xlsxX14SparklineGroup{group},
}
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
return err
}
if extBytes, err = xml.Marshal(&xlsxWorksheetExt{
URI: ExtURISparklineGroups,
Content: string(sparklineGroupsBytes),
}); err != nil {
return err
}
ws.ExtLst.Ext = string(extBytes)
}
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14) f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
return err return err
} }
@ -504,12 +487,14 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup,
var ( var (
err error err error
idx int idx int
decodeExtLst *decodeWorksheetExt appendMode bool
decodeExtLst = new(decodeWorksheetExt)
decodeSparklineGroups *decodeX14SparklineGroups decodeSparklineGroups *decodeX14SparklineGroups
ext *xlsxWorksheetExt ext *xlsxWorksheetExt
sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte
) )
decodeExtLst = new(decodeWorksheetExt) sparklineGroupBytes, _ = xml.Marshal(group)
if ws.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")). if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF { Decode(decodeExtLst); err != nil && err != io.EOF {
return err return err
@ -521,25 +506,31 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup,
Decode(decodeSparklineGroups); err != nil && err != io.EOF { Decode(decodeSparklineGroups); err != nil && err != io.EOF {
return err return err
} }
if sparklineGroupBytes, err = xml.Marshal(group); err != nil {
return err
}
if groups == nil { if groups == nil {
groups = &xlsxX14SparklineGroups{} groups = &xlsxX14SparklineGroups{}
} }
groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value groups.XMLNSXM = NameSpaceSpreadSheetExcel2006Main.Value
groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes) groups.Content = decodeSparklineGroups.Content + string(sparklineGroupBytes)
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { sparklineGroupsBytes, _ = xml.Marshal(groups)
return err
}
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
appendMode = true
} }
} }
if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil {
return err
} }
ws.ExtLst = &xlsxExtLst{ if !appendMode {
Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>"), sparklineGroupsBytes, _ = xml.Marshal(&xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
SparklineGroups: []*xlsxX14SparklineGroup{group},
})
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{
URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes),
})
} }
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
return err return err
} }

@ -264,23 +264,23 @@ func TestAddSparkline(t *testing.T) {
Range: []string{"Sheet2!A3:E3"}, Range: []string{"Sheet2!A3:E3"},
Style: -1, Style: -1,
}), ErrSparklineStyle.Error()) }), ErrSparklineStyle.Error())
// Test creating a conditional format with existing extension lists
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok) assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = `<extLst> ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: `
<ext x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"> <ext uri="{A8765BA9-456A-4dab-B4F3-ACF838C121DE}"><x14:slicerList /></ext>
<x14:sparklineGroups <ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups /></ext>`}
xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"> assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOptions{
<x14:sparklineGroup> Location: []string{"A3"},
</x14:sparklines> Range: []string{"Sheet3!A2:J2"},
</x14:sparklineGroup> Type: "column",
</x14:sparklineGroups> }))
</ext> // Test creating a conditional format with invalid extension list characters
</extLst>` ws.(*xlsxWorksheet).ExtLst.Ext = `<ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups><x14:sparklineGroup></x14:sparklines></x14:sparklineGroup></x14:sparklineGroups></ext>`
assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{ assert.EqualError(t, f.AddSparkline("Sheet1", &SparklineOptions{
Location: []string{"A2"}, Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"}, Range: []string{"Sheet3!A1:J1"},
}), "XML syntax error on line 6: element <sparklineGroup> closed by </sparklines>") }), "XML syntax error on line 1: element <sparklineGroup> closed by </sparklines>")
} }
func TestAppendSparkline(t *testing.T) { func TestAppendSparkline(t *testing.T) {

@ -18,6 +18,7 @@ import (
"io" "io"
"math" "math"
"reflect" "reflect"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@ -807,6 +808,7 @@ var validType = map[string]string{
"3_color_scale": "3_color_scale", "3_color_scale": "3_color_scale",
"data_bar": "dataBar", "data_bar": "dataBar",
"formula": "expression", "formula": "expression",
"iconSet": "iconSet",
} }
// criteriaType defined the list of valid criteria types. // criteriaType defined the list of valid criteria types.
@ -2880,7 +2882,14 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// | MaxType // | MaxType
// | MinValue // | MinValue
// | MaxValue // | MaxValue
// | BarBorderColor
// | BarColor // | BarColor
// | BarDirection
// | BarOnly
// | BarSolid
// iconSet | IconStyle
// | ReverseIcons
// | IconsOnly
// formula | Criteria // formula | Criteria
// //
// The 'Criteria' parameter is used to set the criteria by which the cell data // The 'Criteria' parameter is used to set the criteria by which the cell data
@ -3037,7 +3046,8 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// }, // },
// ) // )
// //
// type: duplicate - The duplicate type is used to highlight duplicate cells in a range: // type: duplicate - The duplicate type is used to highlight duplicate cells in
// a range:
// //
// // Highlight cells rules: Duplicate Values... // // Highlight cells rules: Duplicate Values...
// err := f.SetConditionalFormat("Sheet1", "A1:A10", // err := f.SetConditionalFormat("Sheet1", "A1:A10",
@ -3055,7 +3065,8 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// }, // },
// ) // )
// //
// type: top - The top type is used to specify the top n values by number or percentage in a range: // type: top - The top type is used to specify the top n values by number or
// percentage in a range:
// //
// // Top/Bottom rules: Top 10. // // Top/Bottom rules: Top 10.
// err := f.SetConditionalFormat("Sheet1", "H1:H10", // err := f.SetConditionalFormat("Sheet1", "H1:H10",
@ -3129,7 +3140,10 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // type: data_bar - The data_bar type is used to specify Excel's "Data Bar"
// style conditional format. // style conditional format.
// //
// MinType - The MinType and MaxType properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The MidType is available for 3_color_scale. The properties are used as follows: // MinType - The MinType and MaxType properties are available when the
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
// The MidType is available for 3_color_scale. The properties are used as
// follows:
// //
// // Data Bars: Gradient Fill. // // Data Bars: Gradient Fill.
// err := f.SetConditionalFormat("Sheet1", "K1:K10", // err := f.SetConditionalFormat("Sheet1", "K1:K10",
@ -3194,11 +3208,41 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// BarBorderColor - Used for sets the color for the border line of a data bar, // BarBorderColor - Used for sets the color for the border line of a data bar,
// this is only visible in Excel 2010 and later. // this is only visible in Excel 2010 and later.
// //
// BarOnly - Used for displays a bar data but not the data in the cells. // BarDirection - sets the direction for data bars. The available options are:
//
// context - Data bar direction is set by spreadsheet application based on the context of the data displayed.
// leftToRight - Data bar direction is from right to left.
// rightToLeft - Data bar direction is from left to right.
//
// BarOnly - Used for set displays a bar data but not the data in the cells.
// //
// BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this // BarSolid - Used for turns on a solid (non-gradient) fill for data bars, this
// is only visible in Excel 2010 and later. // is only visible in Excel 2010 and later.
// //
// IconStyle - The available options are:
//
// 3Arrows
// 3ArrowsGray
// 3Flags
// 3Signs
// 3Symbols
// 3Symbols2
// 3TrafficLights1
// 3TrafficLights2
// 4Arrows
// 4ArrowsGray
// 4Rating
// 4RedToBlack
// 4TrafficLights
// 5Arrows
// 5ArrowsGray
// 5Quarters
// 5Rating
//
// ReverseIcons - Used for set reversed icons sets.
//
// IconsOnly - Used for set displayed without the cell value.
//
// StopIfTrue - used to set the "stop if true" feature of a conditional // StopIfTrue - used to set the "stop if true" feature of a conditional
// formatting rule when more than one rule is applied to a cell or a range of // formatting rule when more than one rule is applied to a cell or a range of
// cells. When this parameter is set then subsequent rules are not evaluated // cells. When this parameter is set then subsequent rules are not evaluated
@ -3214,6 +3258,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
"3_color_scale": drawCondFmtColorScale, "3_color_scale": drawCondFmtColorScale,
"dataBar": drawCondFmtDataBar, "dataBar": drawCondFmtDataBar,
"expression": drawCondFmtExp, "expression": drawCondFmtExp,
"iconSet": drawCondFmtIconSet,
} }
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
@ -3235,10 +3280,13 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
if ok { if ok {
// Check for valid criteria types. // Check for valid criteria types.
ct, ok = criteriaType[v.Criteria] ct, ok = criteriaType[v.Criteria]
if ok || vt == "expression" { if ok || vt == "expression" || vt == "iconSet" {
drawFunc, ok := drawContFmtFunc[vt] drawFunc, ok := drawContFmtFunc[vt]
if ok { if ok {
rule, x14rule := drawFunc(p, ct, GUID, &v) rule, x14rule := drawFunc(p, ct, GUID, &v)
if rule == nil {
return ErrParameterInvalid
}
if x14rule != nil { if x14rule != nil {
if err = f.appendCfRule(ws, x14rule); err != nil { if err = f.appendCfRule(ws, x14rule); err != nil {
return err return err
@ -3263,14 +3311,17 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
var ( var (
err error err error
idx int idx int
decodeExtLst *decodeWorksheetExt appendMode bool
decodeExtLst = new(decodeWorksheetExt)
condFmts *xlsxX14ConditionalFormattings condFmts *xlsxX14ConditionalFormattings
decodeCondFmts *decodeX14ConditionalFormattings decodeCondFmts *decodeX14ConditionalFormattings
ext *xlsxWorksheetExt ext *xlsxWorksheetExt
condFmtBytes, condFmtsBytes, extLstBytes, extBytes []byte condFmtBytes, condFmtsBytes, extLstBytes []byte
) )
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{
{XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}},
})
if ws.ExtLst != nil { // append mode ext if ws.ExtLst != nil { // append mode ext
decodeExtLst = new(decodeWorksheetExt)
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")). if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF { Decode(decodeExtLst); err != nil && err != io.EOF {
return err return err
@ -3279,35 +3330,28 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error {
if ext.URI == ExtURIConditionalFormattings { if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts = new(decodeX14ConditionalFormattings) decodeCondFmts = new(decodeX14ConditionalFormattings)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeCondFmts)
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{
{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value,
CfRule: []*xlsxX14CfRule{rule},
},
})
if condFmts == nil { if condFmts == nil {
condFmts = &xlsxX14ConditionalFormattings{} condFmts = &xlsxX14ConditionalFormattings{}
} }
condFmts.Content = decodeCondFmts.Content + string(condFmtBytes) condFmts.Content = decodeCondFmts.Content + string(condFmtBytes)
condFmtsBytes, _ = xml.Marshal(condFmts) condFmtsBytes, _ = xml.Marshal(condFmts)
decodeExtLst.Ext[idx].Content = string(condFmtsBytes) decodeExtLst.Ext[idx].Content = string(condFmtsBytes)
appendMode = true
} }
} }
extLstBytes, _ = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{
Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>"),
}
return err
} }
condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ if !appendMode {
{XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, CfRule: []*xlsxX14CfRule{rule}},
})
condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)})
extBytes, err = xml.Marshal(&xlsxWorksheetExt{ decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{
URI: ExtURIConditionalFormattings, URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes),
Content: string(condFmtsBytes), })
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false)
}) })
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extBytes), "<extLst>"), "</extLst>")} extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
return err return err
} }
@ -3424,6 +3468,7 @@ func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO
for _, rule := range condFmt.CfRule { for _, rule := range condFmt.CfRule {
if rule.DataBar != nil { if rule.DataBar != nil {
format.BarSolid = !rule.DataBar.Gradient format.BarSolid = !rule.DataBar.Gradient
format.BarDirection = rule.DataBar.Direction
if rule.DataBar.BorderColor != nil { if rule.DataBar.BorderColor != nil {
format.BarBorderColor = "#" + strings.TrimPrefix(strings.ToUpper(rule.DataBar.BorderColor.RGB), "FF") format.BarBorderColor = "#" + strings.TrimPrefix(strings.ToUpper(rule.DataBar.BorderColor.RGB), "FF")
} }
@ -3466,6 +3511,20 @@ func extractCondFmtExp(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptio
return format return format
} }
// extractCondFmtIconSet provides a function to extract conditional format
// settings for icon sets by given conditional formatting rule.
func extractCondFmtIconSet(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
format := ConditionalFormatOptions{Type: "iconSet"}
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
}
// GetConditionalFormats returns conditional format settings by given worksheet // GetConditionalFormats returns conditional format settings by given worksheet
// name. // name.
func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) { func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) {
@ -3478,6 +3537,7 @@ func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalForm
"colorScale": extractCondFmtColorScale, "colorScale": extractCondFmtColorScale,
"dataBar": extractCondFmtDataBar, "dataBar": extractCondFmtDataBar,
"expression": extractCondFmtExp, "expression": extractCondFmtExp,
"iconSet": extractCondFmtIconSet,
} }
conditionalFormats := make(map[string][]ConditionalFormatOptions) conditionalFormats := make(map[string][]ConditionalFormatOptions)
@ -3622,19 +3682,22 @@ func drawCondFmtColorScale(p int, ct, GUID string, format *ConditionalFormatOpti
func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) { func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
var x14CfRule *xlsxX14CfRule var x14CfRule *xlsxX14CfRule
var extLst *xlsxExtLst var extLst *xlsxExtLst
if format.BarSolid { if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" {
extLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:id>%s</x14:id></ext>`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)} extLst = &xlsxExtLst{Ext: fmt.Sprintf(`<ext uri="%s" xmlns:x14="%s"><x14:id>%s</x14:id></ext>`, ExtURIConditionalFormattingRuleID, NameSpaceSpreadSheetX14.Value, GUID)}
x14CfRule = &xlsxX14CfRule{ x14CfRule = &xlsxX14CfRule{
Type: validType[format.Type], Type: validType[format.Type],
ID: GUID, ID: GUID,
DataBar: &xlsx14DataBar{ DataBar: &xlsx14DataBar{
MaxLength: 100, MaxLength: 100,
Border: format.BarBorderColor != "",
Gradient: !format.BarSolid,
Direction: format.BarDirection,
Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}}, Cfvo: []*xlsxCfvo{{Type: "autoMin"}, {Type: "autoMax"}},
NegativeFillColor: &xlsxColor{RGB: "FFFF0000"}, NegativeFillColor: &xlsxColor{RGB: "FFFF0000"},
AxisColor: &xlsxColor{RGB: "FFFF0000"}, AxisColor: &xlsxColor{RGB: "FFFF0000"},
}, },
} }
if format.BarBorderColor != "" { if x14CfRule.DataBar.Border {
x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)} x14CfRule.DataBar.BorderColor = &xlsxColor{RGB: getPaletteColor(format.BarBorderColor)}
} }
} }
@ -3663,6 +3726,58 @@ func drawCondFmtExp(p int, ct, GUID string, format *ConditionalFormatOptions) (*
}, nil }, nil
} }
// drawCondFmtIconSet provides a function to create conditional formatting rule
// for icon set by given priority, criteria type and format settings.
func drawCondFmtIconSet(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
cfvo3 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "33"},
{Type: "percent", Val: "67"},
}}}
cfvo4 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "25"},
{Type: "percent", Val: "50"},
{Type: "percent", Val: "75"},
}}}
cfvo5 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "20"},
{Type: "percent", Val: "40"},
{Type: "percent", Val: "60"},
{Type: "percent", Val: "80"},
}}}
presets := map[string]*xlsxCfRule{
"3Arrows": cfvo3,
"3ArrowsGray": cfvo3,
"3Flags": cfvo3,
"3Signs": cfvo3,
"3Symbols": cfvo3,
"3Symbols2": cfvo3,
"3TrafficLights1": cfvo3,
"3TrafficLights2": cfvo3,
"4Arrows": cfvo4,
"4ArrowsGray": cfvo4,
"4Rating": cfvo4,
"4RedToBlack": cfvo4,
"4TrafficLights": cfvo4,
"5Arrows": cfvo5,
"5ArrowsGray": cfvo5,
"5Quarters": cfvo5,
"5Rating": cfvo5,
}
cfRule, ok := presets[format.IconStyle]
if !ok {
return nil, nil
}
cfRule.Priority = p + 1
cfRule.IconSet.IconSet = format.IconStyle
cfRule.IconSet.Reverse = format.ReverseIcons
cfRule.IconSet.ShowValue = boolPtr(!format.IconsOnly)
cfRule.Type = format.Type
return cfRule, nil
}
// getPaletteColor provides a function to convert the RBG color by given // getPaletteColor provides a function to convert the RBG color by given
// string. // string.
func getPaletteColor(color string) string { func getPaletteColor(color string) string {

@ -176,11 +176,22 @@ func TestSetConditionalFormat(t *testing.T) {
for _, ref := range []string{"A1:A2", "B1:B2"} { for _, ref := range []string{"A1:A2", "B1:B2"} {
assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts)) assert.NoError(t, f.SetConditionalFormat("Sheet1", ref, condFmts))
} }
// Test creating a conditional format with invalid extension list characters f = NewFile()
// Test creating a conditional format with existing extension lists
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok) assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = "<extLst><ext><x14:conditionalFormattings></x14:conditionalFormatting></x14:conditionalFormattings></ext></extLst>" ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: `
<ext uri="{A8765BA9-456A-4dab-B4F3-ACF838C121DE}"><x14:slicerList /></ext>
<ext uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"><x14:sparklineGroups /></ext>`}
assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarSolid: true}}))
f = NewFile()
// Test creating a conditional format with invalid extension list characters
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: "<ext><x14:conditionalFormattings></x14:conditionalFormatting></x14:conditionalFormattings></ext>"}
assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element <conditionalFormattings> closed by </conditionalFormatting>") assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", condFmts), "XML syntax error on line 1: element <conditionalFormattings> closed by </conditionalFormatting>")
// Test creating a conditional format with invalid icon set style
assert.EqualError(t, f.SetConditionalFormat("Sheet1", "A1:A2", []ConditionalFormatOptions{{Type: "iconSet", IconStyle: "unknown"}}), ErrParameterInvalid.Error())
} }
func TestGetConditionalFormats(t *testing.T) { func TestGetConditionalFormats(t *testing.T) {
@ -194,8 +205,10 @@ func TestGetConditionalFormats(t *testing.T) {
{{Type: "unique", Format: 1, Criteria: "="}}, {{Type: "unique", Format: 1, Criteria: "="}},
{{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}}, {{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}},
{{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}}, {{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6", BarBorderColor: "#0000FF", BarOnly: true, BarSolid: true, StopIfTrue: true}}, {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "formula", Format: 1, Criteria: "="}}, {{Type: "formula", Format: 1, Criteria: "="}},
{{Type: "iconSet", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
} { } {
f := NewFile() f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A1:A2", format) err := f.SetConditionalFormat("Sheet1", "A1:A2", format)

@ -107,6 +107,18 @@ const (
ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}"
) )
// extensionURIPriority is the priority of URI in the extension lists.
var extensionURIPriority = []string{
ExtURIConditionalFormattings,
ExtURIDataValidations,
ExtURISparklineGroups,
ExtURISlicerListX14,
ExtURIProtectedRanges,
ExtURIIgnoredErrors,
ExtURIWebExtensions,
ExtURITimelineRefs,
}
// Excel specifications and limits // Excel specifications and limits
const ( const (
MaxCellStyles = 65430 MaxCellStyles = 65430

@ -777,8 +777,10 @@ type decodeX14DataBar struct {
XNLName xml.Name `xml:"dataBar"` XNLName xml.Name `xml:"dataBar"`
MaxLength int `xml:"maxLength,attr"` MaxLength int `xml:"maxLength,attr"`
MinLength int `xml:"minLength,attr"` MinLength int `xml:"minLength,attr"`
Border bool `xml:"border,attr,omitempty"`
Gradient bool `xml:"gradient,attr"` Gradient bool `xml:"gradient,attr"`
ShowValue bool `xml:"showValue,attr,omitempty"` ShowValue bool `xml:"showValue,attr,omitempty"`
Direction string `xml:"direction,attr,omitempty"`
Cfvo []*xlsxCfvo `xml:"cfvo"` Cfvo []*xlsxCfvo `xml:"cfvo"`
BorderColor *xlsxColor `xml:"borderColor"` BorderColor *xlsxColor `xml:"borderColor"`
NegativeFillColor *xlsxColor `xml:"negativeFillColor"` NegativeFillColor *xlsxColor `xml:"negativeFillColor"`
@ -801,7 +803,6 @@ type xlsxX14ConditionalFormatting struct {
// xlsxX14CfRule directly maps the cfRule element. // xlsxX14CfRule directly maps the cfRule element.
type xlsxX14CfRule struct { type xlsxX14CfRule struct {
XNLName xml.Name `xml:"x14:cfRule"`
Type string `xml:"type,attr,omitempty"` Type string `xml:"type,attr,omitempty"`
ID string `xml:"id,attr,omitempty"` ID string `xml:"id,attr,omitempty"`
DataBar *xlsx14DataBar `xml:"x14:dataBar"` DataBar *xlsx14DataBar `xml:"x14:dataBar"`
@ -809,11 +810,12 @@ type xlsxX14CfRule struct {
// xlsx14DataBar directly maps the dataBar element. // xlsx14DataBar directly maps the dataBar element.
type xlsx14DataBar struct { type xlsx14DataBar struct {
XNLName xml.Name `xml:"x14:dataBar"`
MaxLength int `xml:"maxLength,attr"` MaxLength int `xml:"maxLength,attr"`
MinLength int `xml:"minLength,attr"` MinLength int `xml:"minLength,attr"`
Border bool `xml:"border,attr"`
Gradient bool `xml:"gradient,attr"` Gradient bool `xml:"gradient,attr"`
ShowValue bool `xml:"showValue,attr,omitempty"` ShowValue bool `xml:"showValue,attr,omitempty"`
Direction string `xml:"direction,attr,omitempty"`
Cfvo []*xlsxCfvo `xml:"x14:cfvo"` Cfvo []*xlsxCfvo `xml:"x14:cfvo"`
BorderColor *xlsxColor `xml:"x14:borderColor"` BorderColor *xlsxColor `xml:"x14:borderColor"`
NegativeFillColor *xlsxColor `xml:"x14:negativeFillColor"` NegativeFillColor *xlsxColor `xml:"x14:negativeFillColor"`
@ -942,8 +944,12 @@ type ConditionalFormatOptions struct {
MaxLength string MaxLength string
BarColor string BarColor string
BarBorderColor string BarBorderColor string
BarDirection string
BarOnly bool BarOnly bool
BarSolid bool BarSolid bool
IconStyle string
ReverseIcons bool
IconsOnly bool
StopIfTrue bool StopIfTrue bool
} }

Loading…
Cancel
Save