Browse Source

- Init conditional format support, relate issue #75;
- go test and godoc updated

Ri Xu 8 năm trước cách đây
mục cha
commit
67636039f6
5 tập tin đã thay đổi với 700 bổ sung97 xóa
  1. 60 0
      excelize_test.go
  2. 528 84
      styles.go
  3. BIN
      test/Workbook1.xlsx
  4. 28 11
      xmlStyles.go
  5. 84 2
      xmlWorksheet.go

+ 60 - 0
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)
+}

+ 528 - 84
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)

BIN
test/Workbook1.xlsx


+ 28 - 11
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"`

+ 84 - 2
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"`
+}