Kaynağa Gözat

Merge branch 'master_upstream'

# Conflicts:
#	col.go
#	stream_file_builder.go
DamianSzkuat 6 yıl önce
ebeveyn
işleme
da75d4fe43
16 değiştirilmiş dosya ile 1015 ekleme ve 105 silme
  1. 29 12
      cell.go
  2. 13 0
      cell_test.go
  3. 59 9
      col.go
  4. 189 0
      data_validation.go
  5. 268 0
      data_validation_test.go
  6. 2 3
      file_test.go
  7. 76 10
      lib.go
  8. 12 5
      lib_test.go
  9. 58 33
      sheet.go
  10. 76 5
      sheet_test.go
  11. 7 0
      stream_file_builder.go
  12. 1 1
      style.go
  13. 1 1
      templates.go
  14. 41 0
      write.go
  15. 112 11
      write_test.go
  16. 71 15
      xmlWorksheet.go

+ 29 - 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.
@@ -100,6 +101,12 @@ func (c *Cell) SetFloat(n float64) {
 	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
 func (c *Cell) GetTime(date1904 bool) (t time.Time, err error) {
 	f, err := c.Float()
@@ -129,6 +136,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 +373,8 @@ func (c *Cell) FormattedValue() (string, error) {
 	}
 	return returnVal, err
 }
+
+// SetDataValidation set data validation
+func (c *Cell) SetDataValidation(dd *xlsxCellDataValidation) {
+	c.DataValidation = dd
+}

+ 13 - 0
cell_test.go

@@ -329,6 +329,19 @@ func (l *CellSuite) TestCellTypeFormatHandling(c *C) {
 		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) {
 	cell := Cell{}
 	cell.SetFloat(0)

+ 59 - 9
col.go

@@ -2,17 +2,20 @@ package xlsx
 
 // Default column width in excel
 const ColWidth = 9.5
+const Excel2006MaxRowCount = 1048576
+const Excel2006MaxRowIndex = Excel2006MaxRowCount - 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.
@@ -48,6 +51,53 @@ func (c *Col) SetStyle(style *Style) {
 	c.style = style
 }
 
+// SetDataValidation set data validation with zero based start and end.
+// Set end to -1 for all rows.
+func (c *Col) SetDataValidation(dd *xlsxCellDataValidation, start, end int) {
+	if end < 0 {
+		end = Excel2006MaxRowIndex
+	}
+
+	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 a zero basd start row.
+// This will apply to the rest of the rest of the column.
+func (c *Col) SetDataValidationWithStart(dd *xlsxCellDataValidation, start int) {
+	c.SetDataValidation(dd, start, -1)
+}
+
 // SetStreamStyle sets the style and number format id to the ones specified in the given StreamStyle
 func (c *Col) SetStreamStyle(style StreamStyle) {
 	c.style = style.style

+ 189 - 0
data_validation.go

@@ -0,0 +1,189 @@
+package xlsx
+
+import (
+	"fmt"
+	"strings"
+)
+
+type DataValidationType int
+
+// Data validation types
+const (
+	_DataValidationType = iota
+	typeNone            //inline use
+	DataValidationTypeCustom
+	DataValidationTypeDate
+	DataValidationTypeDecimal
+	dataValidationTypeList //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 bool) *xlsxCellDataValidation {
+	return &xlsxCellDataValidation{
+		AllowBlank: allowBlank,
+	}
+}
+
+// SetError set error notice
+func (dd *xlsxCellDataValidation) SetError(style DataValidationErrorStyle, title, msg *string) {
+	dd.ShowErrorMessage = true
+	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.ShowInputMessage = true
+	dd.PromptTitle = title
+	dd.Prompt = msg
+}
+
+// SetDropList sets a hard coded list of values that the drop down will choose from.
+// List validations do not work in Apple Numbers.
+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(dataValidationTypeList)
+	return nil
+}
+
+// SetInFileList is like SetDropList, excel that instead of having a hard coded list,
+// a reference to a part of the file is accepted and the list is automatically taken from there.
+// Setting y2 to -1 will select all the way to the end of the column. Selecting to the end of the
+// column will cause Google Sheets to spin indefinitely while trying to load the possible drop down
+// values (more than 5 minutes).
+// List validations do not work in Apple Numbers.
+func (dd *xlsxCellDataValidation) SetInFileList(sheet string, x1, y1, x2, y2 int) error {
+	start := GetCellIDStringFromCoordsWithFixed(x1, y1, true, true)
+	if y2 < 0 {
+		y2 = Excel2006MaxRowIndex
+	}
+
+	end := GetCellIDStringFromCoordsWithFixed(x2, y2, true, true)
+	// Escape single quotes in the file name.
+	// Single quotes are escaped by replacing them with two single quotes.
+	sheet = strings.Replace(sheet, "'", "''", -1)
+	formula := "'" + sheet + "'" + externalSheetBangChar + start + cellRangeChar + end
+	dd.Formula1 = formula
+	dd.Type = convDataValidationType(dataValidationTypeList)
+	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",
+		dataValidationTypeList:     "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]
+
+}

+ 268 - 0
data_validation_test.go

@@ -0,0 +1,268 @@
+package xlsx
+
+import (
+	"bytes"
+	"fmt"
+
+	. "gopkg.in/check.v1"
+)
+
+type DataValidationSuite struct{}
+
+var _ = Suite(&DataValidationSuite{})
+
+func (d *DataValidationSuite) TestDataValidation(t *C) {
+	var file *File
+	var sheet *Sheet
+	var row *Row
+	var cell *Cell
+	var err error
+	var title = "cell"
+	var msg = "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)
+	err = dd.SetDropList([]string{"a1", "a2", "a3"})
+	t.Assert(err, IsNil)
+
+	dd.SetInput(&title, &msg)
+	cell.SetDataValidation(dd)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"c1", "c2", "c3"})
+	t.Assert(err, IsNil)
+	title = "col c"
+	dd.SetInput(&title, &msg)
+	sheet.Col(2).SetDataValidation(dd, 0, 0)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"d", "d1", "d2"})
+	t.Assert(err, IsNil)
+	title = "col d range"
+	dd.SetInput(&title, &msg)
+	sheet.Col(3).SetDataValidation(dd, 3, 7)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"e1", "e2", "e3"})
+	t.Assert(err, IsNil)
+	title = "col e start 3"
+	dd.SetInput(&title, &msg)
+	sheet.Col(4).SetDataValidationWithStart(dd, 1)
+
+	index := 5
+	rowIndex := 1
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(15, 4, DataValidationTypeTextLeng, DataValidationOperatorBetween)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThanOrEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThan)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThan)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThanOrEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotBetween)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	rowIndex++
+	index = 5
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(4, 15, DataValidationTypeWhole, DataValidationOperatorBetween)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThanOrEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThan)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThanOrEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorNotEqual)
+	t.Assert(err, IsNil)
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetRange(10, 50, DataValidationTypeWhole, DataValidationOperatorNotBetween)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	index++
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"1", "2", "4"})
+	t.Assert(err, IsNil)
+	dd1 := NewXlsxCellDataValidation(true)
+	err = dd1.SetDropList([]string{"11", "22", "44"})
+	t.Assert(err, IsNil)
+	dd2 := NewXlsxCellDataValidation(true)
+	err = dd2.SetDropList([]string{"111", "222", "444"})
+	t.Assert(err, IsNil)
+	sheet.Col(12).SetDataValidation(dd, 2, 10)
+	sheet.Col(12).SetDataValidation(dd1, 3, 4)
+	sheet.Col(12).SetDataValidation(dd2, 5, 7)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"1", "2", "4"})
+	t.Assert(err, IsNil)
+	dd1 = NewXlsxCellDataValidation(true)
+	err = dd1.SetDropList([]string{"11", "22", "44"})
+	t.Assert(err, IsNil)
+	sheet.Col(13).SetDataValidation(dd, 2, 10)
+	sheet.Col(13).SetDataValidation(dd1, 1, 2)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"1", "2", "4"})
+	t.Assert(err, IsNil)
+	dd1 = NewXlsxCellDataValidation(true)
+	err = dd1.SetDropList([]string{"11", "22", "44"})
+	t.Assert(err, IsNil)
+	sheet.Col(14).SetDataValidation(dd, 2, 10)
+	sheet.Col(14).SetDataValidation(dd1, 1, 5)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"1", "2", "4"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	dd1 = NewXlsxCellDataValidation(true)
+	err = dd1.SetDropList([]string{"11", "22", "44"})
+	t.Assert(err, IsNil)
+	sheet.Col(15).SetDataValidation(dd, 2, 10)
+	sheet.Col(15).SetDataValidation(dd1, 1, 10)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"1", "2", "4"})
+	t.Assert(err, IsNil)
+	dd1 = NewXlsxCellDataValidation(true)
+	err = dd1.SetDropList([]string{"11", "22", "44"})
+	t.Assert(err, IsNil)
+	dd2 = NewXlsxCellDataValidation(true)
+	err = dd2.SetDropList([]string{"111", "222", "444"})
+	t.Assert(err, IsNil)
+	sheet.Col(16).SetDataValidation(dd, 10, 20)
+	sheet.Col(16).SetDataValidation(dd1, 2, 4)
+	sheet.Col(16).SetDataValidation(dd2, 21, 30)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"d", "d1", "d2"})
+	t.Assert(err, IsNil)
+	title = "col d range"
+	dd.SetInput(&title, &msg)
+	sheet.Col(3).SetDataValidation(dd, 3, Excel2006MaxRowIndex)
+
+	dd = NewXlsxCellDataValidation(true)
+	err = dd.SetDropList([]string{"d", "d1", "d2"})
+	t.Assert(err, IsNil)
+	title = "col d range"
+	dd.SetInput(&title, &msg)
+	sheet.Col(3).SetDataValidation(dd, 4, -1)
+	maxRow := sheet.Col(3).DataValidation[len(sheet.Col(3).DataValidation)-1].maxRow
+	t.Assert(maxRow, Equals, Excel2006MaxRowIndex)
+
+	dest := &bytes.Buffer{}
+	err = file.Write(dest)
+	t.Assert(err, IsNil)
+	// Read and write the file that was just saved.
+	file, err = OpenBinary(dest.Bytes())
+	t.Assert(err, IsNil)
+	dest = &bytes.Buffer{}
+	err = file.Write(dest)
+	t.Assert(err, IsNil)
+}
+
+func (d *DataValidationSuite) TestDataValidation2(t *C) {
+	// Show error and show info start disabled, but automatically get enabled when setting a message
+	dd := NewXlsxCellDataValidation(true)
+	t.Assert(dd.ShowErrorMessage, Equals, false)
+	t.Assert(dd.ShowInputMessage, Equals, false)
+
+	str := "you got an error"
+	dd.SetError(StyleStop, &str, &str)
+	t.Assert(dd.ShowErrorMessage, Equals, true)
+	t.Assert(dd.ShowInputMessage, Equals, false)
+
+	str = "hello"
+	dd.SetInput(&str, &str)
+	t.Assert(dd.ShowInputMessage, Equals, true)
+
+	// Check the formula created by this function
+	// The sheet name needs single quotes, the single quote in the name gets escaped,
+	// and all references are fixed.
+	err := dd.SetInFileList("Sheet ' 2", 2, 1, 3, 10)
+	t.Assert(err, IsNil)
+	expectedFormula := "'Sheet '' 2'!$C$2:$D$11"
+	t.Assert(dd.Formula1, Equals, expectedFormula)
+	t.Assert(dd.Type, Equals, "list")
+}

+ 2 - 3
file_test.go

@@ -570,7 +570,7 @@ func (l *FileSuite) TestMarshalFile(c *C) {
         <a:font script="Geor" typeface="Sylfaen"/>
       </a:majorFont>
       <a:minorFont>
-        <a:latin typeface="Calibri"/>
+        <a:latin typeface="Arial"/>
         <a:ea typeface=""/>
         <a:cs typeface=""/>
         <a:font script="Jpan" typeface="MS Pゴシック"/>
@@ -840,8 +840,7 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	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)
 }
 

+ 76 - 10
lib.go

@@ -13,7 +13,10 @@ import (
 )
 
 const (
-	sheetEnding = `</sheetData></worksheet>`
+	sheetEnding           = `</sheetData></worksheet>`
+	fixedCellRefChar      = "$"
+	cellRangeChar         = ":"
+	externalSheetBangChar = "!"
 )
 
 // XLSXReaderError is the standard error type for otherwise undefined
@@ -33,7 +36,7 @@ func (e *XLSXReaderError) Error() string {
 // the range string "1:3" yield the upper and lower integers 1 and 3.
 func getRangeFromString(rangeString string) (lower int, upper int, error error) {
 	var parts []string
-	parts = strings.SplitN(rangeString, ":", 2)
+	parts = strings.SplitN(rangeString, cellRangeChar, 2)
 	if parts[0] == "" {
 		error = errors.New(fmt.Sprintf("Invalid range '%s'\n", rangeString))
 	}
@@ -146,6 +149,12 @@ func ColIndexToLetters(colRef int) string {
 	return formatColumnName(smooshBase26Slice(parts))
 }
 
+// RowIndexToString is used to convert a zero based, numeric row
+// indentifier into its string representation.
+func RowIndexToString(rowRef int) string {
+	return strconv.Itoa(rowRef + 1)
+}
+
 // letterOnlyMapF is used in conjunction with strings.Map to return
 // only the characters A-Z and a-z in a string
 func letterOnlyMapF(rune rune) rune {
@@ -184,9 +193,22 @@ func GetCoordsFromCellIDString(cellIDString string) (x, y int, error error) {
 // GetCellIDStringFromCoords returns the Excel format cell name that
 // represents a pair of zero based cartesian coordinates.
 func GetCellIDStringFromCoords(x, y int) string {
-	letterPart := ColIndexToLetters(x)
-	numericPart := y + 1
-	return fmt.Sprintf("%s%d", letterPart, numericPart)
+	return GetCellIDStringFromCoordsWithFixed(x, y, false, false)
+}
+
+// GetCellIDStringFromCoordsWithFixed returns the Excel format cell name that
+// represents a pair of zero based cartesian coordinates.
+// It can specify either value as fixed.
+func GetCellIDStringFromCoordsWithFixed(x, y int, xFixed, yFixed bool) string {
+	xStr := ColIndexToLetters(x)
+	if xFixed {
+		xStr = fixedCellRefChar + xStr
+	}
+	yStr := RowIndexToString(y)
+	if yFixed {
+		yStr = fixedCellRefChar + yStr
+	}
+	return xStr + yStr
 }
 
 // getMaxMinFromDimensionRef return the zero based cartesian maximum
@@ -195,7 +217,7 @@ func GetCellIDStringFromCoords(x, y int) string {
 // returns "0,0", "1,1".
 func getMaxMinFromDimensionRef(ref string) (minx, miny, maxx, maxy int, err error) {
 	var parts []string
-	parts = strings.Split(ref, ":")
+	parts = strings.Split(ref, cellRangeChar)
 	minx, miny, err = GetCoordsFromCellIDString(parts[0])
 	if err != nil {
 		return -1, -1, -1, -1, err
@@ -397,10 +419,10 @@ func shiftCell(cellID string, dx, dy int) string {
 	fx, fy, _ := GetCoordsFromCellIDString(cellID)
 
 	// Is fixed column?
-	fixedCol := strings.Index(cellID, "$") == 0
+	fixedCol := strings.Index(cellID, fixedCellRefChar) == 0
 
 	// Is fixed row?
-	fixedRow := strings.LastIndex(cellID, "$") > 0
+	fixedRow := strings.LastIndex(cellID, fixedCellRefChar) > 0
 
 	if !fixedCol {
 		// Shift column
@@ -515,7 +537,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet, rowLi
 		return nil, nil, 0, 0
 	}
 	reftable = file.referenceTable
-	if len(Worksheet.Dimension.Ref) > 0 && len(strings.Split(Worksheet.Dimension.Ref, ":")) == 2 && rowLimit == NoRowLimit {
+	if len(Worksheet.Dimension.Ref) > 0 && len(strings.Split(Worksheet.Dimension.Ref, cellRangeChar)) == 2 && rowLimit == NoRowLimit {
 		minCol, _, maxCol, maxRow, err = getMaxMinFromDimensionRef(Worksheet.Dimension.Ref)
 	} else {
 		minCol, _, maxCol, maxRow, err = calculateMaxMinFromWorksheet(Worksheet)
@@ -571,7 +593,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet, rowLi
 			insertRowIndex++
 		}
 		// range is not empty and only one range exist
-		if len(rawrow.Spans) != 0 && strings.Count(rawrow.Spans, ":") == 1 {
+		if len(rawrow.Spans) != 0 && strings.Count(rawrow.Spans, cellRangeChar) == 1 {
 			row = makeRowFromSpan(rawrow.Spans, sheet)
 		} else {
 			row = makeRowFromRaw(rawrow, sheet)
@@ -699,6 +721,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.DataValidation {
+			sqrefArr := strings.Split(dd.Sqref, " ")
+			for _, sqref := range sqrefArr {
+				parts := strings.Split(sqref, cellRangeChar)
+
+				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

+ 12 - 5
lib_test.go

@@ -1322,10 +1322,12 @@ func (l *LibSuite) TestRowNotOverwrittenWhenFollowedByEmptyRow(c *C) {
 	sharedstringsXML := bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"/>`)
 
 	worksheet := new(xlsxWorksheet)
-	xml.NewDecoder(sheetXML).Decode(worksheet)
+	err := xml.NewDecoder(sheetXML).Decode(worksheet)
+	c.Assert(err, IsNil)
 
 	sst := new(xlsxSST)
-	xml.NewDecoder(sharedstringsXML).Decode(sst)
+	err = xml.NewDecoder(sharedstringsXML).Decode(sst)
+	c.Assert(err, IsNil)
 
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
@@ -1342,10 +1344,12 @@ func (l *LibSuite) TestRowNotOverwrittenWhenFollowedByEmptyRow(c *C) {
 func (l *LibSuite) TestRoundTripFileWithNoSheetCols(c *C) {
 	originalXlFile, err := OpenFile("testdocs/original.xlsx")
 	c.Assert(err, IsNil)
-	originalXlFile.Save("testdocs/after_write.xlsx")
+	err = originalXlFile.Save("testdocs/after_write.xlsx")
+	c.Assert(err, IsNil)
 	_, err = OpenFile("testdocs/after_write.xlsx")
 	c.Assert(err, IsNil)
-	os.Remove("testdocs/after_write.xlsx")
+	err = os.Remove("testdocs/after_write.xlsx")
+	c.Assert(err, IsNil)
 }
 
 func (l *LibSuite) TestReadRestEmptyRowsFromSheet(c *C) {
@@ -1690,6 +1694,9 @@ func TestFuzzCrashers(t *testing.T) {
 	}
 
 	for _, f := range crashers {
-		OpenBinary([]byte(f))
+		_, err := OpenBinary([]byte(f))
+		if err == nil {
+			t.Fatal("Expected a well formed error from opening this file")
+		}
 	}
 }

+ 58 - 33
sheet.go

@@ -56,6 +56,33 @@ func (s *Sheet) AddRow() *Row {
 	return row
 }
 
+// Add a new Row to a Sheet at a specific index
+func (s *Sheet) AddRowAtIndex(index int) (*Row, error) {
+	if index < 0 || index > len(s.Rows) {
+		return nil, errors.New("AddRowAtIndex: index out of bounds")
+	}
+	row := &Row{Sheet: s}
+	s.Rows = append(s.Rows, nil)
+
+	if index < len(s.Rows) {
+		copy(s.Rows[index+1:], s.Rows[index:])
+	}
+	s.Rows[index] = row
+	if len(s.Rows) > s.MaxRow {
+		s.MaxRow = len(s.Rows)
+	}
+	return row, nil
+}
+
+// Removes a row at a specific index
+func (s *Sheet) RemoveRowAtIndex(index int) error {
+	if index < 0 || index >= len(s.Rows) {
+		return errors.New("RemoveRowAtIndex: index out of bounds")
+	}
+	s.Rows = append(s.Rows[:index], s.Rows[index+1:]...)
+	return nil
+}
+
 // Make sure we always have as many Rows as we do cells.
 func (s *Sheet) maybeAddRow(rowCount int) {
 	if rowCount > s.MaxRow {
@@ -160,42 +187,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
-				}
 			}
 		}
 	}
@@ -237,7 +235,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 {
@@ -261,6 +258,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,
@@ -275,6 +276,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 = colName + RowIndexToString(dd.minRow)
+				} else {
+					dd.Sqref = colName + RowIndexToString(dd.minRow) + cellRangeChar + colName + RowIndexToString(dd.maxRow)
+				}
+				worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation, dd)
+
+			}
+			worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
+		}
 	}
 
 	for r, row := range s.Rows {
@@ -344,6 +361,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.DataValidation = append(worksheet.DataValidations.DataValidation, cell.DataValidation)
+				worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
+			}
 
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 				// r == rownum, c == colnum
@@ -352,7 +377,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				endCol := c + cell.HMerge
 				endRow := r + cell.VMerge
 				end := GetCellIDStringFromCoords(endCol, endRow)
-				mc.Ref = start + ":" + end
+				mc.Ref = start + cellRangeChar + end
 				if worksheet.MergeCells == nil {
 					worksheet.MergeCells = &xlsxMergeCells{}
 				}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 76 - 5
sheet_test.go


+ 7 - 0
stream_file_builder.go

@@ -165,6 +165,13 @@ func (sb *StreamFileBuilder) AddSheetS(name string, columnStyles []StreamStyle)
 	return nil
 }
 
+// AddValidation will add a validation to a specific column.
+func (sb *StreamFileBuilder) AddValidation(sheetIndex, colIndex, rowStartIndex int, validation *xlsxCellDataValidation) {
+	sheet := sb.xlsxFile.Sheets[sheetIndex]
+	column := sheet.Col(colIndex)
+	column.SetDataValidationWithStart(validation, rowStartIndex)
+}
+
 // Build begins streaming the XLSX file to the io, by writing all the XLSX metadata. It creates a StreamFile struct
 // that can be used to write the rows to the sheets.
 func (sb *StreamFileBuilder) Build() (*StreamFile, error) {

+ 1 - 1
style.go

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

+ 1 - 1
templates.go

@@ -97,7 +97,7 @@ const TEMPLATE_XL_THEME_THEME = `<?xml version="1.0" encoding="UTF-8" standalone
         <a:font script="Geor" typeface="Sylfaen"/>
       </a:majorFont>
       <a:minorFont>
-        <a:latin typeface="Calibri"/>
+        <a:latin typeface="Arial"/>
         <a:ea typeface=""/>
         <a:cs typeface=""/>
         <a:font script="Jpan" typeface="MS Pゴシック"/>

+ 41 - 0
write.go

@@ -1,6 +1,7 @@
 package xlsx
 
 import (
+	"database/sql"
 	"fmt"
 	"reflect"
 	"time"
@@ -41,6 +42,26 @@ func (r *Row) WriteSlice(e interface{}, cols int) int {
 		case fmt.Stringer: // check Stringer first
 			cell := r.AddCell()
 			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:
 			switch val.Kind() { // underlying type of slice
 			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
 			cell := r.AddCell()
 			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:
 			switch f.Kind() {
 			case reflect.String, reflect.Int, reflect.Int8,

+ 112 - 11
write_test.go

@@ -1,6 +1,7 @@
 package xlsx
 
 import (
+	"database/sql"
 	"math"
 	"time"
 
@@ -26,13 +27,21 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	sheet, _ := f.AddSheet("Test1")
 	row := sheet.AddRow()
 	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{
 		"Eric",
@@ -42,15 +51,23 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 		testStringerImpl{"Stringer"},
 		&testStringerImpl{"Pointer to Stringer"},
 		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)
-	c.Assert(cnt, Equals, 7)
+	c.Assert(cnt, Equals, 15)
 	c.Assert(row, NotNil)
 
 	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 {
 		c.Error(err)
@@ -67,6 +84,26 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	if c6, err = row.Cells[6].Float(); err != nil {
 		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(c1, Equals, 20)
@@ -75,9 +112,20 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 	c.Assert(c4, Equals, "Stringer")
 	c.Assert(c5, Equals, "Pointer to Stringer")
 	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(e2, Equals, nil)
+	c.Assert(e9, Equals, nil)
+	c.Assert(e10, Equals, nil)
+
 }
 
 // Test if we can write a slice to a row
@@ -93,6 +141,10 @@ func (r *RowSuite) TestWriteSlice(c *C) {
 	type interfaceA []interface{}
 	type stringerA []testStringerImpl
 	type stringerPtrA []*testStringerImpl
+	type nullStringA []sql.NullString
+	type nullBoolA []sql.NullBool
+	type nullFloatA []sql.NullFloat64
+	type nullIntA []sql.NullInt64
 
 	s0 := strA{"Eric"}
 	row0 := sheet.AddRow()
@@ -181,4 +233,53 @@ func (r *RowSuite) TestWriteSlice(c *C) {
 	c.Assert(s7_ret, Equals, -1)
 	s7_ret = row7.WriteSlice([]string{s7}, -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, "")
 }

+ 71 - 15
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,61 @@ type xlsxSheetData struct {
 	Row     []xlsxRow `xml:"row"`
 }
 
+// xlsxCellDataValidations  excel cell data validation
+type xlsxCellDataValidations struct {
+	DataValidation []*xlsxCellDataValidation `xml:"dataValidation"`
+	Count          int                       `xml:"count,attr"`
+}
+
+// xlsxCellDataValidation
+// A single item of data validation defined on a range of the worksheet.
+// The list validation type would more commonly be called "a drop down box."
+type xlsxCellDataValidation struct {
+	// A boolean value indicating whether the data validation allows the use of empty or blank
+	//entries. 1 means empty entries are OK and do not violate the validation constraints.
+	AllowBlank bool `xml:"allowBlank,attr,omitempty"`
+	// A boolean value indicating whether to display the input prompt message.
+	ShowInputMessage bool `xml:"showInputMessage,attr,omitempty"`
+	// A boolean value indicating whether to display the error alert message when an invalid
+	// value has been entered, according to the criteria specified.
+	ShowErrorMessage bool `xml:"showErrorMessage,attr,omitempty"`
+	// The style of error alert used for this data validation.
+	// warning, infomation, or stop
+	// Stop will prevent the user from entering data that does not pass validation.
+	ErrorStyle *string `xml:"errorStyle,attr"`
+	// Title bar text of error alert.
+	ErrorTitle *string `xml:"errorTitle,attr"`
+	// The relational operator used with this data validation.
+	// The possible values for this can be equal, notEqual, lessThan, etc.
+	// This only applies to certain validation types.
+	Operator string `xml:"operator,attr,omitempty"`
+	// Message text of error alert.
+	Error *string `xml:"error,attr"`
+	// Title bar text of input prompt.
+	PromptTitle *string `xml:"promptTitle,attr"`
+	// Message text of input prompt.
+	Prompt *string `xml:"prompt,attr"`
+	// The type of data validation.
+	// none, custom, date, decimal, list, textLength, time, whole
+	Type string `xml:"type,attr"`
+	// Range over which data validation is applied.
+	// Cell or range, eg: A1 OR A1:A20
+	Sqref string `xml:"sqref,attr,omitempty"`
+	// The first formula in the Data Validation dropdown. It is used as a bounds for 'between' and
+	// 'notBetween' relational operators, and the only formula used for other relational operators
+	// (equal, notEqual, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual), or for custom
+	// or list type data validation. The content can be a formula or a constant or a list series (comma separated values).
+	Formula1 string `xml:"formula1"`
+	// The second formula in the DataValidation dropdown. It is used as a bounds for 'between' and
+	// 'notBetween' relational operators only.
+	Formula2 string `xml:"formula2,omitempty"`
+	// minRow and maxRow are zero indexed
+	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
@@ -258,8 +314,8 @@ func (mc *xlsxMergeCells) getExtent(cellRef string) (int, int, error) {
 		return 0, 0, nil
 	}
 	for _, cell := range mc.Cells {
-		if strings.HasPrefix(cell.Ref, cellRef+":") {
-			parts := strings.Split(cell.Ref, ":")
+		if strings.HasPrefix(cell.Ref, cellRef+cellRangeChar) {
+			parts := strings.Split(cell.Ref, cellRangeChar)
 			startx, starty, err := GetCoordsFromCellIDString(parts[0])
 			if err != nil {
 				return -1, -1, err

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor