Browse Source

Disassociate CellMetadata, StreamStyle and DefaultCellType from Col

Geoffrey J. Teale 6 years ago
parent
commit
b27fdc7e50
14 changed files with 397 additions and 433 deletions
  1. 10 10
      cell.go
  2. 25 82
      col.go
  3. 9 18
      col_test.go
  4. 12 2
      data_validation.go
  5. 68 70
      data_validation_test.go
  6. 39 38
      lib.go
  7. 42 55
      sheet.go
  8. 4 8
      sheet_test.go
  9. 42 26
      stream_file.go
  10. 63 58
      stream_file_builder.go
  11. 5 5
      stream_style.go
  12. 16 4
      stream_style_test.go
  13. 62 52
      stream_test.go
  14. 0 5
      xmlWorksheet.go

+ 10 - 10
cell.go

@@ -392,27 +392,27 @@ func (c *Cell) SetDataValidation(dd *xlsxDataValidation) {
 	c.DataValidation = dd
 }
 
-// CellMetadata represents anything attributable to a cell
+// StreamingCellMetadata represents anything attributable to a cell
 // except for the cell data itself. For example, it is used
 // in StreamFileBuilder.AddSheetWithDefaultColumnMetadata to
 // associate default attributes for cells in a particular column
-type CellMetadata struct {
+type StreamingCellMetadata struct {
 	cellType    CellType
 	streamStyle StreamStyle
 }
 
 var (
-	DefaultStringCellMetadata  CellMetadata
-	DefaultNumericCellMetadata CellMetadata
-	DefaultDecimalCellMetadata CellMetadata
-	DefaultIntegerCellMetadata CellMetadata
-	DefaultDateCellMetadata    CellMetadata
+	DefaultStringStreamingCellMetadata  StreamingCellMetadata
+	DefaultNumericStreamingCellMetadata StreamingCellMetadata
+	DefaultDecimalStreamingCellMetadata StreamingCellMetadata
+	DefaultIntegerStreamingCellMetadata StreamingCellMetadata
+	DefaultDateStreamingCellMetadata    StreamingCellMetadata
 )
 
-func MakeCellMetadata(cellType CellType, streamStyle StreamStyle) CellMetadata {
-	return CellMetadata{cellType, streamStyle}
+func MakeStreamingCellMetadata(cellType CellType, streamStyle StreamStyle) StreamingCellMetadata {
+	return StreamingCellMetadata{cellType, streamStyle}
 }
 
-func (cm CellMetadata) Ptr() *CellMetadata {
+func (cm StreamingCellMetadata) Ptr() *StreamingCellMetadata {
 	return &cm
 }

+ 25 - 82
col.go

@@ -6,20 +6,19 @@ const Excel2006MaxRowCount = 1048576
 const Excel2006MaxRowIndex = Excel2006MaxRowCount - 1
 
 type Col struct {
-	Min             int
-	Max             int
-	Hidden          bool
-	Width           float64
-	Collapsed       bool
-	OutlineLevel    uint8
-	BestFit         bool
-	CustomWidth     bool
-	Phonetic        bool
-	numFmt          string
-	parsedNumFmt    *parsedNumberFormat
-	style           *Style
-	defaultCellType *CellType
-	outXfID         int
+	Min          int
+	Max          int
+	Hidden       bool
+	Width        float64
+	Collapsed    bool
+	OutlineLevel uint8
+	BestFit      bool
+	CustomWidth  bool
+	Phonetic     bool
+	numFmt       string
+	parsedNumFmt *parsedNumberFormat
+	style        *Style
+	outXfID      int
 }
 
 // NewColForRange return a pointer to a new Col, which will apply to
@@ -67,13 +66,6 @@ func (c *Col) SetType(cellType CellType) {
 	}
 }
 
-// SetCellMetadata sets the CellMetadata related attributes
-// of a Col
-func (c *Col) SetCellMetadata(cellMetadata CellMetadata) {
-	c.defaultCellType = &cellMetadata.cellType
-	c.SetStreamStyle(cellMetadata.streamStyle)
-}
-
 // GetStyle returns the Style associated with a Col
 func (c *Col) GetStyle() *Style {
 	return c.style
@@ -84,53 +76,6 @@ 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 *xlsxDataValidation, start, end int) {
-// 	if end < 0 {
-// 		end = Excel2006MaxRowIndex
-// 	}
-
-// 	dd.minRow = start
-// 	dd.maxRow = end
-
-// 	tmpDD := make([]*xlsxDataValidation, 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(xlsxDataValidation)
-// 			*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 *xlsxDataValidation, 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
@@ -155,20 +100,18 @@ func (c *Col) SetOutlineLevel(outlineLevel uint8) {
 // resulting Col into the Col Store.
 func (c *Col) copyToRange(min, max int) *Col {
 	return &Col{
-		Min:             min,
-		Max:             max,
-		Hidden:          c.Hidden,
-		Width:           c.Width,
-		Collapsed:       c.Collapsed,
-		OutlineLevel:    c.OutlineLevel,
-		BestFit:         c.BestFit,
-		CustomWidth:     c.CustomWidth,
-		Phonetic:        c.Phonetic,
-		numFmt:          c.numFmt,
-		parsedNumFmt:    c.parsedNumFmt,
-		style:           c.style,
-		DataValidation:  append([]*xlsxDataValidation{}, c.DataValidation...),
-		defaultCellType: c.defaultCellType,
+		Min:          min,
+		Max:          max,
+		Hidden:       c.Hidden,
+		Width:        c.Width,
+		Collapsed:    c.Collapsed,
+		OutlineLevel: c.OutlineLevel,
+		BestFit:      c.BestFit,
+		CustomWidth:  c.CustomWidth,
+		Phonetic:     c.Phonetic,
+		numFmt:       c.numFmt,
+		parsedNumFmt: c.parsedNumFmt,
+		style:        c.style,
 	}
 }
 

+ 9 - 18
col_test.go

@@ -55,21 +55,16 @@ func TestCol(t *testing.T) {
 	c.Run("copyToRange", func(c *qt.C) {
 		nf := &parsedNumberFormat{}
 		s := &Style{}
-		cdv1 := &xlsxDataValidation{}
-		cdv2 := &xlsxDataValidation{}
-		ct := CellTypeBool.Ptr()
 		c1 := &Col{
-			Min:             1,
-			Max:             11,
-			Hidden:          true,
-			Width:           300.4,
-			Collapsed:       true,
-			OutlineLevel:    2,
-			numFmt:          "-0.00",
-			parsedNumFmt:    nf,
-			style:           s,
-			DataValidation:  []*xlsxDataValidation{cdv1, cdv2},
-			defaultCellType: ct,
+			Min:          1,
+			Max:          11,
+			Hidden:       true,
+			Width:        300.4,
+			Collapsed:    true,
+			OutlineLevel: 2,
+			numFmt:       "-0.00",
+			parsedNumFmt: nf,
+			style:        s,
 		}
 
 		c2 := c1.copyToRange(4, 10)
@@ -82,10 +77,6 @@ func TestCol(t *testing.T) {
 		c.Assert(c2.numFmt, qt.Equals, c1.numFmt)
 		c.Assert(c2.parsedNumFmt, qt.Equals, c1.parsedNumFmt)
 		c.Assert(c2.style, qt.Equals, c1.style)
-		c.Assert(c2.DataValidation, qt.HasLen, 2)
-		c.Assert(c2.DataValidation[0], qt.Equals, c1.DataValidation[0])
-		c.Assert(c2.DataValidation[1], qt.Equals, c1.DataValidation[1])
-		c.Assert(c2.defaultCellType, qt.Equals, c1.defaultCellType)
 	})
 
 }

+ 12 - 2
data_validation.go

@@ -61,10 +61,20 @@ const (
 	DataValidationOperatorNotEqual
 )
 
-// NewXlsxCellDataValidation return data validation struct
-func NewXlsxCellDataValidation(allowBlank bool) *xlsxDataValidation {
+// NewDataValidation return data validation struct
+func NewDataValidation(startRow, startCol, endRow, endCol int, allowBlank bool) *xlsxDataValidation {
+	startX := ColIndexToLetters(startCol)
+	startY := RowIndexToString(startRow)
+	endX := ColIndexToLetters(endCol)
+	endY := RowIndexToString(endRow)
+
+	sqref := startX + startY
+	if startX != endX || startY != endY {
+		sqref += ":" + endX + endY
+	}
 	return &xlsxDataValidation{
 		AllowBlank: allowBlank,
+		Sqref:      sqref,
 	}
 }
 

+ 68 - 70
data_validation_test.go

@@ -28,207 +28,205 @@ func TestDataValidation(t *testing.T) {
 	cell = row.AddCell()
 	cell.Value = "a1"
 
-	dd := NewXlsxCellDataValidation(true)
+	dd := NewDataValidation(0, 0, 0, 0, true)
 	err = dd.SetDropList([]string{"a1", "a2", "a3"})
 	c.Assert(err, qt.IsNil)
 
 	dd.SetInput(&title, &msg)
 	cell.SetDataValidation(dd)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(2, 0, 2, 0, true)
 	err = dd.SetDropList([]string{"c1", "c2", "c3"})
 	c.Assert(err, qt.IsNil)
 	title = "col c"
 	dd.SetInput(&title, &msg)
-	sheet.SetDataValidation(2, 2, dd, 0, 0)
+	sheet.AddDataValidation(dd)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(3, 3, 3, 7, true)
 	err = dd.SetDropList([]string{"d", "d1", "d2"})
 	c.Assert(err, qt.IsNil)
 	title = "col d range"
 	dd.SetInput(&title, &msg)
-	sheet.SetDataValidation(3, 3, dd, 3, 7)
+	sheet.AddDataValidation(dd)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(4, 1, 4, Excel2006MaxRowIndex, true)
 	err = dd.SetDropList([]string{"e1", "e2", "e3"})
 	c.Assert(err, qt.IsNil)
 	title = "col e start 3"
 	dd.SetInput(&title, &msg)
-	sheet.SetDataValidationWithStart(4, 4, dd, 1)
+	sheet.AddDataValidation(dd)
 
 	index := 5
 	rowIndex := 1
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(15, 4, DataValidationTypeTextLeng, DataValidationOperatorBetween)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThanOrEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorGreaterThan)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThan)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorLessThanOrEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeTextLeng, DataValidationOperatorNotBetween)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
 	rowIndex++
 	index = 5
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(4, 15, DataValidationTypeWhole, DataValidationOperatorBetween)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThanOrEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorGreaterThan)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThan)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorLessThanOrEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 1, DataValidationTypeWhole, DataValidationOperatorNotEqual)
 	c.Assert(err, qt.IsNil)
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(rowIndex, index, rowIndex, index, true)
 	err = dd.SetRange(10, 50, DataValidationTypeWhole, DataValidationOperatorNotBetween)
 	if err != nil {
 		t.Fatal(err)
 	}
-	sheet.Cell(rowIndex, index).SetDataValidation(dd)
+	sheet.AddDataValidation(dd)
 	index++
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(12, 2, 12, 10, true)
 	err = dd.SetDropList([]string{"1", "2", "4"})
 	c.Assert(err, qt.IsNil)
-	dd1 := NewXlsxCellDataValidation(true)
+	dd1 := NewDataValidation(12, 3, 12, 4, true)
 	err = dd1.SetDropList([]string{"11", "22", "44"})
 	c.Assert(err, qt.IsNil)
-	dd2 := NewXlsxCellDataValidation(true)
+	dd2 := NewDataValidation(12, 5, 12, 7, true)
 	err = dd2.SetDropList([]string{"111", "222", "444"})
 	c.Assert(err, qt.IsNil)
-	sheet.SetDataValidation(12, 12, dd, 2, 10)
-	sheet.SetDataValidation(12, 12, dd1, 3, 4)
-	sheet.SetDataValidation(12, 12, dd2, 5, 7)
+	sheet.AddDataValidation(dd)
+	sheet.AddDataValidation(dd1)
+	sheet.AddDataValidation(dd2)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(13, 2, 13, 10, true)
 	err = dd.SetDropList([]string{"1", "2", "4"})
 	c.Assert(err, qt.IsNil)
-	dd1 = NewXlsxCellDataValidation(true)
+	dd1 = NewDataValidation(13, 1, 13, 2, true)
 	err = dd1.SetDropList([]string{"11", "22", "44"})
 	c.Assert(err, qt.IsNil)
-	sheet.SetDataValidation(13, 13, dd, 2, 10)
-	sheet.SetDataValidation(13, 13, dd1, 1, 2)
+	sheet.AddDataValidation(dd)
+	sheet.AddDataValidation(dd1)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(14, 2, 14, 10, true)
 	err = dd.SetDropList([]string{"1", "2", "4"})
 	c.Assert(err, qt.IsNil)
-	dd1 = NewXlsxCellDataValidation(true)
+	dd1 = NewDataValidation(14, 1, 14, 5, true)
 	err = dd1.SetDropList([]string{"11", "22", "44"})
 	c.Assert(err, qt.IsNil)
-	sheet.SetDataValidation(14, 14, dd, 2, 10)
-	sheet.SetDataValidation(14, 14, dd1, 1, 5)
+	sheet.AddDataValidation(dd)
+	sheet.AddDataValidation(dd1)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(15, 2, 15, 10, true)
 	err = dd.SetDropList([]string{"1", "2", "4"})
 	if err != nil {
 		t.Fatal(err)
 	}
-	dd1 = NewXlsxCellDataValidation(true)
+	dd1 = NewDataValidation(15, 1, 15, 10, true)
 	err = dd1.SetDropList([]string{"11", "22", "44"})
 	c.Assert(err, qt.IsNil)
-	sheet.SetDataValidation(15, 15, dd, 2, 10)
-	sheet.SetDataValidation(15, 15, dd1, 1, 10)
+	sheet.AddDataValidation(dd)
+	sheet.AddDataValidation(dd1)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(16, 10, 16, 20, true)
 	err = dd.SetDropList([]string{"1", "2", "4"})
 	c.Assert(err, qt.IsNil)
-	dd1 = NewXlsxCellDataValidation(true)
+	dd1 = NewDataValidation(16, 2, 16, 4, true)
 	err = dd1.SetDropList([]string{"11", "22", "44"})
 	c.Assert(err, qt.IsNil)
-	dd2 = NewXlsxCellDataValidation(true)
+	dd2 = NewDataValidation(16, 12, 16, 30, true)
 	err = dd2.SetDropList([]string{"111", "222", "444"})
 	c.Assert(err, qt.IsNil)
-	sheet.SetDataValidation(16, 16, dd, 10, 20)
-	sheet.SetDataValidation(16, 16, dd1, 2, 4)
-	sheet.SetDataValidation(16, 16, dd2, 21, 30)
+	sheet.AddDataValidation(dd)
+	sheet.AddDataValidation(dd1)
+	sheet.AddDataValidation(dd2)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(3, 3, 3, Excel2006MaxRowIndex, true)
 	err = dd.SetDropList([]string{"d", "d1", "d2"})
 	c.Assert(err, qt.IsNil)
 	title = "col d range"
 	dd.SetInput(&title, &msg)
-	sheet.SetDataValidation(3, 3, dd, 3, Excel2006MaxRowIndex)
+	sheet.AddDataValidation(dd)
 
-	dd = NewXlsxCellDataValidation(true)
+	dd = NewDataValidation(3, 4, 3, Excel2006MaxRowIndex, true)
 	err = dd.SetDropList([]string{"d", "d1", "d2"})
 	c.Assert(err, qt.IsNil)
 	title = "col d range"
 	dd.SetInput(&title, &msg)
-	sheet.SetDataValidation(3, 3, dd, 4, -1)
-	maxRow := sheet.Col(3).DataValidation[len(sheet.Col(3).DataValidation)-1].maxRow
-	c.Assert(maxRow, qt.Equals, Excel2006MaxRowIndex)
+	sheet.AddDataValidation(dd)
 
 	dest := &bytes.Buffer{}
 	err = file.Write(dest)
@@ -244,7 +242,7 @@ func TestDataValidation(t *testing.T) {
 func TestDataValidation2(t *testing.T) {
 	c := qt.New(t)
 	// Show error and show info start disabled, but automatically get enabled when setting a message
-	dd := NewXlsxCellDataValidation(true)
+	dd := NewDataValidation(0, 0, 0, 0, true)
 	c.Assert(dd.ShowErrorMessage, qt.Equals, false)
 	c.Assert(dd.ShowInputMessage, qt.Equals, false)
 

+ 39 - 38
lib.go

@@ -719,44 +719,45 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 	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(xlsxDataValidation)
-						*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(xlsxDataValidation)
-							*newDD = *dd
-							newDD.Sqref = ""
-							sheet.Col(i).SetDataValidation(dd, minRow, maxRow)
-						}
-
-					}
-				} else {
-					newDD := new(xlsxDataValidation)
-					*newDD = *dd
-					newDD.Sqref = ""
-					sheet.Cell(minRow, minCol).SetDataValidation(dd)
-
-				}
-			}
+			sheet.AddDataValidation(dd)
+			// 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(xlsxDataValidation)
+			// 			*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(xlsxDataValidation)
+			// 				*newDD = *dd
+			// 				newDD.Sqref = ""
+			// 				sheet.Col(i).SetDataValidation(dd, minRow, maxRow)
+			// 			}
+
+			// 		}
+			// 	} else {
+			// 		newDD := new(xlsxDataValidation)
+			// 		*newDD = *dd
+			// 		newDD.Sqref = ""
+			// 		sheet.Cell(minRow, minCol).SetDataValidation(dd)
+
+			// 	}
+			// }
 
 		}
 

+ 42 - 55
sheet.go

@@ -9,17 +9,18 @@ import (
 // Sheet is a high level structure intended to provide user access to
 // the contents of a particular sheet within an XLSX file.
 type Sheet struct {
-	Name        string
-	File        *File
-	Rows        []*Row
-	Cols        *ColStore
-	MaxRow      int
-	MaxCol      int
-	Hidden      bool
-	Selected    bool
-	SheetViews  []SheetView
-	SheetFormat SheetFormat
-	AutoFilter  *AutoFilter
+	Name            string
+	File            *File
+	Rows            []*Row
+	Cols            *ColStore
+	MaxRow          int
+	MaxCol          int
+	Hidden          bool
+	Selected        bool
+	SheetViews      []SheetView
+	SheetFormat     SheetFormat
+	AutoFilter      *AutoFilter
+	DataValidations []*xlsxDataValidation
 }
 
 type SheetView struct {
@@ -74,6 +75,11 @@ func (s *Sheet) AddRowAtIndex(index int) (*Row, error) {
 	return row, nil
 }
 
+// Add a DataValidation to a range of cells
+func (s *Sheet) AddDataValidation(dv *xlsxDataValidation) {
+	s.DataValidations = append(s.DataValidations, dv)
+}
+
 // Removes a row at a specific index
 func (s *Sheet) RemoveRowAtIndex(index int) error {
 	if index < 0 || index >= len(s.Rows) {
@@ -196,20 +202,6 @@ func (s *Sheet) SetColWidth(min, max int, width float64) {
 	})
 }
 
-// Set the data validation for a range of columns.
-func (s *Sheet) SetDataValidation(minCol, maxCol int, dd *xlsxDataValidation, minRow, maxRow int) {
-	s.setCol(minCol, maxCol, func(col *Col) {
-		col.SetDataValidation(dd, minRow, maxRow)
-	})
-}
-
-// Set the data validation for a range of columns.
-func (s *Sheet) SetDataValidationWithStart(minCol, maxCol int, dd *xlsxDataValidation, start int) {
-	s.setCol(minCol, maxCol, func(col *Col) {
-		col.SetDataValidation(dd, start, -1)
-	})
-}
-
 // Set the outline level for a range of columns.
 func (s *Sheet) SetOutlineLevel(minCol, maxCol int, outlineLevel uint8) {
 	s.setCol(minCol, maxCol, func(col *Col) {
@@ -225,20 +217,6 @@ func (s *Sheet) SetType(minCol, maxCol int, cellType CellType) {
 
 }
 
-// Set the cell metadata for a range of columns.
-func (s *Sheet) SetCellMetadata(minCol, maxCol int, cm CellMetadata) {
-	s.setCol(minCol, maxCol, func(col *Col) {
-		col.SetCellMetadata(cm)
-	})
-}
-
-// Set the stream style for a range of columns.
-func (s *Sheet) SetStreamStyle(minCol, maxCol int, ss StreamStyle) {
-	s.setCol(minCol, maxCol, func(col *Col) {
-		col.SetStreamStyle(ss)
-	})
-}
-
 // When merging cells, the cell may be the 'original' or the 'covered'.
 // First, figure out which cells are merge starting points. Then create
 // the necessary cells underlying the merge area.
@@ -341,24 +319,20 @@ func (s *Sheet) makeCols(worksheet *xlsxWorksheet, styles *xlsxStyleSheet) (maxL
 			if col.OutlineLevel > maxLevelCol {
 				maxLevelCol = col.OutlineLevel
 			}
-			if nil != col.DataValidation {
-				if nil == worksheet.DataValidations {
-					worksheet.DataValidations = &xlsxDataValidations{}
-				}
-				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)
-			}
+			// if nil != col.DataValidation {
+			// 	if nil == worksheet.DataValidations {
+			// 		worksheet.DataValidations = &xlsxDataValidations{}
+			// 	}
+			// 	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)
+			// 		}
 
 		})
+
 	return maxLevelCol
 }
 
@@ -494,6 +468,18 @@ func (s *Sheet) makeRows(worksheet *xlsxWorksheet, styles *xlsxStyleSheet, refTa
 
 }
 
+func (s *Sheet) makeDataValidations(worksheet *xlsxWorksheet) {
+	if len(s.DataValidations) > 0 {
+		if worksheet.DataValidations == nil {
+			worksheet.DataValidations = &xlsxDataValidations{}
+		}
+		for _, dv := range s.DataValidations {
+			worksheet.DataValidations.DataValidation = append(worksheet.DataValidations.DataValidation, dv)
+		}
+		worksheet.DataValidations.Count = len(worksheet.DataValidations.DataValidation)
+	}
+}
+
 // Dump sheet to its XML representation, intended for internal use only
 func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxWorksheet {
 	worksheet := newXlsxWorksheet()
@@ -506,6 +492,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 	s.makeSheetView(worksheet)
 	s.makeSheetFormatPr(worksheet)
 	maxLevelCol := s.makeCols(worksheet, styles)
+	s.makeDataValidations(worksheet)
 	s.makeRows(worksheet, styles, refTable, maxLevelCol)
 
 	return worksheet

+ 4 - 8
sheet_test.go

@@ -332,17 +332,13 @@ func TestSetDataValidation(t *testing.T) {
 	file := NewFile()
 	sheet, _ := file.AddSheet("Sheet1")
 
-	dd := NewXlsxCellDataValidation(true)
+	dd := NewDataValidation(0, 0, 10, 0, true)
 	err := dd.SetDropList([]string{"a1", "a2", "a3"})
 	c.Assert(err, qt.IsNil)
 
-	sheet.SetDataValidation(0, 10, dd, 0, 0)
-	col := sheet.Cols.FindColByIndex(0)
-	c.Assert(col, notNil)
-	c.Assert(col.Min, qt.Equals, 0)
-	c.Assert(col.Max, qt.Equals, 10)
-	c.Assert(col.DataValidation, qt.HasLen, 1)
-	c.Assert(col.DataValidation[0], qt.Equals, dd)
+	sheet.AddDataValidation(dd)
+	c.Assert(sheet.DataValidations, qt.HasLen, 1)
+	c.Assert(sheet.DataValidations[0], qt.Equals, dd)
 }
 
 func (s *SheetSuite) TestSetRowHeightCM(c *C) {

+ 42 - 26
stream_file.go

@@ -9,14 +9,17 @@ import (
 )
 
 type StreamFile struct {
-	xlsxFile       *File
-	sheetXmlPrefix []string
-	sheetXmlSuffix []string
-	zipWriter      *zip.Writer
-	currentSheet   *streamSheet
-	styleIds       [][]int
-	styleIdMap     map[StreamStyle]int
-	err            error
+	xlsxFile               *File
+	sheetXmlPrefix         []string
+	sheetXmlSuffix         []string
+	zipWriter              *zip.Writer
+	currentSheet           *streamSheet
+	styleIds               [][]int
+	styleIdMap             map[StreamStyle]int
+	streamingCellMetadatas map[int]*StreamingCellMetadata
+	sheetStreamStyles      map[int]cellStreamStyle
+	sheetDefaultCellType   map[int]defaultCellType
+	err                    error
 }
 
 type streamSheet struct {
@@ -56,14 +59,14 @@ func (sf *StreamFile) Write(cells []string) error {
 // WriteWithColumnDefaultMetadata will write a row of cells to the current sheet. Every call to WriteWithColumnDefaultMetadata
 // on the same sheet must contain the same number of cells as the header provided when the sheet was created or
 // an error will be returned. This function will always trigger a flush on success. Each cell will be encoded with the
-// default CellMetadata of the column that it belongs to. However, if the cell data string cannot be
-// parsed into the cell type in CellMetadata, we fall back on encoding the cell as a string and giving it a default
+// default StreamingCellMetadata of the column that it belongs to. However, if the cell data string cannot be
+// parsed into the cell type in StreamingCellMetadata, we fall back on encoding the cell as a string and giving it a default
 // string style
-func (sf *StreamFile) WriteWithColumnDefaultMetadata(cells []string, cellCount int) error {
+func (sf *StreamFile) WriteWithColumnDefaultMetadata(cells []string) error {
 	if sf.err != nil {
 		return sf.err
 	}
-	err := sf.writeWithColumnDefaultMetadata(cells, cellCount)
+	err := sf.writeWithColumnDefaultMetadata(cells)
 	if err != nil {
 		sf.err = err
 		return err
@@ -122,8 +125,12 @@ func (sf *StreamFile) write(cells []string) error {
 	if sf.currentSheet == nil {
 		return NoCurrentSheetError
 	}
-	if len(cells) != sf.currentSheet.columnCount {
-		return WrongNumberOfRowsError
+	cellCount := len(cells)
+	if cellCount != sf.currentSheet.columnCount {
+		if sf.currentSheet.columnCount != 0 {
+			return WrongNumberOfRowsError
+		}
+		sf.currentSheet.columnCount = cellCount
 	}
 
 	sf.currentSheet.rowCount++
@@ -166,13 +173,14 @@ func (sf *StreamFile) write(cells []string) error {
 	return sf.zipWriter.Flush()
 }
 
-func (sf *StreamFile) writeWithColumnDefaultMetadata(cells []string, cellCount int) error {
+func (sf *StreamFile) writeWithColumnDefaultMetadata(cells []string) error {
 
 	if sf.currentSheet == nil {
 		return NoCurrentSheetError
 	}
 
-	currentSheet := sf.xlsxFile.Sheets[sf.currentSheet.index-1]
+	sheetIndex := sf.currentSheet.index - 1
+	currentSheet := sf.xlsxFile.Sheets[sheetIndex]
 
 	var streamCells []StreamCell
 
@@ -180,12 +188,17 @@ func (sf *StreamFile) writeWithColumnDefaultMetadata(cells []string, cellCount i
 		panic("trying to use uninitialised ColStore")
 	}
 
-	if len(cells) != cellCount {
-		return WrongNumberOfRowsError
+	if len(cells) != sf.currentSheet.columnCount {
+		if sf.currentSheet.columnCount != 0 {
+			return WrongNumberOfRowsError
+
+		}
+		sf.currentSheet.columnCount = len(cells)
 	}
 
+	cSS := sf.sheetStreamStyles[sheetIndex]
+	cDCT := sf.sheetDefaultCellType[sheetIndex]
 	for ci, c := range cells {
-		col := currentSheet.Col(ci)
 		// TODO: Legacy code paths like `StreamFileBuilder.AddSheet` could
 		// leave style empty and if cell data cannot be parsed into cell type then
 		// we need a sensible default StreamStyle to fall back to
@@ -194,14 +207,17 @@ func (sf *StreamFile) writeWithColumnDefaultMetadata(cells []string, cellCount i
 		// parse into the default cell type and if parsing fails fall back
 		// to some sensible default
 		cellType := CellTypeInline
-		if col != nil {
-			defaultType := col.defaultCellType
-			// TODO: Again `CellType` could be nil if sheet was created through
-			// legacy code path so, like style, hardcoding for now
-			cellType = defaultType.fallbackTo(cells[col.Min-1], CellTypeString)
-			if defaultType != nil && *defaultType == cellType {
-				style = col.GetStreamStyle()
+		if dct, ok := cDCT[ci]; ok {
+			defaultType := dct
+			cellType = defaultType.fallbackTo(cells[ci], CellTypeString)
+			if ss, ok := cSS[ci]; ok {
+				// TODO: Again `CellType` could be nil if sheet was created through
+				// legacy code path so, like style, hardcoding for now
+				if defaultType != nil && *defaultType == cellType {
+					style = ss
+				}
 			}
+
 		}
 
 		streamCells = append(

+ 63 - 58
stream_file_builder.go

@@ -39,18 +39,24 @@ import (
 	"strings"
 )
 
+type cellStreamStyle map[int]StreamStyle
+type defaultCellType map[int]*CellType
+
 type StreamFileBuilder struct {
-	built                          bool
-	firstSheetAdded                bool
-	customStylesAdded              bool
-	xlsxFile                       *File
-	zipWriter                      *zip.Writer
-	cellTypeToStyleIds             map[CellType]int
-	maxStyleId                     int
-	styleIds                       [][]int
-	customStreamStyles             map[StreamStyle]struct{}
-	styleIdMap                     map[StreamStyle]int
-	defaultColumnCellMetadataAdded bool
+	built                                   bool
+	firstSheetAdded                         bool
+	customStylesAdded                       bool
+	xlsxFile                                *File
+	zipWriter                               *zip.Writer
+	cellTypeToStyleIds                      map[CellType]int
+	maxStyleId                              int
+	styleIds                                [][]int
+	customStreamStyles                      map[StreamStyle]struct{}
+	styleIdMap                              map[StreamStyle]int
+	streamingCellMetadatas                  map[int]*StreamingCellMetadata
+	sheetStreamStyles                       map[int]cellStreamStyle
+	sheetDefaultCellType                    map[int]defaultCellType
+	defaultColumnStreamingCellMetadataAdded bool
 }
 
 const (
@@ -69,12 +75,15 @@ var BuiltStreamFileBuilderError = errors.New("StreamFileBuilder has already been
 // NewStreamFileBuilder creates an StreamFileBuilder that will write to the the provided io.writer
 func NewStreamFileBuilder(writer io.Writer) *StreamFileBuilder {
 	return &StreamFileBuilder{
-		zipWriter:          zip.NewWriter(writer),
-		xlsxFile:           NewFile(),
-		cellTypeToStyleIds: make(map[CellType]int),
-		maxStyleId:         initMaxStyleId,
-		customStreamStyles: make(map[StreamStyle]struct{}),
-		styleIdMap:         make(map[StreamStyle]int),
+		zipWriter:              zip.NewWriter(writer),
+		xlsxFile:               NewFile(),
+		cellTypeToStyleIds:     make(map[CellType]int),
+		maxStyleId:             initMaxStyleId,
+		customStreamStyles:     make(map[StreamStyle]struct{}),
+		styleIdMap:             make(map[StreamStyle]int),
+		streamingCellMetadatas: make(map[int]*StreamingCellMetadata),
+		sheetStreamStyles:      make(map[int]cellStreamStyle),
+		sheetDefaultCellType:   make(map[int]defaultCellType),
 	}
 }
 
@@ -91,13 +100,11 @@ func NewStreamFileBuilderForPath(path string) (*StreamFileBuilder, error) {
 // AddSheet will add sheets with the given name with the provided headers. The headers cannot be edited later, and all
 // rows written to the sheet must contain the same number of cells as the header. Sheet names must be unique, or an
 // error will be thrown.
-func (sb *StreamFileBuilder) AddSheet(name string, headers []string, cellTypes []*CellType) error {
+func (sb *StreamFileBuilder) AddSheet(name string, cellTypes []*CellType) error {
 	if sb.built {
 		return BuiltStreamFileBuilderError
 	}
-	if len(cellTypes) > len(headers) {
-		return errors.New("cellTypes is longer than headers")
-	}
+
 	sheet, err := sb.xlsxFile.AddSheet(name)
 	if err != nil {
 		// Set built on error so that all subsequent calls to the builder will also fail.
@@ -105,12 +112,7 @@ func (sb *StreamFileBuilder) AddSheet(name string, headers []string, cellTypes [
 		return err
 	}
 	sb.styleIds = append(sb.styleIds, []int{})
-	row := sheet.AddRow()
-	if count := row.WriteSlice(&headers, -1); count != len(headers) {
-		// Set built on error so that all subsequent calls to the builder will also fail.
-		sb.built = true
-		return errors.New("failed to write headers")
-	}
+
 	for i, cellType := range cellTypes {
 		var cellStyleIndex int
 		var ok bool
@@ -134,43 +136,39 @@ func (sb *StreamFileBuilder) AddSheet(name string, headers []string, cellTypes [
 	return nil
 }
 
-func (sb *StreamFileBuilder) AddSheetWithDefaultColumnMetadata(name string, headers []string, columnsDefaultCellMetadata []*CellMetadata) error {
+func (sb *StreamFileBuilder) AddSheetWithDefaultColumnMetadata(name string, columnsDefaultStreamingCellMetadata []*StreamingCellMetadata) error {
 	if sb.built {
 		return BuiltStreamFileBuilderError
 	}
-	if len(columnsDefaultCellMetadata) > len(headers) {
-		return errors.New("columnsDefaultCellMetadata is longer than headers")
-	}
-	sheet, err := sb.xlsxFile.AddSheet(name)
+	_, err := sb.xlsxFile.AddSheet(name)
 	if err != nil {
 		// Set built on error so that all subsequent calls to the builder will also fail.
 		sb.built = true
 		return err
 	}
 	sb.styleIds = append(sb.styleIds, []int{})
-	row := sheet.AddRow()
-	if count := row.WriteSlice(&headers, -1); count != len(headers) {
-		// Set built on error so that all subsequent calls to the builder will also fail.
-		sb.built = true
-		return errors.New("failed to write headers")
-	}
+	sheetIndex := len(sb.xlsxFile.Sheets) - 1
 
-	for i, cellMetadata := range columnsDefaultCellMetadata {
+	cSS := make(cellStreamStyle)
+	dCT := make(defaultCellType)
+	for i, streamingCellMetadata := range columnsDefaultStreamingCellMetadata {
 		var cellStyleIndex int
 		var ok bool
-		if cellMetadata != nil {
+		if streamingCellMetadata != nil {
 			// Exact same logic as `AddSheet` to ensure compatibility as much as possible
 			// with the `AddSheet` + `StreamFile.Write` code path
-			cellStyleIndex, ok = sb.cellTypeToStyleIds[cellMetadata.cellType]
+			cellStyleIndex, ok = sb.cellTypeToStyleIds[streamingCellMetadata.cellType]
 			if !ok {
 				sb.maxStyleId++
 				cellStyleIndex = sb.maxStyleId
-				sb.cellTypeToStyleIds[cellMetadata.cellType] = sb.maxStyleId
+				sb.cellTypeToStyleIds[streamingCellMetadata.cellType] = sb.maxStyleId
 			}
 
 			// Add streamStyle and set default cell metadata on col
-			sb.customStreamStyles[cellMetadata.streamStyle] = struct{}{}
-			sheet.SetCellMetadata(i+1, i+1, *cellMetadata)
+			sb.customStreamStyles[streamingCellMetadata.streamStyle] = struct{}{}
+			sb.streamingCellMetadatas[i+1] = streamingCellMetadata
+			cSS[i] = streamingCellMetadata.streamStyle
+			dCT[i] = streamingCellMetadata.cellType.Ptr()
 		}
 		sb.styleIds[len(sb.styleIds)-1] = append(sb.styleIds[len(sb.styleIds)-1], cellStyleIndex)
 	}
@@ -180,7 +178,9 @@ func (sb *StreamFileBuilder) AddSheetWithDefaultColumnMetadata(name string, head
 	sb.customStylesAdded = true
 	// Hack to ensure the `dimension` tag on each `worksheet` xml is stripped. Otherwise only the first
 	// row of each worksheet will be read back rather than all rows
-	sb.defaultColumnCellMetadataAdded = true
+	sb.defaultColumnStreamingCellMetadataAdded = true
+	sb.sheetStreamStyles[sheetIndex] = cSS
+	sb.sheetDefaultCellType[sheetIndex] = dCT
 	return nil
 }
 
@@ -216,22 +216,24 @@ func (sb *StreamFileBuilder) AddSheetS(name string, columnStyles []StreamStyle)
 		panic("trying to use uninitialised ColStore")
 	}
 
+	cSS := make(map[int]StreamStyle)
 	// Set default column styles based on the cel styles in the first row
 	// Set the default column width to 11. This makes enough places for the
 	// default date style cells to display the dates correctly
 	for i, colStyle := range columnStyles {
 		colNum := i + 1
-		sheet.SetStreamStyle(colNum, colNum, colStyle)
+		cSS[colNum] = colStyle
 		sheet.SetColWidth(colNum, colNum, 11)
 	}
+	sheetIndex := len(sb.xlsxFile.Sheets) - 1
+	sb.sheetStreamStyles[sheetIndex] = cSS
 	return nil
 }
 
-// AddValidation will add a validation to a specific column.
-func (sb *StreamFileBuilder) AddValidation(sheetIndex, colIndex, rowStartIndex int, validation *xlsxDataValidation) {
+// AddValidation will add a validation to a sheet.
+func (sb *StreamFileBuilder) AddValidation(sheetIndex int, validation *xlsxDataValidation) {
 	sheet := sb.xlsxFile.Sheets[sheetIndex]
-	column := sheet.Col(colIndex)
-	column.SetDataValidationWithStart(validation, rowStartIndex)
+	sheet.AddDataValidation(validation)
 }
 
 // Build begins streaming the XLSX file to the io, by writing all the XLSX metadata. It creates a StreamFile struct
@@ -255,23 +257,26 @@ func (sb *StreamFileBuilder) Build() (*StreamFile, error) {
 	}
 
 	es := &StreamFile{
-		zipWriter:      sb.zipWriter,
-		xlsxFile:       sb.xlsxFile,
-		sheetXmlPrefix: make([]string, len(sb.xlsxFile.Sheets)),
-		sheetXmlSuffix: make([]string, len(sb.xlsxFile.Sheets)),
-		styleIds:       sb.styleIds,
-		styleIdMap:     sb.styleIdMap,
+		zipWriter:              sb.zipWriter,
+		xlsxFile:               sb.xlsxFile,
+		sheetXmlPrefix:         make([]string, len(sb.xlsxFile.Sheets)),
+		sheetXmlSuffix:         make([]string, len(sb.xlsxFile.Sheets)),
+		styleIds:               sb.styleIds,
+		styleIdMap:             sb.styleIdMap,
+		streamingCellMetadatas: sb.streamingCellMetadatas,
+		sheetStreamStyles:      sb.sheetStreamStyles,
+		sheetDefaultCellType:   sb.sheetDefaultCellType,
 	}
 	for path, data := range parts {
 		// If the part is a sheet, don't write it yet. We only want to write the XLSX metadata files, since at this
 		// point the sheets are still empty. The sheet files will be written later as their rows come in.
 		if strings.HasPrefix(path, sheetFilePathPrefix) {
-			// sb.default ColumnCellMetadataAdded is a hack because neither the `AddSheet` nor `AddSheetS` codepaths
+			// sb.default ColumnStreamingCellMetadataAdded is a hack because neither the `AddSheet` nor `AddSheetS` codepaths
 			// actually encode a valid worksheet dimension. `AddSheet` encodes an empty one: "" and `AddSheetS` encodes
 			// an effectively empty one: "A1". `AddSheetWithDefaultColumnMetadata` uses logic from both paths which results
 			// in an effectively invalid dimension being encoded which, upon read, results in only reading in the header of
 			// a given worksheet and non of the rows that follow
-			if err := sb.processEmptySheetXML(es, path, data, !sb.customStylesAdded || sb.defaultColumnCellMetadataAdded); err != nil {
+			if err := sb.processEmptySheetXML(es, path, data, !sb.customStylesAdded || sb.defaultColumnStreamingCellMetadataAdded); err != nil {
 				return nil, err
 			}
 			continue

+ 5 - 5
stream_style.go

@@ -75,11 +75,11 @@ func init() {
 
 	StreamStyleDefaultDecimal = MakeDecimalStyle(DefaultFont(), DefaultFill(), DefaultAlignment(), DefaultBorder())
 
-	DefaultStringCellMetadata = CellMetadata{CellTypeString, StreamStyleDefaultString}
-	DefaultNumericCellMetadata = CellMetadata{CellTypeNumeric, StreamStyleDefaultString}
-	DefaultDecimalCellMetadata = CellMetadata{CellTypeNumeric, StreamStyleDefaultDecimal}
-	DefaultIntegerCellMetadata = CellMetadata{CellTypeNumeric, StreamStyleDefaultInteger}
-	DefaultDateCellMetadata = CellMetadata{CellTypeDate, StreamStyleDefaultDate}
+	DefaultStringStreamingCellMetadata = StreamingCellMetadata{CellTypeString, StreamStyleDefaultString}
+	DefaultNumericStreamingCellMetadata = StreamingCellMetadata{CellTypeNumeric, StreamStyleDefaultString}
+	DefaultDecimalStreamingCellMetadata = StreamingCellMetadata{CellTypeNumeric, StreamStyleDefaultDecimal}
+	DefaultIntegerStreamingCellMetadata = StreamingCellMetadata{CellTypeNumeric, StreamStyleDefaultInteger}
+	DefaultDateStreamingCellMetadata = StreamingCellMetadata{CellTypeDate, StreamStyleDefaultDate}
 }
 
 // MakeStyle creates a new StreamStyle and add it to the styles that will be streamed.

+ 16 - 4
stream_style_test.go

@@ -438,11 +438,13 @@ func TestStreamStyleDates(t *testing.T) {
 		filePath = fmt.Sprintf("Workbook_Date_test.xlsx")
 	}
 
+	// We use a fixed time to avoid weird errors around midnight
+	fixedTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
 	sheetNames := []string{"Sheet1"}
 	workbookData := [][][]StreamCell{
 		{
 			{NewStringStreamCell("Date:")},
-			{NewDateStreamCell(time.Now())},
+			{NewDateStreamCell(fixedTime)},
 		},
 	}
 
@@ -473,7 +475,7 @@ func TestStreamStyleDates(t *testing.T) {
 	}
 
 	expectedWorkbookDataStrings[0][0] = append(expectedWorkbookDataStrings[0][0], workbookData[0][0][0].cellData)
-	year, month, day := time.Now().Date()
+	year, month, day := fixedTime.Date()
 	monthString := strconv.Itoa(int(month))
 	if int(month) < 10 {
 		monthString = "0" + monthString
@@ -482,11 +484,21 @@ func TestStreamStyleDates(t *testing.T) {
 	if day < 10 {
 		dayString = "0" + dayString
 	}
+	yearString := strconv.Itoa(year - 2000)
+	if (year - 2000) < 10 {
+		yearString = "0" + yearString
+	}
 	expectedWorkbookDataStrings[0][1] = append(expectedWorkbookDataStrings[0][1],
-		monthString+"-"+dayString+"-"+strconv.Itoa(year-2000))
+		monthString+"-"+dayString+"-"+yearString)
 
 	if !reflect.DeepEqual(actualWorkbookData, expectedWorkbookDataStrings) {
-		t.Fatal("Expected workbook data to be equal")
+		t.Fatalf(`Expected workbook data to be equal:
+    Expected:
+%s
+
+    Actual:
+%s
+`, expectedWorkbookDataStrings, actualWorkbookData)
 	}
 
 	if err := checkForCorrectCellStyles(actualWorkbookCells, workbookData); err != nil {

+ 62 - 52
stream_test.go

@@ -235,9 +235,9 @@ func TestXlsxStreamWrite(t *testing.T) {
 		},
 	}
 	for i, testCase := range testCases {
-		if testCase.testName != "One Sheet" {
-			continue
-		}
+		// if testCase.testName != "One Sheet" {
+		// 	continue
+		// }
 		t.Run(testCase.testName, func(t *testing.T) {
 			var filePath string
 			var buffer bytes.Buffer
@@ -286,7 +286,7 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 		sheetNames           []string
 		workbookData         [][][]string
 		expectedWorkbookData [][][]string
-		headerTypes          [][]*CellMetadata
+		headerTypes          [][]*StreamingCellMetadata
 		expectedError        error
 	}{
 		{
@@ -308,8 +308,8 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 					{"123", "Taco", "string", "0000000123"},
 				},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultDecimalCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultDecimalStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr()},
 			},
 		},
 		{
@@ -329,8 +329,8 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 					{"1234.00"},
 				},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultDecimalCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultDecimalStreamingCellMetadata.Ptr()},
 			},
 		},
 		{
@@ -370,9 +370,9 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 					{"2357", "Margarita", "700"},
 				},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultIntegerCellMetadata.Ptr(), nil, DefaultDecimalCellMetadata.Ptr(), nil},
-				{DefaultIntegerCellMetadata.Ptr(), nil, DefaultDecimalCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultIntegerCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultIntegerStreamingCellMetadata.Ptr(), nil, DefaultDecimalStreamingCellMetadata.Ptr(), nil},
+				{DefaultIntegerStreamingCellMetadata.Ptr(), nil, DefaultDecimalStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultIntegerStreamingCellMetadata.Ptr()},
 				{nil, nil, nil},
 			},
 		},
@@ -453,14 +453,25 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 				{{}},
 				{{}},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultIntegerStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultIntegerStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr()},
 				{nil},
 				{nil, nil},
 				{nil},
 				{nil},
 				{nil},
 			},
+			expectedWorkbookData: [][][]string{
+				{
+					{"Token", "Name", "Price", "SKU"},
+					{"123", "Taco", "300", "0000000123"},
+				},
+				{{}},
+				{{"Id", "Unit Cost"}},
+				{{}},
+				{{}},
+				{{}},
+			},
 		},
 		{
 			testName: "Two Sheets, only writes to one, should not error and should still create a valid file",
@@ -474,8 +485,16 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 				},
 				{{}},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultDateCellMetadata.Ptr(), DefaultDateCellMetadata.Ptr(), DefaultDateCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
+			expectedWorkbookData: [][][]string{
+				{
+					{"Token", "Name", "Price", "SKU"},
+					{"123", "Taco", "300", "0000000123"},
+				},
+				{{}},
+			},
+
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultDateStreamingCellMetadata.Ptr(), DefaultDateStreamingCellMetadata.Ptr(), DefaultDateStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr()},
 				{nil},
 			},
 		},
@@ -513,8 +532,8 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 					{"789", "Burritos", "400", "754", "789", "Burritos", "400", "754", "789", "Burritos", "400", "754", "789", "Burritos", "400", "754", "789", "Burritos", "400", "754", "789", "Burritos", "400", "754"},
 				},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultIntegerStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultIntegerStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr()},
 			},
 		},
 		{
@@ -537,15 +556,15 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 					{"123", "Taco", "300", "0000000123"},
 				},
 			},
-			headerTypes: [][]*CellMetadata{
-				{DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
+			headerTypes: [][]*StreamingCellMetadata{
+				{DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr(), DefaultStringStreamingCellMetadata.Ptr()},
 			},
 		},
 	}
 	for i, testCase := range testCases {
-		if testCase.testName != "One Sheet, too few columns in row 1" {
-			continue
-		}
+		// if testCase.testName != "Lots of Sheets, only writes rows to one, only writes headers to one, should not error and should still create a valid file" {
+		// 	continue
+		// }
 		t.Run(testCase.testName, func(t *testing.T) {
 
 			var filePath string
@@ -579,6 +598,9 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 			if !reflect.DeepEqual(actualSheetNames, testCase.sheetNames) {
 				t.Fatal("Expected sheet names to be equal")
 			}
+			if testCase.expectedWorkbookData == nil {
+				testCase.expectedWorkbookData = testCase.workbookData
+			}
 			if !reflect.DeepEqual(actualWorkbookData, testCase.expectedWorkbookData) {
 				t.Log("expected: \n")
 				t.Logf("%s\n", testCase.expectedWorkbookData)
@@ -594,7 +616,7 @@ func TestXlsxStreamWriteWithDefaultCellType(t *testing.T) {
 
 // Ensures that the cell type of all cells in each column across all sheets matches the provided header types
 // in each corresponding sheet
-func verifyCellTypesInColumnMatchHeaderType(t *testing.T, workbookCellTypes [][][]CellType, headerMetadata [][]*CellMetadata, workbookData [][][]string) {
+func verifyCellTypesInColumnMatchHeaderType(t *testing.T, workbookCellTypes [][][]CellType, headerMetadata [][]*StreamingCellMetadata, workbookData [][][]string) {
 
 	numSheets := len(workbookCellTypes)
 	numHeaders := len(headerMetadata)
@@ -605,7 +627,7 @@ func verifyCellTypesInColumnMatchHeaderType(t *testing.T, workbookCellTypes [][]
 	for sheetI, headers := range headerMetadata {
 		var sanitizedHeaders []CellType
 		for _, header := range headers {
-			if header == (*CellMetadata)(nil) || header.cellType == CellTypeString {
+			if header == (*StreamingCellMetadata)(nil) || header.cellType == CellTypeString {
 				sanitizedHeaders = append(sanitizedHeaders, CellTypeInline)
 			} else {
 				sanitizedHeaders = append(sanitizedHeaders, header.cellType)
@@ -694,15 +716,12 @@ func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string,
 	} else {
 		file = NewStreamFileBuilder(fileBuffer)
 	}
-	var headerLen []int
 	for i, sheetName := range sheetNames {
-		header := workbookData[i][0]
-		headerLen = append(headerLen, len(header))
 		var sheetHeaderTypes []*CellType
 		if i < len(headerTypes) {
 			sheetHeaderTypes = headerTypes[i]
 		}
-		err := file.AddSheet(sheetName, header, sheetHeaderTypes)
+		err := file.AddSheet(sheetName, sheetHeaderTypes)
 		if err != nil {
 			return err
 		}
@@ -718,11 +737,7 @@ func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string,
 				return err
 			}
 		}
-		streamFile.currentSheet.columnCount = headerLen[i]
-		for i, row := range sheetData {
-			if i == 0 {
-				continue
-			}
+		for _, row := range sheetData {
 			err = streamFile.Write(row)
 			if err != nil {
 				return err
@@ -737,7 +752,7 @@ func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string,
 }
 
 // writeStreamFileWithDefaultMetadata is the same thing as writeStreamFile but with headerMetadata instead of headerTypes
-func writeStreamFileWithDefaultMetadata(filePath string, fileBuffer io.Writer, sheetNames []string, workbookData [][][]string, headerMetadata [][]*CellMetadata, shouldMakeRealFiles bool) error {
+func writeStreamFileWithDefaultMetadata(filePath string, fileBuffer io.Writer, sheetNames []string, workbookData [][][]string, headerMetadata [][]*StreamingCellMetadata, shouldMakeRealFiles bool) error {
 	var file *StreamFileBuilder
 	var err error
 	if shouldMakeRealFiles {
@@ -748,15 +763,13 @@ func writeStreamFileWithDefaultMetadata(filePath string, fileBuffer io.Writer, s
 	} else {
 		file = NewStreamFileBuilder(fileBuffer)
 	}
-	var headerLen []int
+
 	for i, sheetName := range sheetNames {
-		header := workbookData[i][0]
-		headerLen = append(headerLen, len(header))
-		var sheetHeaderTypes []*CellMetadata
+		var sheetHeaderTypes []*StreamingCellMetadata
 		if i < len(headerMetadata) {
 			sheetHeaderTypes = headerMetadata[i]
 		}
-		err := file.AddSheetWithDefaultColumnMetadata(sheetName, header, sheetHeaderTypes)
+		err := file.AddSheetWithDefaultColumnMetadata(sheetName, sheetHeaderTypes)
 		if err != nil {
 			return err
 		}
@@ -772,12 +785,9 @@ func writeStreamFileWithDefaultMetadata(filePath string, fileBuffer io.Writer, s
 				return err
 			}
 		}
-		cellCount := headerLen[i]
-		for i, row := range sheetData {
-			if i == 0 {
-				continue
-			}
-			err = streamFile.WriteWithColumnDefaultMetadata(row, cellCount)
+
+		for _, row := range sheetData {
+			err = streamFile.WriteWithColumnDefaultMetadata(row)
 			if err != nil {
 				return err
 			}
@@ -835,11 +845,11 @@ func readXLSXFile(t *testing.T, filePath string, fileBuffer io.ReaderAt, size in
 func (s *StreamSuite) TestAddSheetErrorsAfterBuild(t *C) {
 	file := NewStreamFileBuilder(bytes.NewBuffer(nil))
 
-	err := file.AddSheet("Sheet1", []string{"Header"}, nil)
+	err := file.AddSheet("Sheet1", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = file.AddSheet("Sheet2", []string{"Header2"}, nil)
+	err = file.AddSheet("Sheet2", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -848,7 +858,7 @@ func (s *StreamSuite) TestAddSheetErrorsAfterBuild(t *C) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = file.AddSheet("Sheet3", []string{"Header3"}, nil)
+	err = file.AddSheet("Sheet3", nil)
 	if err != BuiltStreamFileBuilderError {
 		t.Fatal(err)
 	}
@@ -857,11 +867,11 @@ func (s *StreamSuite) TestAddSheetErrorsAfterBuild(t *C) {
 func (s *StreamSuite) TestBuildErrorsAfterBuild(t *C) {
 	file := NewStreamFileBuilder(bytes.NewBuffer(nil))
 
-	err := file.AddSheet("Sheet1", []string{"Header"}, nil)
+	err := file.AddSheet("Sheet1", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = file.AddSheet("Sheet2", []string{"Header2"}, nil)
+	err = file.AddSheet("Sheet2", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -885,11 +895,11 @@ func TestCloseWithNothingWrittenToSheets(t *testing.T) {
 		{{"Header1", "Header2"}},
 		{{"Header3", "Header4"}},
 	}
-	err := file.AddSheet(sheetNames[0], workbookData[0][0], nil)
+	err := file.AddSheet(sheetNames[0], nil)
 	if err != nil {
 		t.Fatal(err)
 	}
-	err = file.AddSheet(sheetNames[1], workbookData[1][0], nil)
+	err = file.AddSheet(sheetNames[1], nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 0 - 5
xmlWorksheet.go

@@ -274,11 +274,6 @@ type xlsxDataValidation struct {
 	// 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