Browse Source

Merge branch 'master' into ryanh/AddRowAtIndex

Ryan Hollis 6 years ago
parent
commit
d7f36a0a83
19 changed files with 904 additions and 128 deletions
  1. 2 0
      .travis.yml
  2. 28 21
      cell.go
  3. 29 2
      cell_test.go
  4. 68 9
      col.go
  5. 166 0
      datavalidation.go
  6. 195 0
      datavalidation_test.go
  7. 53 11
      date.go
  8. 2 0
      date_test.go
  9. 1 2
      example_read_test.go
  10. 13 3
      file.go
  11. 24 2
      file_test.go
  12. 44 0
      lib.go
  13. 68 49
      sheet.go
  14. 16 4
      sheet_test.go
  15. 1 1
      style.go
  16. BIN
      testdocs/max_sheet_name_length.xlsx
  17. 41 0
      write.go
  18. 112 11
      write_test.go
  19. 41 13
      xmlWorksheet.go

+ 2 - 0
.travis.yml

@@ -3,6 +3,8 @@ language: go
 go:
 go:
   - 1.8.x
   - 1.8.x
   - 1.9.x
   - 1.9.x
+  - 1.10.x
+  - 1.11.x
 
 
 script:
 script:
   - go vet ./...
   - go vet ./...

+ 28 - 21
cell.go

@@ -42,17 +42,18 @@ func (ct CellType) Ptr() *CellType {
 // Cell is a high level structure intended to provide user access to
 // Cell is a high level structure intended to provide user access to
 // the contents of Cell within an xlsx.Row.
 // the contents of Cell within an xlsx.Row.
 type Cell struct {
 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.
 // 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.
 // NewCell creates a cell and adds it to a row.
 func NewCell(r *Row) *Cell {
 func NewCell(r *Row) *Cell {
-	return &Cell{Row: r, NumFmt: "general"}
+	return &Cell{Row: r}
 }
 }
 
 
 // Merge with other cells, horizontally and/or vertically.
 // Merge with other cells, horizontally and/or vertically.
@@ -100,6 +101,12 @@ func (c *Cell) SetFloat(n float64) {
 	c.SetValue(n)
 	c.SetValue(n)
 }
 }
 
 
+// IsTime returns true if the cell stores a time value.
+func (c *Cell) IsTime() bool {
+	c.getNumberFormat()
+	return c.parsedNumFmt.isTimeFormat
+}
+
 //GetTime returns the value of a Cell as a time.Time
 //GetTime returns the value of a Cell as a time.Time
 func (c *Cell) GetTime(date1904 bool) (t time.Time, err error) {
 func (c *Cell) GetTime(date1904 bool) (t time.Time, err error) {
 	f, err := c.Float()
 	f, err := c.Float()
@@ -129,14 +136,9 @@ func (c *Cell) SetFloatWithFormat(n float64, format string) {
 	c.formula = ""
 	c.formula = ""
 }
 }
 
 
-var timeLocationUTC, _ = time.LoadLocation("UTC")
-
-func TimeToUTCTime(t time.Time) time.Time {
-	return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
-}
-
-func TimeToExcelTime(t time.Time) float64 {
-	return float64(t.UnixNano())/8.64e13 + 25569.0
+// SetCellFormat set cell value  format
+func (c *Cell) SetFormat(format string) {
+	c.NumFmt = format
 }
 }
 
 
 // DateTimeOptions are additional options for exporting times
 // DateTimeOptions are additional options for exporting times
@@ -175,7 +177,7 @@ func (c *Cell) SetDateTime(t time.Time) {
 func (c *Cell) SetDateWithOptions(t time.Time, options DateTimeOptions) {
 func (c *Cell) SetDateWithOptions(t time.Time, options DateTimeOptions) {
 	_, offset := t.In(options.Location).Zone()
 	_, offset := t.In(options.Location).Zone()
 	t = time.Unix(t.Unix()+int64(offset), 0)
 	t = time.Unix(t.Unix()+int64(offset), 0)
-	c.SetDateTimeWithFormat(TimeToExcelTime(t.In(timeLocationUTC)), options.ExcelTimeFormat)
+	c.SetDateTimeWithFormat(TimeToExcelTime(t.In(timeLocationUTC), c.date1904), options.ExcelTimeFormat)
 }
 }
 
 
 func (c *Cell) SetDateTimeWithFormat(n float64, format string) {
 func (c *Cell) SetDateTimeWithFormat(n float64, format string) {
@@ -371,3 +373,8 @@ func (c *Cell) FormattedValue() (string, error) {
 	}
 	}
 	return returnVal, err
 	return returnVal, err
 }
 }
+
+// SetDataValidation set data validation
+func (c *Cell) SetDataValidation(dd *xlsxCellDataValidation) {
+	c.DataValidation = dd
+}

+ 29 - 2
cell_test.go

@@ -329,6 +329,19 @@ func (l *CellSuite) TestCellTypeFormatHandling(c *C) {
 		c.Assert(val, Equals, testCase.formattedValueOutput)
 		c.Assert(val, Equals, testCase.formattedValueOutput)
 	}
 	}
 }
 }
+
+func (s *CellSuite) TestIsTime(c *C) {
+	cell := Cell{}
+	isTime := cell.IsTime()
+	c.Assert(isTime, Equals, false)
+	cell.Value = "43221"
+	c.Assert(isTime, Equals, false)
+	cell.NumFmt = "d-mmm-yy"
+	cell.Value = "43221"
+	isTime = cell.IsTime()
+	c.Assert(isTime, Equals, true)
+}
+
 func (s *CellSuite) TestGetTime(c *C) {
 func (s *CellSuite) TestGetTime(c *C) {
 	cell := Cell{}
 	cell := Cell{}
 	cell.SetFloat(0)
 	cell.SetFloat(0)
@@ -597,6 +610,20 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	fvc.Equals(smallCell, "Saturday, December 30, 1899")
 	fvc.Equals(smallCell, "Saturday, December 30, 1899")
 }
 }
 
 
+func (s *CellSuite) TestTimeToExcelTime(c *C) {
+	c.Assert(0.0, Equals, TimeToExcelTime(time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC), false))
+	c.Assert(-1462.0, Equals, TimeToExcelTime(time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC), true))
+	c.Assert(25569.0, Equals, TimeToExcelTime(time.Unix(0, 0), false))
+	c.Assert(43269.0, Equals, TimeToExcelTime(time.Date(2018, 6, 18, 0, 0, 0, 0, time.UTC), false))
+	c.Assert(401769.0, Equals, TimeToExcelTime(time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC), false))
+	smallDate := time.Date(1899, 12, 30, 0, 0, 0, 1000, time.UTC)
+	smallExcelTime := TimeToExcelTime(smallDate, false)
+
+	c.Assert(true, Equals, 0.0 != smallExcelTime)
+	roundTrippedDate := TimeFromExcelTime(smallExcelTime, false)
+	c.Assert(roundTrippedDate, Equals, smallDate)
+}
+
 // test setters and getters
 // test setters and getters
 func (s *CellSuite) TestSetterGetters(c *C) {
 func (s *CellSuite) TestSetterGetters(c *C) {
 	cell := Cell{}
 	cell := Cell{}
@@ -744,7 +771,7 @@ func (s *CellSuite) TestSetDateWithOptions(c *C) {
 	})
 	})
 	val, err = cell.Float()
 	val, err = cell.Float()
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
-	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 7, 0, 0, 0, time.UTC)))
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 7, 0, 0, 0, time.UTC), false))
 
 
 	// test jp timezone
 	// test jp timezone
 	jpTZ, err := time.LoadLocation("Asia/Tokyo")
 	jpTZ, err := time.LoadLocation("Asia/Tokyo")
@@ -755,7 +782,7 @@ func (s *CellSuite) TestSetDateWithOptions(c *C) {
 	})
 	})
 	val, err = cell.Float()
 	val, err = cell.Float()
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
-	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 21, 0, 0, 0, time.UTC)))
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 21, 0, 0, 0, time.UTC), false))
 }
 }
 
 
 func (s *CellSuite) TestIsTimeFormat(c *C) {
 func (s *CellSuite) TestIsTimeFormat(c *C) {

+ 68 - 9
col.go

@@ -2,17 +2,20 @@ package xlsx
 
 
 // Default column width in excel
 // Default column width in excel
 const ColWidth = 9.5
 const ColWidth = 9.5
+const Excel2006MaxRowIndex = 1048576
+const Excel2006MinRowIndex = 1
 
 
 type Col struct {
 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.
 // 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) {
 func (c *Col) SetStyle(style *Style) {
 	c.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)
+}

+ 166 - 0
datavalidation.go

@@ -0,0 +1,166 @@
+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:       allowBlank,
+		ShowErrorMessage: showErrorMessage,
+		ShowInputMessage: 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
+}
+
+// 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
+	}
+}

+ 53 - 11
date.go

@@ -5,8 +5,33 @@ import (
 	"time"
 	"time"
 )
 )
 
 
-const MJD_0 float64 = 2400000.5
-const MJD_JD2000 float64 = 51544.5
+const (
+	MJD_0 float64 = 2400000.5
+	MJD_JD2000 float64 = 51544.5
+
+	secondsInADay = float64((24*time.Hour)/time.Second)
+	nanosInADay = float64((24*time.Hour)/time.Nanosecond)
+)
+
+var (
+	timeLocationUTC, _ = time.LoadLocation("UTC")
+
+	unixEpoc = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
+	// In 1900 mode, Excel takes dates in floating point numbers of days starting with Jan 1 1900.
+	// The days are not zero indexed, so Jan 1 1900 would be 1.
+	// Except that Excel pretends that Feb 29, 1900 occurred to be compatible with a bug in Lotus 123.
+	// So, this constant uses Dec 30, 1899 instead of Jan 1, 1900, so the diff will be correct.
+	// http://www.cpearson.com/excel/datetime.htm
+	excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)
+	excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
+	// Days between epocs, including both off by one errors for 1900.
+	daysBetween1970And1900 = float64(unixEpoc.Sub(excel1900Epoc)/(24 * time.Hour))
+	daysBetween1970And1904 = float64(unixEpoc.Sub(excel1904Epoc)/(24 * time.Hour))
+)
+
+func TimeToUTCTime(t time.Time) time.Time {
+	return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
+}
 
 
 func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
 func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
 	switch {
 	switch {
@@ -78,10 +103,10 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
 // Convert an excelTime representation (stored as a floating point number) to a time.Time.
 // Convert an excelTime representation (stored as a floating point number) to a time.Time.
 func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time {
 func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time {
 	var date time.Time
 	var date time.Time
-	var intPart int64 = int64(excelTime)
+	var wholeDaysPart = int(excelTime)
 	// Excel uses Julian dates prior to March 1st 1900, and
 	// Excel uses Julian dates prior to March 1st 1900, and
 	// Gregorian thereafter.
 	// Gregorian thereafter.
-	if intPart <= 61 {
+	if wholeDaysPart <= 61 {
 		const OFFSET1900 = 15018.0
 		const OFFSET1900 = 15018.0
 		const OFFSET1904 = 16480.0
 		const OFFSET1904 = 16480.0
 		var date time.Time
 		var date time.Time
@@ -92,14 +117,31 @@ func TimeFromExcelTime(excelTime float64, date1904 bool) time.Time {
 		}
 		}
 		return date
 		return date
 	}
 	}
-	var floatPart float64 = excelTime - float64(intPart)
-	var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
+	var floatPart = excelTime - float64(wholeDaysPart)
+	if date1904 {
+		date = excel1904Epoc
+	} else {
+		date = excel1900Epoc
+	}
+	durationPart := time.Duration(nanosInADay * floatPart)
+	return date.AddDate(0,0, wholeDaysPart).Add(durationPart)
+}
+
+// TimeToExcelTime will convert a time.Time into Excel's float representation, in either 1900 or 1904
+// mode. If you don't know which to use, set date1904 to false.
+// TODO should this should handle Julian dates?
+func TimeToExcelTime(t time.Time, date1904 bool) float64 {
+	// Get the number of days since the unix epoc
+	daysSinceUnixEpoc := float64(t.Unix())/secondsInADay
+	// Get the number of nanoseconds in days since Unix() is in seconds.
+	nanosPart := float64(t.Nanosecond())/nanosInADay
+	// Add both together plus the number of days difference between unix and Excel epocs.
+	var offsetDays float64
 	if date1904 {
 	if date1904 {
-		date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
+		offsetDays = daysBetween1970And1904
 	} else {
 	} else {
-		date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
+		offsetDays = daysBetween1970And1900
 	}
 	}
-	durationDays := time.Duration(intPart) * time.Hour * 24
-	durationPart := time.Duration(dayNanoSeconds * floatPart)
-	return date.Add(durationDays).Add(durationPart)
+	daysSinceExcelEpoc := daysSinceUnixEpoc + offsetDays + nanosPart
+	return daysSinceExcelEpoc
 }
 }

+ 2 - 0
date_test.go

@@ -50,6 +50,8 @@ func (d *DateSuite) TestTimeFromExcelTime(c *C) {
 	c.Assert(date, Equals, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC))
 	c.Assert(date, Equals, time.Date(1900, 3, 1, 0, 0, 0, 0, time.UTC))
 	date = TimeFromExcelTime(41275.0, false)
 	date = TimeFromExcelTime(41275.0, false)
 	c.Assert(date, Equals, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC))
 	c.Assert(date, Equals, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC))
+	date = TimeFromExcelTime(401769, false)
+	c.Assert(date, Equals, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC))
 }
 }
 
 
 func (d *DateSuite) TestTimeFromExcelTimeWithFractionalPart(c *C) {
 func (d *DateSuite) TestTimeFromExcelTimeWithFractionalPart(c *C) {

+ 1 - 2
example_read_test.go

@@ -29,8 +29,7 @@ func ExampleRow_ReadStruct() {
 	readStruct := &structTest{}
 	readStruct := &structTest{}
 	err := row.ReadStruct(readStruct)
 	err := row.ReadStruct(readStruct)
 	if err != nil {
 	if err != nil {
-		fmt.Println(readStruct)
-	} else {
 		panic(err)
 		panic(err)
 	}
 	}
+	fmt.Println(readStruct)
 }
 }

+ 13 - 3
file.go

@@ -10,6 +10,7 @@ import (
 	"os"
 	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"unicode/utf8"
 )
 )
 
 
 // File is a high level structure providing a slice of Sheet structs
 // File is a high level structure providing a slice of Sheet structs
@@ -149,13 +150,22 @@ func (f *File) Write(writer io.Writer) (err error) {
 	return zipWriter.Close()
 	return zipWriter.Close()
 }
 }
 
 
-// Add a new Sheet, with the provided name, to a File
+// Add a new Sheet, with the provided name, to a File. 
+// The maximum sheet name length is 31 characters. If the sheet name length is exceeded an error is thrown.
+// These special characters are also not allowed: : \ / ? * [ ]
 func (f *File) AddSheet(sheetName string) (*Sheet, error) {
 func (f *File) AddSheet(sheetName string) (*Sheet, error) {
 	if _, exists := f.Sheet[sheetName]; exists {
 	if _, exists := f.Sheet[sheetName]; exists {
 		return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
 		return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
 	}
 	}
-	if len(sheetName) >= 31 {
-		return nil, fmt.Errorf("sheet name must be less than 31 characters long.  It is currently '%d' characters long", len(sheetName))
+	if utf8.RuneCountInString(sheetName) > 31 {
+		return nil, fmt.Errorf("sheet name must be 31 or fewer characters long.  It is currently '%d' characters long", utf8.RuneCountInString(sheetName))
+	}
+	// Iterate over the runes
+	for _, r := range sheetName {
+		// Excel forbids : \ / ? * [ ]
+		if r == ':' || r == '\\' || r == '/' || r == '?' || r == '*' || r == '[' || r == ']' {
+			return nil, fmt.Errorf("sheet name must not contain any restricted characters : \\ / ? * [ ] but contains '%s'", string(r))
+		}
 	}
 	}
 	sheet := &Sheet{
 	sheet := &Sheet{
 		Name:     sheetName,
 		Name:     sheetName,

+ 24 - 2
file_test.go

@@ -376,6 +376,20 @@ func (l *FileSuite) TestAppendSheetWithDuplicateName(c *C) {
 	c.Assert(err, ErrorMatches, "duplicate sheet name 'MySheet'.")
 	c.Assert(err, ErrorMatches, "duplicate sheet name 'MySheet'.")
 }
 }
 
 
+// Test that we can read & create a 31 rune sheet name
+func (l *FileSuite) TestMaxSheetNameLength(c *C) {
+	// Open a genuine xlsx created by Microsoft Excel 2007
+	xlsxFile, err := OpenFile("./testdocs/max_sheet_name_length.xlsx")
+	c.Assert(err, IsNil)
+	c.Assert(xlsxFile, NotNil)
+	c.Assert(xlsxFile.Sheets[0].Name, Equals, "αααααβββββγγγγγδδδδδεεεεεζζζζζη")
+	// Create a new file with the same sheet name
+	f := NewFile()
+	s, err := f.AddSheet(xlsxFile.Sheets[0].Name)
+	c.Assert(err, IsNil)
+	c.Assert(s.Name, Equals, "αααααβββββγγγγγδδδδδεεεεεζζζζζη")
+}
+
 // Test that we can get the Nth sheet
 // Test that we can get the Nth sheet
 func (l *FileSuite) TestNthSheet(c *C) {
 func (l *FileSuite) TestNthSheet(c *C) {
 	var f *File
 	var f *File
@@ -389,6 +403,15 @@ func (l *FileSuite) TestNthSheet(c *C) {
 	c.Assert(sheetByIndex, Equals, sheetByName)
 	c.Assert(sheetByIndex, Equals, sheetByName)
 }
 }
 
 
+// Test invalid sheet name characters
+func (l *FileSuite) TestInvalidSheetNameCharacters(c *C) {
+	f := NewFile()
+	for _, invalidChar := range []string{":", "\\", "/", "?", "*", "[", "]"} {
+		_, err := f.AddSheet(invalidChar)
+		c.Assert(err, NotNil)
+	}
+}
+
 // Test that we can create a Workbook and marshal it to XML.
 // Test that we can create a Workbook and marshal it to XML.
 func (l *FileSuite) TestMarshalWorkbook(c *C) {
 func (l *FileSuite) TestMarshalWorkbook(c *C) {
 	var f *File
 	var f *File
@@ -817,8 +840,7 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	// spreadsheet.  Style support will follow.
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellXfs count="2"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellXfs></styleSheet>`
-
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellXfs count="2"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellXfs></styleSheet>`
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 }
 
 

+ 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.DefaultRowHeight = worksheet.SheetFormatPr.DefaultRowHeight
 	sheet.SheetFormat.OutlineLevelCol = worksheet.SheetFormatPr.OutlineLevelCol
 	sheet.SheetFormat.OutlineLevelCol = worksheet.SheetFormatPr.OutlineLevelCol
 	sheet.SheetFormat.OutlineLevelRow = worksheet.SheetFormatPr.OutlineLevelRow
 	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
 	result.Sheet = sheet
 	sc <- result
 	sc <- result

+ 68 - 49
sheet.go

@@ -83,16 +83,42 @@ func (s *Sheet) RemoveRowAtIndex(index int) error {
 	return nil
 	return nil
 }
 }
 
 
+// Make sure we always have as many Rows as we do cells.
+func (s *Sheet) maybeAddRow(rowCount int) {
+	if rowCount > s.MaxRow {
+		loopCnt := rowCount - s.MaxRow
+		for i := 0; i < loopCnt; i++ {
+
+			row := &Row{Sheet: s}
+			s.Rows = append(s.Rows, row)
+		}
+		s.MaxRow = rowCount
+	}
+}
+
+// Make sure we always have as many Rows as we do cells.
+func (s *Sheet) Row(idx int) *Row {
+	s.maybeAddRow(idx + 1)
+	return s.Rows[idx]
+}
+
 // Make sure we always have as many Cols as we do cells.
 // Make sure we always have as many Cols as we do cells.
 func (s *Sheet) maybeAddCol(cellCount int) {
 func (s *Sheet) maybeAddCol(cellCount int) {
 	if cellCount > s.MaxCol {
 	if cellCount > s.MaxCol {
-		col := &Col{
-			style:     NewStyle(),
-			Min:       cellCount,
-			Max:       cellCount,
-			Hidden:    false,
-			Collapsed: false}
-		s.Cols = append(s.Cols, col)
+		loopCnt := cellCount - s.MaxCol
+		currIndex := s.MaxCol + 1
+		for i := 0; i < loopCnt; i++ {
+
+			col := &Col{
+				style:     NewStyle(),
+				Min:       currIndex,
+				Max:       currIndex,
+				Hidden:    false,
+				Collapsed: false}
+			s.Cols = append(s.Cols, col)
+			currIndex++
+		}
+
 		s.MaxCol = cellCount
 		s.MaxCol = cellCount
 	}
 	}
 }
 }
@@ -132,17 +158,12 @@ func (s *Sheet) SetColWidth(startcol, endcol int, width float64) error {
 	if startcol > endcol {
 	if startcol > endcol {
 		return fmt.Errorf("Could not set width for range %d-%d: startcol must be less than endcol.", startcol, endcol)
 		return fmt.Errorf("Could not set width for range %d-%d: startcol must be less than endcol.", startcol, endcol)
 	}
 	}
-	col := &Col{
-		style:     NewStyle(),
-		Min:       startcol + 1,
-		Max:       endcol + 1,
-		Hidden:    false,
-		Collapsed: false,
-		Width:     width}
-	s.Cols = append(s.Cols, col)
-	if endcol+1 > s.MaxCol {
-		s.MaxCol = endcol + 1
+	end := endcol + 1
+	s.maybeAddCol(end)
+	for ; startcol < end; startcol++ {
+		s.Cols[startcol].Width = width
 	}
 	}
+
 	return nil
 	return nil
 }
 }
 
 
@@ -167,42 +188,13 @@ func (s *Sheet) handleMerged() {
 	// borders to them depending on their position. If any cells required by the merge
 	// borders to them depending on their position. If any cells required by the merge
 	// are missing, they will be allocated by s.Cell().
 	// are missing, they will be allocated by s.Cell().
 	for key, cell := range merged {
 	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)
 		maincol, mainrow, _ := GetCoordsFromCellIDString(key)
 		for rownum := 0; rownum <= cell.VMerge; rownum++ {
 		for rownum := 0; rownum <= cell.VMerge; rownum++ {
 			for colnum := 0; colnum <= cell.HMerge; colnum++ {
 			for colnum := 0; colnum <= cell.HMerge; colnum++ {
-				tmpcell := s.Cell(mainrow+rownum, maincol+colnum)
-				style := tmpcell.GetStyle()
-				style.ApplyBorder = true
-
-				if rownum == 0 {
-					style.Border.Top = top
-				}
+				// make cell
+				s.Cell(mainrow+rownum, maincol+colnum)
 
 
-				if rownum == (cell.VMerge) {
-					style.Border.Bottom = bottom
-				}
-
-				if colnum == 0 {
-					style.Border.Left = left
-				}
-
-				if colnum == (cell.HMerge) {
-					style.Border.Right = right
-				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -244,7 +236,6 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 	worksheet.SheetFormatPr.DefaultColWidth = s.SheetFormat.DefaultColWidth
 	worksheet.SheetFormatPr.DefaultColWidth = s.SheetFormat.DefaultColWidth
 
 
 	colsXfIdList := make([]int, len(s.Cols))
 	colsXfIdList := make([]int, len(s.Cols))
-	worksheet.Cols = &xlsxCols{Col: []xlsxCol{}}
 	for c, col := range s.Cols {
 	for c, col := range s.Cols {
 		XfId := 0
 		XfId := 0
 		if col.Min == 0 {
 		if col.Min == 0 {
@@ -269,6 +260,10 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		} else {
 		} else {
 			customWidth = true
 			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,
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 			xlsxCol{Min: col.Min,
 			xlsxCol{Min: col.Min,
 				Max:          col.Max,
 				Max:          col.Max,
@@ -283,6 +278,22 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		if col.OutlineLevel > maxLevelCol {
 		if col.OutlineLevel > maxLevelCol {
 			maxLevelCol = col.OutlineLevel
 			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 {
 	for r, row := range s.Rows {
@@ -352,6 +363,14 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			}
 			}
 
 
 			xRow.C = append(xRow.C, xC)
 			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 {
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 				// r == rownum, c == colnum
 				// r == rownum, c == colnum

File diff suppressed because it is too large
+ 16 - 4
sheet_test.go


+ 1 - 1
style.go

@@ -164,7 +164,7 @@ func DefaultFont() *Font {
 }
 }
 
 
 func DefaultFill() *Fill {
 func DefaultFill() *Fill {
-	return NewFill("none", "FFFFFFFF", "00000000")
+	return NewFill("none", "", "")
 
 
 }
 }
 
 

BIN
testdocs/max_sheet_name_length.xlsx


+ 41 - 0
write.go

@@ -1,6 +1,7 @@
 package xlsx
 package xlsx
 
 
 import (
 import (
+	"database/sql"
 	"fmt"
 	"fmt"
 	"reflect"
 	"reflect"
 	"time"
 	"time"
@@ -41,6 +42,26 @@ func (r *Row) WriteSlice(e interface{}, cols int) int {
 		case fmt.Stringer: // check Stringer first
 		case fmt.Stringer: // check Stringer first
 			cell := r.AddCell()
 			cell := r.AddCell()
 			cell.SetString(t.String())
 			cell.SetString(t.String())
+		case sql.NullString:  // check null sql types nulls = ''
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.String)
+			}
+		case sql.NullBool:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetBool(t.Bool)
+			}
+		case sql.NullInt64:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.Int64)
+			}
+		case sql.NullFloat64:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.Float64)
+			}
 		default:
 		default:
 			switch val.Kind() { // underlying type of slice
 			switch val.Kind() { // underlying type of slice
 			case reflect.String, reflect.Int, reflect.Int8,
 			case reflect.String, reflect.Int, reflect.Int8,
@@ -93,6 +114,26 @@ func (r *Row) WriteStruct(e interface{}, cols int) int {
 		case fmt.Stringer: // check Stringer first
 		case fmt.Stringer: // check Stringer first
 			cell := r.AddCell()
 			cell := r.AddCell()
 			cell.SetString(t.String())
 			cell.SetString(t.String())
+		case sql.NullString: // check null sql types nulls = ''
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.String)
+			}
+		case sql.NullBool:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetBool(t.Bool)
+			}
+		case sql.NullInt64:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.Int64)
+			}
+		case sql.NullFloat64:
+			cell := r.AddCell()
+			if cell.SetString(``); t.Valid {
+				cell.SetValue(t.Float64)
+			}
 		default:
 		default:
 			switch f.Kind() {
 			switch f.Kind() {
 			case reflect.String, reflect.Int, reflect.Int8,
 			case reflect.String, reflect.Int, reflect.Int8,

+ 112 - 11
write_test.go

@@ -1,6 +1,7 @@
 package xlsx
 package xlsx
 
 
 import (
 import (
+	"database/sql"
 	"math"
 	"math"
 	"time"
 	"time"
 
 
@@ -26,13 +27,21 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	sheet, _ := f.AddSheet("Test1")
 	sheet, _ := f.AddSheet("Test1")
 	row := sheet.AddRow()
 	row := sheet.AddRow()
 	type e struct {
 	type e struct {
-		FirstName   string
-		Age         int
-		GPA         float64
-		LikesPHP    bool
-		Stringer    testStringerImpl
-		StringerPtr *testStringerImpl
-		Time        time.Time
+		FirstName       string
+		Age             int
+		GPA             float64
+		LikesPHP        bool
+		Stringer        testStringerImpl
+		StringerPtr     *testStringerImpl
+		Time            time.Time
+		LastName        sql.NullString
+		HasPhd          sql.NullBool
+		GithubStars     sql.NullInt64
+		Raiting         sql.NullFloat64
+		NullLastName    sql.NullString
+		NullHasPhd      sql.NullBool
+		NullGithubStars sql.NullInt64
+		NullRaiting     sql.NullFloat64
 	}
 	}
 	testStruct := e{
 	testStruct := e{
 		"Eric",
 		"Eric",
@@ -42,15 +51,23 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 		testStringerImpl{"Stringer"},
 		testStringerImpl{"Stringer"},
 		&testStringerImpl{"Pointer to Stringer"},
 		&testStringerImpl{"Pointer to Stringer"},
 		time.Unix(0, 0),
 		time.Unix(0, 0),
+		sql.NullString{String: `Smith`, Valid: true},
+		sql.NullBool{Bool: false, Valid: true},
+		sql.NullInt64{Int64: 100, Valid: true},
+		sql.NullFloat64{Float64: 0.123, Valid: true},
+		sql.NullString{String: `What ever`, Valid: false},
+		sql.NullBool{Bool: true, Valid: false},
+		sql.NullInt64{Int64: 100, Valid: false},
+		sql.NullFloat64{Float64: 0.123, Valid: false},
 	}
 	}
 	cnt := row.WriteStruct(&testStruct, -1)
 	cnt := row.WriteStruct(&testStruct, -1)
-	c.Assert(cnt, Equals, 7)
+	c.Assert(cnt, Equals, 15)
 	c.Assert(row, NotNil)
 	c.Assert(row, NotNil)
 
 
 	var (
 	var (
-		c0, c4, c5 string
-		err        error
-		c6         float64
+		c0, c4, c5, c7, c11, c12, c13, c14 string
+		err                                error
+		c6                                 float64
 	)
 	)
 	if c0, err = row.Cells[0].FormattedValue(); err != nil {
 	if c0, err = row.Cells[0].FormattedValue(); err != nil {
 		c.Error(err)
 		c.Error(err)
@@ -67,6 +84,26 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	if c6, err = row.Cells[6].Float(); err != nil {
 	if c6, err = row.Cells[6].Float(); err != nil {
 		c.Error(err)
 		c.Error(err)
 	}
 	}
+	if c7, err = row.Cells[7].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+
+	c8 := row.Cells[8].Bool()
+	c9, e9 := row.Cells[9].Int()
+	c10, e10 := row.Cells[10].Float()
+
+	if c11, err = row.Cells[11].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	if c12, err = row.Cells[12].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	if c13, err = row.Cells[13].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	if c14, err = row.Cells[14].FormattedValue(); err != nil {
+		c.Error(err)
+	}
 
 
 	c.Assert(c0, Equals, "Eric")
 	c.Assert(c0, Equals, "Eric")
 	c.Assert(c1, Equals, 20)
 	c.Assert(c1, Equals, 20)
@@ -75,9 +112,20 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	c.Assert(c4, Equals, "Stringer")
 	c.Assert(c4, Equals, "Stringer")
 	c.Assert(c5, Equals, "Pointer to Stringer")
 	c.Assert(c5, Equals, "Pointer to Stringer")
 	c.Assert(math.Floor(c6), Equals, 25569.0)
 	c.Assert(math.Floor(c6), Equals, 25569.0)
+	c.Assert(c7, Equals, `Smith`)
+	c.Assert(c8, Equals, false)
+	c.Assert(c9, Equals, 100)
+	c.Assert(c10, Equals, 0.123)
+	c.Assert(c11, Equals, ``)
+	c.Assert(c12, Equals, ``)
+	c.Assert(c13, Equals, ``)
+	c.Assert(c14, Equals, ``)
 
 
 	c.Assert(e1, Equals, nil)
 	c.Assert(e1, Equals, nil)
 	c.Assert(e2, Equals, nil)
 	c.Assert(e2, Equals, nil)
+	c.Assert(e9, Equals, nil)
+	c.Assert(e10, Equals, nil)
+
 }
 }
 
 
 // Test if we can write a slice to a row
 // Test if we can write a slice to a row
@@ -93,6 +141,10 @@ func (r *RowSuite) TestWriteSlice(c *C) {
 	type interfaceA []interface{}
 	type interfaceA []interface{}
 	type stringerA []testStringerImpl
 	type stringerA []testStringerImpl
 	type stringerPtrA []*testStringerImpl
 	type stringerPtrA []*testStringerImpl
+	type nullStringA []sql.NullString
+	type nullBoolA []sql.NullBool
+	type nullFloatA []sql.NullFloat64
+	type nullIntA []sql.NullInt64
 
 
 	s0 := strA{"Eric"}
 	s0 := strA{"Eric"}
 	row0 := sheet.AddRow()
 	row0 := sheet.AddRow()
@@ -181,4 +233,53 @@ func (r *RowSuite) TestWriteSlice(c *C) {
 	c.Assert(s7_ret, Equals, -1)
 	c.Assert(s7_ret, Equals, -1)
 	s7_ret = row7.WriteSlice([]string{s7}, -1)
 	s7_ret = row7.WriteSlice([]string{s7}, -1)
 	c.Assert(s7_ret, Equals, -1)
 	c.Assert(s7_ret, Equals, -1)
+
+	s8 := nullStringA{sql.NullString{String: "Smith", Valid: true}, sql.NullString{String: `What ever`, Valid: false}}
+	row8 := sheet.AddRow()
+	row8.WriteSlice(&s8, -1)
+	c.Assert(row8, NotNil)
+
+	if val, err := row8.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Smith")
+	}
+	// check second cell on empty string ""
+
+	if val2, err := row8.Cells[1].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val2, Equals, "")
+	}
+
+	s9 := nullBoolA{sql.NullBool{Bool: false, Valid: true}, sql.NullBool{Bool: true, Valid: false}}
+	row9 := sheet.AddRow()
+	row9.WriteSlice(&s9, -1)
+	c.Assert(row9, NotNil)
+	c9 := row9.Cells[0].Bool()
+	c9Null := row9.Cells[1].String()
+	c.Assert(c9, Equals, false)
+	c.Assert(c9Null, Equals, "")
+
+	s10 := nullIntA{sql.NullInt64{Int64: 100, Valid: true}, sql.NullInt64{Int64: 100, Valid: false}}
+	row10 := sheet.AddRow()
+	row10.WriteSlice(&s10, -1)
+	c.Assert(row10, NotNil)
+	c10, e10 := row10.Cells[0].Int()
+	c10Null, e10Null := row10.Cells[1].FormattedValue()
+	c.Assert(e10, Equals, nil)
+	c.Assert(c10, Equals, 100)
+	c.Assert(e10Null, Equals, nil)
+	c.Assert(c10Null, Equals, "")
+
+	s11 := nullFloatA{sql.NullFloat64{Float64: 0.123, Valid: true}, sql.NullFloat64{Float64: 0.123, Valid: false}}
+	row11 := sheet.AddRow()
+	row11.WriteSlice(&s11, -1)
+	c.Assert(row11, NotNil)
+	c11, e11 := row11.Cells[0].Float()
+	c11Null, e11Null := row11.Cells[1].FormattedValue()
+	c.Assert(e11, Equals, nil)
+	c.Assert(c11, Equals, 0.123)
+	c.Assert(e11Null, Equals, nil)
+	c.Assert(c11Null, Equals, "")
 }
 }

+ 41 - 13
xmlWorksheet.go

@@ -10,19 +10,20 @@ import (
 // currently I have not checked it for completeness - it does as much
 // currently I have not checked it for completeness - it does as much
 // as I need.
 // as I need.
 type xlsxWorksheet struct {
 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
 // xlsxHeaderFooter directly maps the headerFooter element in the namespace
@@ -223,6 +224,33 @@ type xlsxSheetData struct {
 	Row     []xlsxRow `xml:"row"`
 	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       bool    `xml:"allowBlank,attr"`         // allow empty
+	ShowInputMessage bool    `xml:"showInputMessage,attr"`   // 1, true,0,false,  select cell,  Whether the input message is displayed
+	ShowErrorMessage bool    `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,omitempty"` //Validity of data validation rules, cell and range, eg: A1 OR A1:A20
+	Formula1         string  `xml:"formula1"`             // data validation role
+	Formula2         string  `xml:"formula2,omitempty"`   //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
 // xlsxRow directly maps the row element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // currently I have not checked it for completeness - it does as much

Some files were not shown because too many files changed in this diff