summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRi Xu <xuri.me@gmail.com>2017-08-11 23:15:33 +0800
committerRi Xu <xuri.me@gmail.com>2017-08-11 23:15:33 +0800
commit67636039f672f23d4bf812b96ebb8521aec5d642 (patch)
tree0ad106951f37fee011a6033b43bce01c8a870851
parenta8cf38ebd5537b678a3747cd246fd1c511c73331 (diff)
- Init conditional format support, relate issue #75;
- go test and godoc updated
-rw-r--r--excelize_test.go60
-rw-r--r--styles.go612
-rw-r--r--test/Workbook1.xlsxbin22968 -> 22962 bytes
-rw-r--r--xmlStyles.go39
-rw-r--r--xmlWorksheet.go86
5 files changed, 700 insertions, 97 deletions
diff --git a/excelize_test.go b/excelize_test.go
index 2798319..f4a0774 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -1,6 +1,7 @@
package excelize
import (
+ "fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
@@ -895,3 +896,62 @@ func TestRemoveRow(t *testing.T) {
t.Log(err)
}
}
+
+func TestConditionalFormat(t *testing.T) {
+ xlsx := NewFile()
+ for j := 1; j <= 10; j++ {
+ for i := 0; i <= 10; i++ {
+ xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
+ }
+ }
+ var format1, format2, format3 int
+ var err error
+ // Rose format for bad conditional.
+ format1, err = xlsx.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
+ t.Log(err)
+ // Light yellow format for neutral conditional.
+ format2, err = xlsx.NewConditionalStyle(`{"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`)
+ t.Log(err)
+ // Light green format for good conditional.
+ format3, err = xlsx.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`)
+ t.Log(err)
+ // Color scales: 2 color.
+ xlsx.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
+ // Color scales: 3 color.
+ xlsx.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
+ // Hightlight cells rules: between...
+ xlsx.SetConditionalFormat("Sheet1", "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
+ // Hightlight cells rules: Greater Than...
+ xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))
+ // Hightlight cells rules: Equal To...
+ xlsx.SetConditionalFormat("Sheet1", "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))
+ // Hightlight cells rules: Not Equal To...
+ xlsx.SetConditionalFormat("Sheet1", "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))
+ // Hightlight cells rules: Duplicate Values...
+ xlsx.SetConditionalFormat("Sheet1", "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))
+ // Top/Bottom rules: Top 10%.
+ xlsx.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
+ // Top/Bottom rules: Above Average...
+ xlsx.SetConditionalFormat("Sheet1", "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))
+ // Top/Bottom rules: Below Average...
+ xlsx.SetConditionalFormat("Sheet1", "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
+ // Data Bars: Gradient Fill.
+ xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ err = xlsx.SaveAs("./test/Workbook_conditional_format.xlsx")
+ if err != nil {
+ t.Log(err)
+ }
+
+ // Set conditional format with illegal JSON string.
+ _, err = xlsx.NewConditionalStyle("")
+ t.Log(err)
+ // Set conditional format with illegal valid type.
+ xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ // Set conditional format with illegal criteria type.
+ xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ // Set conditional format with file without dxfs element.
+ xlsx, err = OpenFile("./test/Workbook1.xlsx")
+ t.Log(err)
+ _, err = xlsx.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
+ t.Log(err)
+}
diff --git a/styles.go b/styles.go
index b359313..67fe6ff 100644
--- a/styles.go
+++ b/styles.go
@@ -774,6 +774,61 @@ var builtInNumFmtFunc = map[int]func(i int, v string) string{
49: formatToString,
}
+// validType defined the list of valid validation types.
+var validType = map[string]string{
+ "cell": "cellIs",
+ "date": "date", // Doesn't support currently
+ "time": "time", // Doesn't support currently
+ "average": "aboveAverage",
+ "duplicate": "duplicateValues",
+ "unique": "uniqueValues",
+ "top": "top10",
+ "bottom": "top10",
+ "text": "text", // Doesn't support currently
+ "time_period": "timePeriod", // Doesn't support currently
+ "blanks": "containsBlanks", // Doesn't support currently
+ "no_blanks": "notContainsBlanks", // Doesn't support currently
+ "errors": "containsErrors", // Doesn't support currently
+ "no_errors": "notContainsErrors", // Doesn't support currently
+ "2_color_scale": "2_color_scale",
+ "3_color_scale": "3_color_scale",
+ "data_bar": "dataBar",
+ "formula": "expression", // Doesn't support currently
+}
+
+// criteriaType defined the list of valid criteria types.
+var criteriaType = map[string]string{
+ "between": "between",
+ "not between": "notBetween",
+ "equal to": "equal",
+ "=": "equal",
+ "==": "equal",
+ "not equal to": "notEqual",
+ "!=": "notEqual",
+ "<>": "notEqual",
+ "greater than": "greaterThan",
+ ">": "greaterThan",
+ "less than": "lessThan",
+ "<": "lessThan",
+ "greater than or equal to": "greaterThanOrEqual",
+ ">=": "greaterThanOrEqual",
+ "less than or equal to": "lessThanOrEqual",
+ "<=": "lessThanOrEqual",
+ "containing": "containsText",
+ "not containing": "notContains",
+ "begins with": "beginsWith",
+ "ends with": "endsWith",
+ "yesterday": "yesterday",
+ "today": "today",
+ "last 7 days": "last7Days",
+ "last week": "lastWeek",
+ "this week": "thisWeek",
+ "continue week": "continueWeek",
+ "last month": "lastMonth",
+ "this month": "thisMonth",
+ "continue month": "continueMonth",
+}
+
// formatToString provides function to return original string by given built-in
// number formats code and cell string.
func formatToString(i int, v string) string {
@@ -935,9 +990,9 @@ func (f *File) styleSheetWriter() {
}
// parseFormatStyleSet provides function to parse the format settings of the
-// borders.
-func parseFormatStyleSet(style string) (*formatCellStyle, error) {
- format := formatCellStyle{
+// cells and conditional formats.
+func parseFormatStyleSet(style string) (*formatStyle, error) {
+ format := formatStyle{
DecimalPlaces: 2,
}
err := json.Unmarshal([]byte(style), &format)
@@ -1806,79 +1861,114 @@ func parseFormatStyleSet(style string) (*formatCellStyle, error) {
// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017
//
func (f *File) NewStyle(style string) (int, error) {
- var cellXfsID int
- styleSheet := f.stylesReader()
- formatCellStyle, err := parseFormatStyleSet(style)
+ var cellXfsID, fontID, borderID, fillID int
+ s := f.stylesReader()
+ fs, err := parseFormatStyleSet(style)
if err != nil {
return cellXfsID, err
}
- numFmtID := setNumFmt(styleSheet, formatCellStyle)
- fontID := setFont(styleSheet, formatCellStyle)
- borderID := setBorders(styleSheet, formatCellStyle)
- fillID := setFills(styleSheet, formatCellStyle)
- applyAlignment, alignment := setAlignment(styleSheet, formatCellStyle)
- cellXfsID = setCellXfs(styleSheet, fontID, numFmtID, fillID, borderID, applyAlignment, alignment)
+ numFmtID := setNumFmt(s, fs)
+
+ if fs.Font != nil {
+ font, _ := xml.Marshal(setFont(fs))
+ s.Fonts.Count++
+ s.Fonts.Font = append(s.Fonts.Font, &xlsxFont{
+ Font: string(font[6 : len(font)-7]),
+ })
+ fontID = s.Fonts.Count - 1
+ }
+
+ s.Borders.Count++
+ s.Borders.Border = append(s.Borders.Border, setBorders(fs))
+ borderID = s.Borders.Count - 1
+
+ s.Fills.Count++
+ s.Fills.Fill = append(s.Fills.Fill, setFills(fs, true))
+ fillID = s.Fills.Count - 1
+
+ applyAlignment, alignment := fs.Alignment != nil, setAlignment(fs)
+ cellXfsID = setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, alignment)
return cellXfsID, nil
}
-// setFont provides function to add font style by given cell format settings.
-func setFont(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
- if formatCellStyle.Font == nil {
- return 0
+// NewConditionalStyle provides function to create style for conditional format
+// by given style format. Note that the color field uses RGB color code. Only
+// support set font, fills, alignment and borders currently.
+func (f *File) NewConditionalStyle(style string) (int, error) {
+ s := f.stylesReader()
+ fs, err := parseFormatStyleSet(style)
+ if err != nil {
+ return 0, err
+ }
+ dxf := dxf{
+ Fill: setFills(fs, false),
+ Alignment: setAlignment(fs),
+ Border: setBorders(fs),
+ }
+ if fs.Font != nil {
+ dxf.Font = setFont(fs)
+ }
+ dxfStr, _ := xml.Marshal(dxf)
+ if s.Dxfs == nil {
+ s.Dxfs = &xlsxDxfs{}
}
+ s.Dxfs.Count++
+ s.Dxfs.Dxfs = append(s.Dxfs.Dxfs, &xlsxDxf{
+ Dxf: string(dxfStr[5 : len(dxfStr)-6]),
+ })
+ return s.Dxfs.Count - 1, nil
+}
+
+// setFont provides function to add font style by given cell format settings.
+func setFont(formatStyle *formatStyle) *font {
fontUnderlineType := map[string]string{"single": "single", "double": "double"}
- if formatCellStyle.Font.Family == "" {
- formatCellStyle.Font.Family = "Calibri"
+ if formatStyle.Font.Family == "" {
+ formatStyle.Font.Family = "Calibri"
}
- if formatCellStyle.Font.Size < 1 {
- formatCellStyle.Font.Size = 11
+ if formatStyle.Font.Size < 1 {
+ formatStyle.Font.Size = 11
}
- if formatCellStyle.Font.Color == "" {
- formatCellStyle.Font.Color = "#000000"
+ if formatStyle.Font.Color == "" {
+ formatStyle.Font.Color = "#000000"
}
f := font{
- B: formatCellStyle.Font.Bold,
- I: formatCellStyle.Font.Italic,
- Sz: &attrValInt{Val: formatCellStyle.Font.Size},
- Color: &xlsxColor{RGB: getPaletteColor(formatCellStyle.Font.Color)},
- Name: &attrValString{Val: formatCellStyle.Font.Family},
+ B: formatStyle.Font.Bold,
+ I: formatStyle.Font.Italic,
+ Sz: &attrValInt{Val: formatStyle.Font.Size},
+ Color: &xlsxColor{RGB: getPaletteColor(formatStyle.Font.Color)},
+ Name: &attrValString{Val: formatStyle.Font.Family},
Family: &attrValInt{Val: 2},
Scheme: &attrValString{Val: "minor"},
}
- val, ok := fontUnderlineType[formatCellStyle.Font.Underline]
+ val, ok := fontUnderlineType[formatStyle.Font.Underline]
if ok {
f.U = &attrValString{Val: val}
}
- font, _ := xml.Marshal(f)
- style.Fonts.Count++
- style.Fonts.Font = append(style.Fonts.Font, &xlsxFont{
- Font: string(font[6 : len(font)-7]),
- })
- return style.Fonts.Count - 1
+ return &f
}
// setNumFmt provides function to check if number format code in the range of
// built-in values.
-func setNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
+func setNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
dp := "0."
numFmtID := 164 // Default custom number format code from 164.
- if formatCellStyle.DecimalPlaces < 0 || formatCellStyle.DecimalPlaces > 30 {
- formatCellStyle.DecimalPlaces = 2
+ if formatStyle.DecimalPlaces < 0 || formatStyle.DecimalPlaces > 30 {
+ formatStyle.DecimalPlaces = 2
}
- for i := 0; i < formatCellStyle.DecimalPlaces; i++ {
+ for i := 0; i < formatStyle.DecimalPlaces; i++ {
dp += "0"
}
- if formatCellStyle.CustomNumFmt != nil {
- return setCustomNumFmt(style, formatCellStyle)
+ if formatStyle.CustomNumFmt != nil {
+ return setCustomNumFmt(style, formatStyle)
}
- _, ok := builtInNumFmt[formatCellStyle.NumFmt]
+ _, ok := builtInNumFmt[formatStyle.NumFmt]
if !ok {
- fc, currency := currencyNumFmt[formatCellStyle.NumFmt]
+ fc, currency := currencyNumFmt[formatStyle.NumFmt]
if !currency {
- return setLangNumFmt(style, formatCellStyle)
+ return setLangNumFmt(style, formatStyle)
}
fc = strings.Replace(fc, "0.00", dp, -1)
- if formatCellStyle.NegRed {
+ if formatStyle.NegRed {
fc = fc + ";[Red]" + fc
}
if style.NumFmts != nil {
@@ -1902,12 +1992,12 @@ func setNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
}
return numFmtID
}
- return formatCellStyle.NumFmt
+ return formatStyle.NumFmt
}
// setCustomNumFmt provides function to set custom number format code.
-func setCustomNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
- nf := xlsxNumFmt{FormatCode: *formatCellStyle.CustomNumFmt}
+func setCustomNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
+ nf := xlsxNumFmt{FormatCode: *formatStyle.CustomNumFmt}
if style.NumFmts != nil {
nf.NumFmtID = style.NumFmts.NumFmt[len(style.NumFmts.NumFmt)-1].NumFmtID + 1
style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf)
@@ -1924,13 +2014,13 @@ func setCustomNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) in
}
// setLangNumFmt provides function to set number format code with language.
-func setLangNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
- numFmts, ok := langNumFmt[formatCellStyle.Lang]
+func setLangNumFmt(style *xlsxStyleSheet, formatStyle *formatStyle) int {
+ numFmts, ok := langNumFmt[formatStyle.Lang]
if !ok {
return 0
}
var fc string
- fc, ok = numFmts[formatCellStyle.NumFmt]
+ fc, ok = numFmts[formatStyle.NumFmt]
if !ok {
return 0
}
@@ -1940,7 +2030,7 @@ func setLangNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int
style.NumFmts.NumFmt = append(style.NumFmts.NumFmt, &nf)
style.NumFmts.Count++
} else {
- nf.NumFmtID = formatCellStyle.NumFmt
+ nf.NumFmtID = formatStyle.NumFmt
numFmts := xlsxNumFmts{
NumFmt: []*xlsxNumFmt{&nf},
Count: 1,
@@ -1952,7 +2042,7 @@ func setLangNumFmt(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int
// setFills provides function to add fill elements in the styles.xml by given
// cell format settings.
-func setFills(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
+func setFills(formatStyle *formatStyle, fg bool) *xlsxFill {
var patterns = []string{
"none",
"solid",
@@ -1983,15 +2073,15 @@ func setFills(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
}
var fill xlsxFill
- switch formatCellStyle.Fill.Type {
+ switch formatStyle.Fill.Type {
case "gradient":
- if len(formatCellStyle.Fill.Color) != 2 {
+ if len(formatStyle.Fill.Color) != 2 {
break
}
var gradient xlsxGradientFill
- switch formatCellStyle.Fill.Shading {
+ switch formatStyle.Fill.Shading {
case 0, 1, 2, 3:
- gradient.Degree = variants[formatCellStyle.Fill.Shading]
+ gradient.Degree = variants[formatStyle.Fill.Shading]
case 4:
gradient.Type = "path"
case 5:
@@ -2004,7 +2094,7 @@ func setFills(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
break
}
var stops []*xlsxGradientFillStop
- for index, color := range formatCellStyle.Fill.Color {
+ for index, color := range formatStyle.Fill.Color {
var stop xlsxGradientFillStop
stop.Position = float64(index)
stop.Color.RGB = getPaletteColor(color)
@@ -2013,46 +2103,46 @@ func setFills(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
gradient.Stop = stops
fill.GradientFill = &gradient
case "pattern":
- if formatCellStyle.Fill.Pattern > 18 || formatCellStyle.Fill.Pattern < 0 {
+ if formatStyle.Fill.Pattern > 18 || formatStyle.Fill.Pattern < 0 {
break
}
- if len(formatCellStyle.Fill.Color) < 1 {
+ if len(formatStyle.Fill.Color) < 1 {
break
}
var pattern xlsxPatternFill
- pattern.PatternType = patterns[formatCellStyle.Fill.Pattern]
- pattern.FgColor.RGB = getPaletteColor(formatCellStyle.Fill.Color[0])
+ pattern.PatternType = patterns[formatStyle.Fill.Pattern]
+ if fg {
+ pattern.FgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0])
+ } else {
+ pattern.BgColor.RGB = getPaletteColor(formatStyle.Fill.Color[0])
+ }
fill.PatternFill = &pattern
}
- style.Fills.Count++
- style.Fills.Fill = append(style.Fills.Fill, &fill)
- return style.Fills.Count - 1
+ return &fill
}
// setAlignment provides function to formatting information pertaining to text
// alignment in cells. There are a variety of choices for how text is aligned
// both horizontally and vertically, as well as indentation settings, and so on.
-func setAlignment(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) (bool, *xlsxAlignment) {
- if formatCellStyle.Alignment == nil {
- return false, &xlsxAlignment{}
- }
- var alignment = xlsxAlignment{
- Horizontal: formatCellStyle.Alignment.Horizontal,
- Indent: formatCellStyle.Alignment.Indent,
- JustifyLastLine: formatCellStyle.Alignment.JustifyLastLine,
- ReadingOrder: formatCellStyle.Alignment.ReadingOrder,
- RelativeIndent: formatCellStyle.Alignment.RelativeIndent,
- ShrinkToFit: formatCellStyle.Alignment.ShrinkToFit,
- TextRotation: formatCellStyle.Alignment.TextRotation,
- Vertical: formatCellStyle.Alignment.Vertical,
- WrapText: formatCellStyle.Alignment.WrapText,
+func setAlignment(formatStyle *formatStyle) *xlsxAlignment {
+ var alignment xlsxAlignment
+ if formatStyle.Alignment != nil {
+ alignment.Horizontal = formatStyle.Alignment.Horizontal
+ alignment.Indent = formatStyle.Alignment.Indent
+ alignment.JustifyLastLine = formatStyle.Alignment.JustifyLastLine
+ alignment.ReadingOrder = formatStyle.Alignment.ReadingOrder
+ alignment.RelativeIndent = formatStyle.Alignment.RelativeIndent
+ alignment.ShrinkToFit = formatStyle.Alignment.ShrinkToFit
+ alignment.TextRotation = formatStyle.Alignment.TextRotation
+ alignment.Vertical = formatStyle.Alignment.Vertical
+ alignment.WrapText = formatStyle.Alignment.WrapText
}
- return true, &alignment
+ return &alignment
}
// setBorders provides function to add border elements in the styles.xml by
// given borders format settings.
-func setBorders(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
+func setBorders(formatStyle *formatStyle) *xlsxBorder {
var styles = []string{
"none",
"thin",
@@ -2071,7 +2161,7 @@ func setBorders(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
}
var border xlsxBorder
- for _, v := range formatCellStyle.Border {
+ for _, v := range formatStyle.Border {
if v.Style > 13 || v.Style < 0 {
continue
}
@@ -2100,9 +2190,7 @@ func setBorders(style *xlsxStyleSheet, formatCellStyle *formatCellStyle) int {
border.DiagonalDown = true
}
}
- style.Borders.Count++
- style.Borders.Border = append(style.Borders.Border, &border)
- return style.Borders.Count - 1
+ return &border
}
// setCellXfs provides function to set describes all of the formatting for a
@@ -2228,6 +2316,362 @@ func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) {
}
}
+// SetConditionalFormat provides function to create conditional formatting rule
+// for cell value. Conditional formatting is a feature of Excel which allows you
+// to apply a format to a cell or a range of cells based on certain criteria.
+//
+// The type option is a required parameter and it has no default value.
+// Allowable type values and their associated parameters are:
+//
+// Type | Parameters
+// ---------------+------------------------------------
+// cell | criteria
+// | value
+// | minimum
+// | maximum
+// date | criteria
+// | value
+// | minimum
+// | maximum
+// time_period | criteria
+// text | criteria
+// | value
+// average | criteria
+// duplicate | (none)
+// unique | (none)
+// top | criteria
+// | value
+// bottom | criteria
+// | value
+// blanks | (none)
+// no_blanks | (none)
+// errors | (none)
+// no_errors | (none)
+// 2_color_scale | min_type
+// | max_type
+// | min_value
+// | max_value
+// | min_color
+// | max_color
+// 3_color_scale | min_type
+// | mid_type
+// | max_type
+// | min_value
+// | mid_value
+// | max_value
+// | min_color
+// | mid_color
+// | max_color
+// data_bar | min_type
+// | max_type
+// | min_value
+// | max_value
+// | bar_color
+// formula | criteria
+//
+// The criteria parameter is used to set the criteria by which the cell data
+// will be evaluated. It has no default value. The most common criteria as
+// applied to {'type': 'cell'} are:
+//
+// between |
+// not between |
+// equal to | ==
+// not equal to | !=
+// greater than | >
+// less than | <
+// greater than or equal to | >=
+// less than or equal to | <=
+//
+// You can either use Excel's textual description strings, in the first column
+// above, or the more common symbolic alternatives.
+//
+// Additional criteria which are specific to other conditional format types are
+// shown in the relevant sections below.
+//
+// value: The value is generally used along with the criteria parameter to set
+// the rule by which the cell data will be evaluated:
+//
+// xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format))
+//
+// The value property can also be an cell reference:
+//
+// xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"$C$1"}]`, format))
+//
+// type: format - The format parameter is used to specify the format that will
+// be applied to the cell when the conditional formatting criterion is met. The
+// format is created using the NewConditionalStyle() method in the same way as
+// cell formats:
+//
+// format1, err = xlsx.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
+// if err != nil {
+// fmt.Println(err)
+// }
+// xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format1))
+//
+// Note: In Excel, a conditional format is superimposed over the existing cell
+// format and not all cell format properties can be modified. Properties that
+// cannot be modified in a conditional format are font name, font size,
+// superscript and subscript, diagonal borders, all alignment properties and all
+// protection properties.
+//
+// Excel specifies some default formats to be used with conditional formatting.
+// These can be replicated using the following excelize formats:
+//
+// // Rose format for bad conditional.
+// format1, err = xlsx.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
+//
+// // Light yellow format for neutral conditional.
+// format2, err = xlsx.NewConditionalStyle(`{"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`)
+//
+// // Light green format for good conditional.
+// format3, err = xlsx.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`)
+// t.Log(err)
+//
+// type: minimum - The minimum parameter is used to set the lower limiting value
+// when the criteria is either "between" or "not between".
+//
+// // Hightlight cells rules: between...
+// xlsx.SetConditionalFormat("Sheet1", "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
+//
+// type: maximum - The maximum parameter is used to set the upper limiting value
+// when the criteria is either "between" or "not between". See the previous
+// example.
+//
+// type: average - The average type is used to specify Excel's "Average" style
+// conditional format:
+//
+// // Top/Bottom rules: Above Average...
+// xlsx.SetConditionalFormat("Sheet1", "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format1))
+//
+// // Top/Bottom rules: Below Average...
+// xlsx.SetConditionalFormat("Sheet1", "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format2))
+//
+// type: duplicate - The duplicate type is used to highlight duplicate cells in a range:
+//
+// // Hightlight cells rules: Duplicate Values...
+// xlsx.SetConditionalFormat("Sheet1", "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format1))
+//
+// type: unique - The unique type is used to highlight unique cells in a range:
+//
+// // Hightlight cells rules: Not Equal To...
+// xlsx.SetConditionalFormat("Sheet1", "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format1))
+//
+// 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.
+// xlsx.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6"}]`, format1))
+//
+// The criteria can be used to indicate that a percentage condition is required:
+//
+// xlsx.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
+//
+// type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2
+// Color Scale" style conditional format:
+//
+// // Color scales: 2 color.
+// xlsx.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
+//
+// This conditional type can be modified with min_type, max_type, min_value,
+// max_value, min_color and max_color, see below.
+//
+// type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3
+// Color Scale" style conditional format:
+//
+// // Color scales: 3 color.
+// xlsx.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
+//
+// This conditional type can be modified with min_type, mid_type, max_type,
+// min_value, mid_value, max_value, min_color, mid_color and max_color, see
+// below.
+//
+// type: data_bar - The data_bar type is used to specify Excel's "Data Bar"
+// style conditional format.
+//
+// min_type - The min_type and max_type properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_type is available for 3_color_scale. The properties are used as follows:
+//
+// // Data Bars: Gradient Fill.
+// xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+//
+// The available min/mid/max types are:
+//
+// min (for min_type only)
+// num
+// percent
+// percentile
+// formula
+// max (for max_type only)
+//
+// mid_type - Used for 3_color_scale. Same as min_type, see above.
+//
+// max_type - Same as min_type, see above.
+//
+// min_value - The min_value and max_value properties are available when the
+// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The
+// mid_value is available for 3_color_scale.
+//
+// mid_value - Used for 3_color_scale. Same as min_value, see above.
+//
+// max_value - Same as min_value, see above.
+//
+// min_color - The min_color and max_color properties are available when the
+// conditional formatting type is 2_color_scale, 3_color_scale or data_bar.
+// The mid_color is available for 3_color_scale. The properties are used as
+// follows:
+//
+// // Color scales: 3 color.
+// xlsx.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
+//
+// mid_color - Used for 3_color_scale. Same as min_color, see above.
+//
+// max_color - Same as min_color, see above.
+//
+// bar_color - Used for data_bar. Same as min_color, see above.
+//
+func (f *File) SetConditionalFormat(sheet, area, formatSet string) {
+ var format []*formatConditional
+ json.Unmarshal([]byte(formatSet), &format)
+
+ drawContFmtFunc := map[string]func(p int, ct string, fmtCond *formatConditional) *xlsxCfRule{
+ "cellIs": drawCondFmtCellIs,
+ "top10": drawCondFmtTop10,
+ "aboveAverage": drawCondFmtAboveAverage,
+ "duplicateValues": drawCondFmtDuplicateUniqueValues,
+ "uniqueValues": drawCondFmtDuplicateUniqueValues,
+ "2_color_scale": drawCondFmtColorScale,
+ "3_color_scale": drawCondFmtColorScale,
+ "dataBar": drawCondFmtDataBar,
+ }
+
+ xlsx := f.workSheetReader(sheet)
+ cfRule := []*xlsxCfRule{}
+ for p, v := range format {
+ var vt, ct string
+ var ok bool
+ // "type" is a required parameter, check for valid validation types.
+ vt, ok = validType[v.Type]
+ if !ok {
+ continue
+ }
+ // Check for valid criteria types.
+ ct, ok = criteriaType[v.Criteria]
+ if !ok {
+ continue
+ }
+
+ drawfunc, ok := drawContFmtFunc[vt]
+ if ok {
+ cfRule = append(cfRule, drawfunc(p, ct, v))
+ }
+ }
+
+ xlsx.ConditionalFormatting = append(xlsx.ConditionalFormatting, &xlsxConditionalFormatting{
+ SQRef: area,
+ CfRule: cfRule,
+ })
+}
+
+// drawCondFmtCellIs provides function to create conditional formatting rule for
+// cell value (include between, not between, equal, not equal, greater than and
+// less than) by given priority, criteria type and format settings.
+func drawCondFmtCellIs(p int, ct string, format *formatConditional) *xlsxCfRule {
+ c := &xlsxCfRule{
+ Priority: p + 1,
+ Type: validType[format.Type],
+ Operator: ct,
+ DxfID: &format.Format,
+ }
+ // "between" and "not between" criteria require 2 values.
+ _, ok := map[string]bool{"between": true, "notBetween": true}[ct]
+ if ok {
+ c.Formula = append(c.Formula, format.Minimum)
+ c.Formula = append(c.Formula, format.Maximum)
+ }
+ _, ok = map[string]bool{"equal": true, "notEqual": true, "greaterThan": true, "lessThan": true}[ct]
+ if ok {
+ c.Formula = append(c.Formula, format.Value)
+ }
+ return c
+}
+
+// drawCondFmtTop10 provides function to create conditional formatting rule for
+// top N (default is top 10) by given priority, criteria type and format
+// settings.
+func drawCondFmtTop10(p int, ct string, format *formatConditional) *xlsxCfRule {
+ c := &xlsxCfRule{
+ Priority: p + 1,
+ Type: validType[format.Type],
+ Rank: 10,
+ DxfID: &format.Format,
+ Percent: format.Percent,
+ }
+ rank, err := strconv.Atoi(format.Value)
+ if err == nil {
+ c.Rank = rank
+ }
+ return c
+}
+
+// drawCondFmtAboveAverage provides function to create conditional formatting
+// rule for above average and below average by given priority, criteria type and
+// format settings.
+func drawCondFmtAboveAverage(p int, ct string, format *formatConditional) *xlsxCfRule {
+ return &xlsxCfRule{
+ Priority: p + 1,
+ Type: validType[format.Type],
+ AboveAverage: &format.AboveAverage,
+ DxfID: &format.Format,
+ }
+}
+
+// drawCondFmtDuplicateUniqueValues provides function to create conditional
+// formatting rule for duplicate and unique values by given priority, criteria
+// type and format settings.
+func drawCondFmtDuplicateUniqueValues(p int, ct string, format *formatConditional) *xlsxCfRule {
+ return &xlsxCfRule{
+ Priority: p + 1,
+ Type: validType[format.Type],
+ DxfID: &format.Format,
+ }
+}
+
+// drawCondFmtColorScale provides function to create conditional formatting rule
+// for color scale (include 2 color scale and 3 color scale) by given priority,
+// criteria type and format settings.
+func drawCondFmtColorScale(p int, ct string, format *formatConditional) *xlsxCfRule {
+ c := &xlsxCfRule{
+ Priority: p + 1,
+ Type: "colorScale",
+ ColorScale: &xlsxColorScale{
+ Cfvo: []*xlsxCfvo{
+ {Type: format.MinType},
+ },
+ Color: []*xlsxColor{
+ {RGB: getPaletteColor(format.MinColor)},
+ },
+ },
+ }
+ if validType[format.Type] == "3_color_scale" {
+ c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MidType, Val: 50})
+ c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MidColor)})
+ }
+ c.ColorScale.Cfvo = append(c.ColorScale.Cfvo, &xlsxCfvo{Type: format.MaxType})
+ c.ColorScale.Color = append(c.ColorScale.Color, &xlsxColor{RGB: getPaletteColor(format.MaxColor)})
+ return c
+}
+
+// drawCondFmtDataBar provides function to create conditional formatting rule
+// for data bar by given priority, criteria type and format settings.
+func drawCondFmtDataBar(p int, ct string, format *formatConditional) *xlsxCfRule {
+ return &xlsxCfRule{
+ Priority: p + 1,
+ Type: validType[format.Type],
+ DataBar: &xlsxDataBar{
+ Cfvo: []*xlsxCfvo{{Type: format.MinType}, {Type: format.MaxType}},
+ Color: []*xlsxColor{{RGB: getPaletteColor(format.BarColor)}},
+ },
+ }
+}
+
// getPaletteColor provides function to convert the RBG color by given string.
func getPaletteColor(color string) string {
return "FF" + strings.Replace(strings.ToUpper(color), "#", "", -1)
diff --git a/test/Workbook1.xlsx b/test/Workbook1.xlsx
index 8e7c88a..a8328cf 100644
--- a/test/Workbook1.xlsx
+++ b/test/Workbook1.xlsx
Binary files differ
diff --git a/xmlStyles.go b/xmlStyles.go
index 43bf945..8477804 100644
--- a/xmlStyles.go
+++ b/xmlStyles.go
@@ -62,7 +62,7 @@ type xlsxColor struct {
Auto bool `xml:"auto,attr,omitempty"`
RGB string `xml:"rgb,attr,omitempty"`
Indexed int `xml:"indexed,attr,omitempty"`
- Theme int `xml:"theme,attr,omitempty"`
+ Theme *int `xml:"theme,attr"`
Tint float64 `xml:"tint,attr,omitempty"`
}
@@ -75,14 +75,20 @@ type xlsxFonts struct {
// font directly maps the font element.
type font struct {
- B bool `xml:"b,omitempty"`
- I bool `xml:"i,omitempty"`
- U *attrValString `xml:"u"`
- Sz *attrValInt `xml:"sz"`
- Color *xlsxColor `xml:"color"`
- Name *attrValString `xml:"name"`
- Family *attrValInt `xml:"family"`
- Scheme *attrValString `xml:"scheme"`
+ Name *attrValString `xml:"name"`
+ Charset *attrValInt `xml:"charset"`
+ Family *attrValInt `xml:"family"`
+ B bool `xml:"b,omitempty"`
+ I bool `xml:"i,omitempty"`
+ Strike bool `xml:"strike,omitempty"`
+ Outline bool `xml:"outline,omitempty"`
+ Shadow bool `xml:"shadow,omitempty"`
+ Condense bool `xml:"condense,omitempty"`
+ Extend bool `xml:"extend,omitempty"`
+ Color *xlsxColor `xml:"color"`
+ Sz *attrValInt `xml:"sz"`
+ U *attrValString `xml:"u"`
+ Scheme *attrValString `xml:"scheme"`
}
// xlsxFont directly maps the font element. This element defines the properties
@@ -245,6 +251,17 @@ type xlsxDxf struct {
Dxf string `xml:",innerxml"`
}
+// dxf directly maps the dxf element.
+type dxf struct {
+ Font *font `xml:"font"`
+ NumFmt *xlsxNumFmt `xml:"numFmt"`
+ Fill *xlsxFill `xml:"fill"`
+ Alignment *xlsxAlignment `xml:"alignment"`
+ Border *xlsxBorder `xml:"border"`
+ Protection *xlsxProtection `xml:"protection"`
+ ExtLst *xlsxExt `xml:"extLst"`
+}
+
// xlsxTableStyles directly maps the tableStyles element. This element
// represents a collection of Table style definitions for Table styles and
// PivotTable styles used in this workbook. It consists of a sequence of
@@ -302,8 +319,8 @@ type formatFont struct {
Color string `json:"color"`
}
-// formatCellStyle directly maps the styles settings of the cells.
-type formatCellStyle struct {
+// formatStyle directly maps the styles settings of the cells.
+type formatStyle struct {
Border []struct {
Type string `json:"type"`
Color string `json:"color"`
diff --git a/xmlWorksheet.go b/xmlWorksheet.go
index ca757fd..f69a484 100644
--- a/xmlWorksheet.go
+++ b/xmlWorksheet.go
@@ -365,8 +365,65 @@ type xlsxPhoneticPr struct {
// condition is true. This collection expresses conditional formatting rules
// applied to a particular cell or range.
type xlsxConditionalFormatting struct {
- SQRef string `xml:"sqref,attr,omitempty"`
- CfRule string `xml:",innerxml"`
+ SQRef string `xml:"sqref,attr,omitempty"`
+ CfRule []*xlsxCfRule `xml:"cfRule"`
+}
+
+// xlsxCfRule (Conditional Formatting Rule) represents a description of a
+// conditional formatting rule.
+type xlsxCfRule struct {
+ AboveAverage *bool `xml:"aboveAverage,attr"`
+ Bottom bool `xml:"bottom,attr,omitempty"`
+ DxfID *int `xml:"dxfId,attr"`
+ EqualAverage bool `xml:"equalAverage,attr,omitempty"`
+ Operator string `xml:"operator,attr,omitempty"`
+ Percent bool `xml:"percent,attr,omitempty"`
+ Priority int `xml:"priority,attr,omitempty"`
+ Rank int `xml:"rank,attr,omitempty"`
+ StdDev int `xml:"stdDev,attr,omitempty"`
+ StopIfTrue bool `xml:"stopIfTrue,attr,omitempty"`
+ Text string `xml:"text,attr,omitempty"`
+ TimePeriod string `xml:"timePeriod,attr,omitempty"`
+ Type string `xml:"type,attr,omitempty"`
+ Formula []string `xml:"formula,omitempty"`
+ ColorScale *xlsxColorScale `xml:"colorScale"`
+ DataBar *xlsxDataBar `xml:"dataBar"`
+ IconSet *xlsxIconSet `xml:"iconSet"`
+ ExtLst *xlsxExtLst `xml:"extLst"`
+}
+
+// xlsxColorScale (Color Scale) describes a gradated color scale in this
+// conditional formatting rule.
+type xlsxColorScale struct {
+ Cfvo []*xlsxCfvo `xml:"cfvo"`
+ Color []*xlsxColor `xml:"color"`
+}
+
+// dataBar (Data Bar) describes a data bar conditional formatting rule.
+type xlsxDataBar struct {
+ MaxLength int `xml:"maxLength,attr,omitempty"`
+ MinLength int `xml:"minLength,attr,omitempty"`
+ ShowValue bool `xml:"showValue,attr,omitempty"`
+ Cfvo []*xlsxCfvo `xml:"cfvo"`
+ Color []*xlsxColor `xml:"color"`
+}
+
+// xlsxIconSet (Icon Set) describes an icon set conditional formatting rule.
+type xlsxIconSet struct {
+ Cfvo []*xlsxCfvo `xml:"cfvo"`
+ IconSet string `xml:"iconSet,attr,omitempty"`
+ ShowValue bool `xml:"showValue,attr,omitempty"`
+ Percent bool `xml:"percent,attr,omitempty"`
+ Reverse bool `xml:"reverse,attr,omitempty"`
+}
+
+// cfvo (Conditional Format Value Object) describes the values of the
+// interpolation points in a gradient scale.
+type xlsxCfvo struct {
+ Gte bool `xml:"gte,attr,omitempty"`
+ Type string `xml:"type,attr,omitempty"`
+ Val int `xml:"val,attr"`
+ ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxHyperlinks directly maps the hyperlinks element in the namespace
@@ -465,3 +522,28 @@ type formatPanes struct {
Pane string `json:"pane"`
} `json:"panes"`
}
+
+// formatConditional
+type formatConditional struct {
+ Type string `json:"type"`
+ AboveAverage bool `json:"above_average"`
+ Percent bool `json:"percent"`
+ Format int `json:"format"`
+ Criteria string `json:"criteria"`
+ Value string `json:"value,omitempty"`
+ Minimum string `json:"minimum,omitempty"`
+ Maximum string `json:"maximum,omitempty"`
+ MinType string `json:"min_type,omitempty"`
+ MidType string `json:"mid_type,omitempty"`
+ MaxType string `json:"max_type,omitempty"`
+ MinValue string `json:"min_value,omitempty"`
+ MidValue string `json:"mid_value,omitempty"`
+ MaxValue string `json:"max_value,omitempty"`
+ MinColor string `json:"min_color,omitempty"`
+ MidColor string `json:"mid_color,omitempty"`
+ MaxColor string `json:"max_color,omitempty"`
+ MinLength string `json:"min_length,omitempty"`
+ MaxLength string `json:"max_length,omitempty"`
+ MultiRange string `json:"multi_range,omitempty"`
+ BarColor string `json:"bar_color,omitempty"`
+}