Browse Source

Merge pull request #381 from rentiansheng/master

support excel data validation
Ryan Hollis 6 years ago
parent
commit
d014214427
7 changed files with 575 additions and 66 deletions
  1. 23 12
      cell.go
  2. 68 9
      col.go
  3. 174 0
      datavalidation.go
  4. 195 0
      datavalidation_test.go
  5. 44 0
      lib.go
  6. 30 32
      sheet.go
  7. 41 13
      xmlWorksheet.go

+ 23 - 12
cell.go

@@ -42,17 +42,18 @@ func (ct CellType) Ptr() *CellType {
 // Cell is a high level structure intended to provide user access to
 // the contents of Cell within an xlsx.Row.
 type Cell struct {
-	Row          *Row
-	Value        string
-	formula      string
-	style        *Style
-	NumFmt       string
-	parsedNumFmt *parsedNumberFormat
-	date1904     bool
-	Hidden       bool
-	HMerge       int
-	VMerge       int
-	cellType     CellType
+	Row            *Row
+	Value          string
+	formula        string
+	style          *Style
+	NumFmt         string
+	parsedNumFmt   *parsedNumberFormat
+	date1904       bool
+	Hidden         bool
+	HMerge         int
+	VMerge         int
+	cellType       CellType
+	DataValidation *xlsxCellDataValidation
 }
 
 // CellInterface defines the public API of the Cell.
@@ -63,7 +64,7 @@ type CellInterface interface {
 
 // NewCell creates a cell and adds it to a row.
 func NewCell(r *Row) *Cell {
-	return &Cell{Row: r, NumFmt: "general"}
+	return &Cell{Row: r}
 }
 
 // Merge with other cells, horizontally and/or vertically.
@@ -129,6 +130,11 @@ func (c *Cell) SetFloatWithFormat(n float64, format string) {
 	c.formula = ""
 }
 
+// SetCellFormat set cell value  format
+func (c *Cell) SetFormat(format string) {
+	c.NumFmt = format
+}
+
 // DateTimeOptions are additional options for exporting times
 type DateTimeOptions struct {
 	// Location allows calculating times in other timezones/locations
@@ -361,3 +367,8 @@ func (c *Cell) FormattedValue() (string, error) {
 	}
 	return returnVal, err
 }
+
+// SetDataValidation set data validation
+func (c *Cell) SetDataValidation(dd *xlsxCellDataValidation) {
+	c.DataValidation = dd
+}

+ 68 - 9
col.go

@@ -2,17 +2,20 @@ package xlsx
 
 // Default column width in excel
 const ColWidth = 9.5
+const Excel2006MaxRowIndex = 1048576
+const Excel2006MinRowIndex = 1
 
 type Col struct {
-	Min          int
-	Max          int
-	Hidden       bool
-	Width        float64
-	Collapsed    bool
-	OutlineLevel uint8
-	numFmt       string
-	parsedNumFmt *parsedNumberFormat
-	style        *Style
+	Min            int
+	Max            int
+	Hidden         bool
+	Width          float64
+	Collapsed      bool
+	OutlineLevel   uint8
+	numFmt         string
+	parsedNumFmt   *parsedNumberFormat
+	style          *Style
+	DataValidation []*xlsxCellDataValidation
 }
 
 // SetType will set the format string of a column based on the type that you want to set it to.
@@ -47,3 +50,59 @@ func (c *Col) GetStyle() *Style {
 func (c *Col) SetStyle(style *Style) {
 	c.style = style
 }
+
+// SetDataValidation set data validation with start,end ; start or end  = 0  equal all column
+func (c *Col) SetDataValidation(dd *xlsxCellDataValidation, start, end int) {
+
+	if 0 == start {
+		start = Excel2006MinRowIndex
+	} else {
+		start = start + 1
+	}
+
+	if 0 == end {
+		end = Excel2006MinRowIndex
+	} else if end < Excel2006MaxRowIndex {
+		end = end + 1
+	}
+
+	dd.minRow = start
+	dd.maxRow = end
+
+	tmpDD := make([]*xlsxCellDataValidation, 0)
+	for _, item := range c.DataValidation {
+		if item.maxRow < dd.minRow {
+			tmpDD = append(tmpDD, item) //No intersection
+		} else if item.minRow > dd.maxRow {
+			tmpDD = append(tmpDD, item) //No intersection
+		} else if dd.minRow <= item.minRow && dd.maxRow >= item.maxRow {
+			continue //union , item can be ignored
+		} else if dd.minRow >= item.minRow {
+			//Split into three or two, Newly added object, intersect with the current object in the lower half
+			tmpSplit := new(xlsxCellDataValidation)
+			*tmpSplit = *item
+
+			if dd.minRow > item.minRow { //header whetherneed to split
+				item.maxRow = dd.minRow - 1
+				tmpDD = append(tmpDD, item)
+			}
+			if dd.maxRow < tmpSplit.maxRow { //footer whetherneed to split
+				tmpSplit.minRow = dd.maxRow + 1
+				tmpDD = append(tmpDD, tmpSplit)
+			}
+
+		} else {
+			item.minRow = dd.maxRow + 1
+			tmpDD = append(tmpDD, item)
+		}
+	}
+	tmpDD = append(tmpDD, dd)
+	c.DataValidation = tmpDD
+
+}
+
+// SetDataValidationWithStart set data validation with start
+func (c *Col) SetDataValidationWithStart(dd *xlsxCellDataValidation, start int) {
+	//2006 excel all row 1048576
+	c.SetDataValidation(dd, start, Excel2006MaxRowIndex)
+}

+ 174 - 0
datavalidation.go

@@ -0,0 +1,174 @@
+package xlsx
+
+import (
+	"fmt"
+	"strings"
+)
+
+type DataValidationType int
+
+// Data validation types
+const (
+	_DataValidationType = iota
+	typeNone            //inline use
+	DataValidationTypeCustom
+	DataValidationTypeDate
+	DataValidationTypeDecimal
+	typeList //inline use
+	DataValidationTypeTextLeng
+	DataValidationTypeTime
+	// DataValidationTypeWhole Integer
+	DataValidationTypeWhole
+)
+
+const (
+	// dataValidationFormulaStrLen 255 characters+ 2 quotes
+	dataValidationFormulaStrLen = 257
+	// dataValidationFormulaStrLenErr
+	dataValidationFormulaStrLenErr = "data validation must be 0-255 characters"
+)
+
+type DataValidationErrorStyle int
+
+// Data validation error styles
+const (
+	_ DataValidationErrorStyle = iota
+	StyleStop
+	StyleWarning
+	StyleInformation
+)
+
+// Data validation error styles
+const (
+	styleStop        = "stop"
+	styleWarning     = "warning"
+	styleInformation = "information"
+)
+
+// DataValidationOperator operator enum
+type DataValidationOperator int
+
+// Data validation operators
+const (
+	_DataValidationOperator = iota
+	DataValidationOperatorBetween
+	DataValidationOperatorEqual
+	DataValidationOperatorGreaterThan
+	DataValidationOperatorGreaterThanOrEqual
+	DataValidationOperatorLessThan
+	DataValidationOperatorLessThanOrEqual
+	DataValidationOperatorNotBetween
+	DataValidationOperatorNotEqual
+)
+
+// NewXlsxCellDataValidation return data validation struct
+func NewXlsxCellDataValidation(allowBlank, ShowInputMessage, showErrorMessage bool) *xlsxCellDataValidation {
+	return &xlsxCellDataValidation{
+		AllowBlank:       convBoolToStr(allowBlank),
+		ShowErrorMessage: convBoolToStr(showErrorMessage),
+		ShowInputMessage: convBoolToStr(ShowInputMessage),
+	}
+}
+
+// SetError set error notice
+func (dd *xlsxCellDataValidation) SetError(style DataValidationErrorStyle, title, msg *string) {
+	dd.Error = msg
+	dd.ErrorTitle = title
+	strStyle := styleStop
+	switch style {
+	case StyleStop:
+		strStyle = styleStop
+	case StyleWarning:
+		strStyle = styleWarning
+	case StyleInformation:
+		strStyle = styleInformation
+
+	}
+	dd.ErrorStyle = &strStyle
+}
+
+// SetInput set prompt notice
+func (dd *xlsxCellDataValidation) SetInput(title, msg *string) {
+	dd.PromptTitle = title
+	dd.Prompt = msg
+}
+
+// SetDropList data validation list
+func (dd *xlsxCellDataValidation) SetDropList(keys []string) error {
+	formula := "\"" + strings.Join(keys, ",") + "\""
+	if dataValidationFormulaStrLen < len(formula) {
+		return fmt.Errorf(dataValidationFormulaStrLenErr)
+	}
+	dd.Formula1 = formula
+	dd.Type = convDataValidationType(typeList)
+	return nil
+}
+
+// SetDropList data validation range
+func (dd *xlsxCellDataValidation) SetRange(f1, f2 int, t DataValidationType, o DataValidationOperator) error {
+	formula1 := fmt.Sprintf("%d", f1)
+	formula2 := fmt.Sprintf("%d", f2)
+
+	switch o {
+	case DataValidationOperatorBetween:
+		if f1 > f2 {
+			tmp := formula1
+			formula1 = formula2
+			formula2 = tmp
+		}
+	case DataValidationOperatorNotBetween:
+		if f1 > f2 {
+			tmp := formula1
+			formula1 = formula2
+			formula2 = tmp
+		}
+	}
+
+	dd.Formula1 = formula1
+	dd.Formula2 = formula2
+	dd.Type = convDataValidationType(t)
+	dd.Operator = convDataValidationOperatior(o)
+	return nil
+}
+
+// convBoolToStr  convert boolean to string , false to 0, true to 1
+func convBoolToStr(bl bool) string {
+	if bl {
+		return "1"
+	}
+	return "0"
+}
+
+// convDataValidationType get excel data validation type
+func convDataValidationType(t DataValidationType) string {
+	typeMap := map[DataValidationType]string{
+		typeNone:                   "none",
+		DataValidationTypeCustom:   "custom",
+		DataValidationTypeDate:     "date",
+		DataValidationTypeDecimal:  "decimal",
+		typeList:                   "list",
+		DataValidationTypeTextLeng: "textLength",
+		DataValidationTypeTime:     "time",
+		DataValidationTypeWhole:    "whole",
+	}
+
+	return typeMap[t]
+
+}
+
+// convDataValidationOperatior get excel data validation operator
+func convDataValidationOperatior(o DataValidationOperator) string {
+	typeMap := map[DataValidationOperator]string{
+		DataValidationOperatorBetween:            "between",
+		DataValidationOperatorEqual:              "equal",
+		DataValidationOperatorGreaterThan:        "greaterThan",
+		DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual",
+		DataValidationOperatorLessThan:           "lessThan",
+		DataValidationOperatorLessThanOrEqual:    "lessThanOrEqual",
+		DataValidationOperatorNotBetween:         "notBetween",
+		DataValidationOperatorNotEqual:           "notEqual",
+	}
+
+	return typeMap[o]
+
+}

+ 195 - 0
datavalidation_test.go

@@ -0,0 +1,195 @@
+package xlsx
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestDataValidation(t *testing.T) {
+	var file *File
+	var sheet *Sheet
+	var row *Row
+	var cell *Cell
+	var err error
+	var title string = "cell"
+	var msg string = "cell msg"
+
+	file = NewFile()
+	sheet, err = file.AddSheet("Sheet1")
+	if err != nil {
+		fmt.Printf(err.Error())
+	}
+	row = sheet.AddRow()
+	cell = row.AddCell()
+	cell.Value = "a1"
+
+	dd := NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"a1", "a2", "a3"})
+
+	dd.SetInput(&title, &msg)
+	cell.SetDataValidation(dd)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"c1", "c2", "c3"})
+	title = "col c"
+	dd.SetInput(&title, &msg)
+	sheet.Col(2).SetDataValidation(dd, 0, 0)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"d", "d1", "d2"})
+	title = "col d range"
+	dd.SetInput(&title, &msg)
+	sheet.Col(3).SetDataValidation(dd, 3, 7)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"e1", "e2", "e3"})
+	title = "col e start 3"
+	dd.SetInput(&title, &msg)
+	sheet.Col(4).SetDataValidationWithStart(dd, 1)
+
+	index := 5
+	rowIndex := 1
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(15, 4, DataValidationTypeTextLeng, DataValidationOperatorBetween)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThanOrEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThan)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThan)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThanOrEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotBetween)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	rowIndex++
+	index = 5
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(4, 15, DataValidationTypeWhole, DataValidationOperatorBetween)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThanOrEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThan)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThanOrEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorNotEqual)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetRange(10, 50, DataValidationTypeWhole, DataValidationOperatorNotBetween)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	if err != nil {
+		fmt.Printf(err.Error())
+	}
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"1", "2", "4"})
+	dd1 := NewXlsxCellDataValidation(true, true, true)
+	dd1.SetDropList([]string{"11", "22", "44"})
+	dd2 := NewXlsxCellDataValidation(true, true, true)
+	dd2.SetDropList([]string{"111", "222", "444"})
+	sheet.Col(12).SetDataValidation(dd, 2, 10)
+	sheet.Col(12).SetDataValidation(dd1, 3, 4)
+	sheet.Col(12).SetDataValidation(dd2, 5, 7)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"1", "2", "4"})
+	dd1 = NewXlsxCellDataValidation(true, true, true)
+	dd1.SetDropList([]string{"11", "22", "44"})
+	sheet.Col(13).SetDataValidation(dd, 2, 10)
+	sheet.Col(13).SetDataValidation(dd1, 1, 2)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"1", "2", "4"})
+	dd1 = NewXlsxCellDataValidation(true, true, true)
+	dd1.SetDropList([]string{"11", "22", "44"})
+	sheet.Col(14).SetDataValidation(dd, 2, 10)
+	sheet.Col(14).SetDataValidation(dd1, 1, 5)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"1", "2", "4"})
+	dd1 = NewXlsxCellDataValidation(true, true, true)
+	dd1.SetDropList([]string{"11", "22", "44"})
+	sheet.Col(15).SetDataValidation(dd, 2, 10)
+	sheet.Col(15).SetDataValidation(dd1, 1, 10)
+
+	dd = NewXlsxCellDataValidation(true, true, true)
+	dd.SetDropList([]string{"1", "2", "4"})
+	dd1 = NewXlsxCellDataValidation(true, true, true)
+	dd1.SetDropList([]string{"11", "22", "44"})
+	dd2 = NewXlsxCellDataValidation(true, true, true)
+	dd2.SetDropList([]string{"111", "222", "444"})
+	sheet.Col(16).SetDataValidation(dd, 10, 20)
+	sheet.Col(16).SetDataValidation(dd1, 2, 4)
+	sheet.Col(16).SetDataValidation(dd2, 21, 30)
+
+	file.Save("datavalidation.xlsx")
+
+}
+
+func TestReadDataValidation(t *testing.T) {
+	file, err := OpenFile("datavalidation.xlsx")
+	if nil != err {
+		t.Errorf(err.Error())
+		return
+	}
+	err = file.Save("datavalidation_read.xlsx")
+	if nil != err {
+		t.Errorf(err.Error())
+		return
+	}
+}

+ 44 - 0
lib.go

@@ -699,6 +699,50 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 	sheet.SheetFormat.DefaultRowHeight = worksheet.SheetFormatPr.DefaultRowHeight
 	sheet.SheetFormat.OutlineLevelCol = worksheet.SheetFormatPr.OutlineLevelCol
 	sheet.SheetFormat.OutlineLevelRow = worksheet.SheetFormatPr.OutlineLevelRow
+	if nil != worksheet.DataValidations {
+		for _, dd := range worksheet.DataValidations.DataValidattion {
+			sqrefArr := strings.Split(dd.Sqref, " ")
+			for _, sqref := range sqrefArr {
+				parts := strings.Split(sqref, ":")
+
+				minCol, minRow, err := GetCoordsFromCellIDString(parts[0])
+				if nil != err {
+					return fmt.Errorf("data validation %s", err.Error())
+				}
+
+				if 2 == len(parts) {
+					maxCol, maxRow, err := GetCoordsFromCellIDString(parts[1])
+					if nil != err {
+						return fmt.Errorf("data validation %s", err.Error())
+					}
+
+					if minCol == maxCol && minRow == maxRow {
+						newDD := new(xlsxCellDataValidation)
+						*newDD = *dd
+						newDD.Sqref = ""
+						sheet.Cell(minRow, minCol).SetDataValidation(newDD)
+					} else {
+						// one col mutli dd , error todo
+						for i := minCol; i <= maxCol; i++ {
+							newDD := new(xlsxCellDataValidation)
+							*newDD = *dd
+							newDD.Sqref = ""
+							sheet.Col(i).SetDataValidation(dd, minRow, maxRow)
+						}
+
+					}
+				} else {
+					newDD := new(xlsxCellDataValidation)
+					*newDD = *dd
+					newDD.Sqref = ""
+					sheet.Cell(minRow, minCol).SetDataValidation(dd)
+
+				}
+			}
+
+		}
+
+	}
 
 	result.Sheet = sheet
 	sc <- result

+ 30 - 32
sheet.go

@@ -161,42 +161,13 @@ func (s *Sheet) handleMerged() {
 	// borders to them depending on their position. If any cells required by the merge
 	// are missing, they will be allocated by s.Cell().
 	for key, cell := range merged {
-		mainstyle := cell.GetStyle()
-
-		top := mainstyle.Border.Top
-		left := mainstyle.Border.Left
-		right := mainstyle.Border.Right
-		bottom := mainstyle.Border.Bottom
-
-		// When merging cells, the upper left cell does not maintain
-		// the original borders
-		mainstyle.Border.Top = "none"
-		mainstyle.Border.Left = "none"
-		mainstyle.Border.Right = "none"
-		mainstyle.Border.Bottom = "none"
 
 		maincol, mainrow, _ := GetCoordsFromCellIDString(key)
 		for rownum := 0; rownum <= cell.VMerge; rownum++ {
 			for colnum := 0; colnum <= cell.HMerge; colnum++ {
-				tmpcell := s.Cell(mainrow+rownum, maincol+colnum)
-				style := tmpcell.GetStyle()
-				style.ApplyBorder = true
+				// make cell
+				s.Cell(mainrow+rownum, maincol+colnum)
 
-				if rownum == 0 {
-					style.Border.Top = top
-				}
-
-				if rownum == (cell.VMerge) {
-					style.Border.Bottom = bottom
-				}
-
-				if colnum == 0 {
-					style.Border.Left = left
-				}
-
-				if colnum == (cell.HMerge) {
-					style.Border.Right = right
-				}
 			}
 		}
 	}
@@ -238,7 +209,6 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 	worksheet.SheetFormatPr.DefaultColWidth = s.SheetFormat.DefaultColWidth
 
 	colsXfIdList := make([]int, len(s.Cols))
-	worksheet.Cols = &xlsxCols{Col: []xlsxCol{}}
 	for c, col := range s.Cols {
 		XfId := 0
 		if col.Min == 0 {
@@ -263,6 +233,10 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		} else {
 			customWidth = true
 		}
+		// When the cols content is empty, the cols flag is not output in the xml file.
+		if worksheet.Cols == nil {
+			worksheet.Cols = &xlsxCols{Col: []xlsxCol{}}
+		}
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 			xlsxCol{Min: col.Min,
 				Max:          col.Max,
@@ -277,6 +251,22 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		if col.OutlineLevel > maxLevelCol {
 			maxLevelCol = col.OutlineLevel
 		}
+		if nil != col.DataValidation {
+			if nil == worksheet.DataValidations {
+				worksheet.DataValidations = &xlsxCellDataValidations{}
+			}
+			colName := ColIndexToLetters(c)
+			for _, dd := range col.DataValidation {
+				if dd.minRow == dd.maxRow {
+					dd.Sqref = fmt.Sprintf("%s%d", colName, dd.minRow)
+				} else {
+					dd.Sqref = fmt.Sprintf("%s%d:%s%d", colName, dd.minRow, colName, dd.maxRow)
+				}
+				worksheet.DataValidations.DataValidattion = append(worksheet.DataValidations.DataValidattion, dd)
+
+			}
+			worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidattion)
+		}
 	}
 
 	for r, row := range s.Rows {
@@ -346,6 +336,14 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			}
 
 			xRow.C = append(xRow.C, xC)
+			if nil != cell.DataValidation {
+				if nil == worksheet.DataValidations {
+					worksheet.DataValidations = &xlsxCellDataValidations{}
+				}
+				cell.DataValidation.Sqref = xC.R
+				worksheet.DataValidations.DataValidattion = append(worksheet.DataValidations.DataValidattion, cell.DataValidation)
+				worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidattion)
+			}
 
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 				// r == rownum, c == colnum

+ 41 - 13
xmlWorksheet.go

@@ -10,19 +10,20 @@ import (
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxWorksheet struct {
-	XMLName       xml.Name          `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
-	SheetPr       xlsxSheetPr       `xml:"sheetPr"`
-	Dimension     xlsxDimension     `xml:"dimension"`
-	SheetViews    xlsxSheetViews    `xml:"sheetViews"`
-	SheetFormatPr xlsxSheetFormatPr `xml:"sheetFormatPr"`
-	Cols          *xlsxCols         `xml:"cols,omitempty"`
-	SheetData     xlsxSheetData     `xml:"sheetData"`
-	AutoFilter    *xlsxAutoFilter   `xml:"autoFilter,omitempty"`
-	MergeCells    *xlsxMergeCells   `xml:"mergeCells,omitempty"`
-	PrintOptions  xlsxPrintOptions  `xml:"printOptions"`
-	PageMargins   xlsxPageMargins   `xml:"pageMargins"`
-	PageSetUp     xlsxPageSetUp     `xml:"pageSetup"`
-	HeaderFooter  xlsxHeaderFooter  `xml:"headerFooter"`
+	XMLName         xml.Name                 `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
+	SheetPr         xlsxSheetPr              `xml:"sheetPr"`
+	Dimension       xlsxDimension            `xml:"dimension"`
+	SheetViews      xlsxSheetViews           `xml:"sheetViews"`
+	SheetFormatPr   xlsxSheetFormatPr        `xml:"sheetFormatPr"`
+	Cols            *xlsxCols                `xml:"cols,omitempty"`
+	SheetData       xlsxSheetData            `xml:"sheetData"`
+	DataValidations *xlsxCellDataValidations `xml:"dataValidations"`
+	AutoFilter      *xlsxAutoFilter          `xml:"autoFilter,omitempty"`
+	MergeCells      *xlsxMergeCells          `xml:"mergeCells,omitempty"`
+	PrintOptions    xlsxPrintOptions         `xml:"printOptions"`
+	PageMargins     xlsxPageMargins          `xml:"pageMargins"`
+	PageSetUp       xlsxPageSetUp            `xml:"pageSetup"`
+	HeaderFooter    xlsxHeaderFooter         `xml:"headerFooter"`
 }
 
 // xlsxHeaderFooter directly maps the headerFooter element in the namespace
@@ -223,6 +224,33 @@ type xlsxSheetData struct {
 	Row     []xlsxRow `xml:"row"`
 }
 
+// xlsxCellDataValidations  excel cell data validation
+type xlsxCellDataValidations struct {
+	DataValidattion []*xlsxCellDataValidation `xml:"dataValidation"`
+	Count           int                       `xml:"count,attr"`
+}
+
+// xlsxCellDataValidation single data validation
+type xlsxCellDataValidation struct {
+	AllowBlank       string  `xml:"allowBlank,attr"`         // allow empty
+	ShowInputMessage string  `xml:"showInputMessage,attr"`   // 1, true,0,false,  select cell,  Whether the input message is displayed
+	ShowErrorMessage string  `xml:"showErrorMessage,attr"`   // 1, true,0,false,  input error value, Whether the error message is displayed
+	ErrorStyle       *string `xml:"errorStyle,attr"`         //error icon style, warning, infomation,stop
+	ErrorTitle       *string `xml:"errorTitle,attr"`         // error title
+	Operator         string  `xml:"operator,attr,omitempty"` //
+	Error            *string `xml:"error,attr"`              // input error value,  notice message
+	PromptTitle      *string `xml:"promptTitle,attr"`
+	Prompt           *string `xml:"prompt,attr"`
+	Type             string  `xml:"type,attr"`  //data type, none,custom,date,decimal,list, textLength,time,whole
+	Sqref            string  `xml:"sqref,attr"` //Validity of data validation rules, cell and range, eg: A1 OR A1:A20
+	Formula1         string  `xml:"formula1"`   // data validation role
+	Formula2         string  `xml:"formula2"`   //data validation role
+	minRow           int     //`xml:"-"`
+	maxRow           int     //`xml:"-"`
+	//minCol         int     `xml:"-"` //spare
+	//maxCol         int     `xml:"-"` //spare
+}
+
 // xlsxRow directly maps the row element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much