Browse Source

'WriteWithDefaultCellType' to 'WriteWithColumnDefaultMetadata' now encoding cell style

Alex Milkov 6 years ago
parent
commit
905bf9db92
6 changed files with 265 additions and 77 deletions
  1. 25 0
      cell.go
  2. 25 11
      col.go
  3. 27 11
      stream_file.go
  4. 66 11
      stream_file_builder.go
  5. 6 0
      stream_style.go
  6. 116 44
      stream_test.go

+ 25 - 0
cell.go

@@ -391,3 +391,28 @@ func (c *Cell) FormattedValue() (string, error) {
 func (c *Cell) SetDataValidation(dd *xlsxCellDataValidation) {
 	c.DataValidation = dd
 }
+
+// CellMetadata 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 {
+	cellType    CellType
+	streamStyle StreamStyle
+}
+
+var (
+	DefaultStringCellMetadata  CellMetadata
+	DefaultNumericCellMetadata CellMetadata
+	DefaultDecimalCellMetadata CellMetadata
+	DefaultIntegerCellMetadata CellMetadata
+	DefaultDateCellMetadata    CellMetadata
+)
+
+func MakeCellMetadata(cellType CellType, streamStyle StreamStyle) CellMetadata {
+	return CellMetadata{cellType, streamStyle}
+}
+
+func (cm CellMetadata) Ptr() *CellMetadata {
+	return &cm
+}

+ 25 - 11
col.go

@@ -6,23 +6,22 @@ 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
-	DataValidation []*xlsxCellDataValidation
+	Min             int
+	Max             int
+	Hidden          bool
+	Width           float64
+	Collapsed       bool
+	OutlineLevel    uint8
+	numFmt          string
+	parsedNumFmt    *parsedNumberFormat
+	style           *Style
+	DataValidation  []*xlsxCellDataValidation
 	defaultCellType *CellType
 }
 
 // SetType will set the format string of a column based on the type that you want to set it to.
 // This function does not really make a lot of sense.
 func (c *Col) SetType(cellType CellType) {
-	c.defaultCellType = &cellType
 	switch cellType {
 	case CellTypeString:
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
@@ -43,6 +42,13 @@ 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
@@ -103,5 +109,13 @@ func (c *Col) SetDataValidationWithStart(dd *xlsxCellDataValidation, start int)
 // 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
+	// TODO: `style.xNumFmtId` could be out of the range of the builtin map
+	// returning "" which may not be a valid formatCode
 	c.numFmt = builtInNumFmt[style.xNumFmtId]
 }
+
+func (c *Col) GetStreamStyle() StreamStyle {
+	// TODO: Like `SetStreamStyle`, `numFmt` could be out of the range of the builtin inv map
+	// returning 0 which maps to formatCode "general"
+	return StreamStyle{builtInNumFmtInv[c.numFmt], c.style}
+}

+ 27 - 11
stream_file.go

@@ -53,17 +53,17 @@ func (sf *StreamFile) Write(cells []string) error {
 	return sf.zipWriter.Flush()
 }
 
-// WriteWithDefaultCellType will write a row of cells to the current sheet. Every call to WriteWithDefaultCellType
+// 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 have the
-// default cell type of the column that it belongs to. However, if the cell data string cannot be
-// parsed into said cell type, we fall back on encoding the cell as a string
-// In addition, note that cells WILL NOT be styled
-func (sf *StreamFile) WriteWithDefaultCellType(cells []string) error {
+// 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
+// string style
+func (sf *StreamFile) WriteWithColumnDefaultMetadata(cells []string) error {
 	if sf.err != nil {
 		return sf.err
 	}
-	err := sf.writeWithColumnDefaultCellType(cells)
+	err := sf.writeWithColumnDefaultMetadata(cells)
 	if err != nil {
 		sf.err = err
 		return err
@@ -165,7 +165,7 @@ func (sf *StreamFile) write(cells []string) error {
 	return sf.zipWriter.Flush()
 }
 
-func (sf *StreamFile) writeWithColumnDefaultCellType(cells []string) error {
+func (sf *StreamFile) writeWithColumnDefaultMetadata(cells []string) error {
 
 	if sf.currentSheet == nil {
 		return NoCurrentSheetError
@@ -178,12 +178,29 @@ func (sf *StreamFile) writeWithColumnDefaultCellType(cells []string) error {
 
 	var streamCells []StreamCell
 	for colIndex, col := range currentSheet.Cols {
+
+		// 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
+		style := StreamStyleDefaultString
+
+		// Because `cellData` could be anything we need to attempt to
+		// parse into the default cell type and if parsing fails fall back
+		// to some sensible default
+		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[colIndex], CellTypeString)
+		if defaultType != nil && *defaultType == cellType {
+			style = col.GetStreamStyle()
+		}
+
 		streamCells = append(
 			streamCells,
 			NewStreamCell(
 				cells[colIndex],
-				StreamStyle{},
-				col.defaultCellType.fallbackTo(cells[colIndex], CellTypeString),
+				style,
+				cellType,
 			))
 	}
 	return sf.writeS(streamCells)
@@ -215,7 +232,6 @@ func (sf *StreamFile) writeS(cells []StreamCell) error {
 		if err != nil {
 			return nil
 		}
-
 		// Write the cell
 		if _, err := sf.currentSheet.writer.Write(marshaledCell); err != nil {
 			return err

+ 66 - 11
stream_file_builder.go

@@ -41,16 +41,17 @@ import (
 )
 
 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
+	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
 }
 
 const (
@@ -133,6 +134,55 @@ func (sb *StreamFileBuilder) AddSheet(name string, headers []string, cellTypes [
 	return nil
 }
 
+func (sb *StreamFileBuilder) AddSheetWithDefaultColumnMetadata(name string, headers []string, columnsDefaultCellMetadata []*CellMetadata) 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)
+	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")
+	}
+	for i, cellMetadata := range columnsDefaultCellMetadata {
+		var cellStyleIndex int
+		var ok bool
+		if cellMetadata != 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]
+			if !ok {
+				sb.maxStyleId++
+				cellStyleIndex = sb.maxStyleId
+				sb.cellTypeToStyleIds[cellMetadata.cellType] = sb.maxStyleId
+			}
+
+			// Add streamStyle and set default cell metadata on col
+			sb.customStreamStyles[cellMetadata.streamStyle] = struct{}{}
+			sheet.Cols[i].SetCellMetadata(*cellMetadata)
+		}
+		sb.styleIds[len(sb.styleIds)-1] = append(sb.styleIds[len(sb.styleIds)-1], cellStyleIndex)
+	}
+	// Add fall back streamStyle
+	sb.customStreamStyles[StreamStyleDefaultString] = struct{}{}
+	// Toggle to true to ensure `styleIdMap` is constructed from `customStreamStyles` on `Build`
+	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
+	return nil
+}
+
 // AddSheetS will add a sheet with the given name and column styles. The number of column styles given
 // is the number of columns that will be created, and thus the number of cells each row has to have.
 // columnStyles[0] becomes the style of the first column, columnStyles[1] the style of the second column etc.
@@ -212,7 +262,12 @@ func (sb *StreamFileBuilder) Build() (*StreamFile, error) {
 		// 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) {
-			if err := sb.processEmptySheetXML(es, path, data, !sb.customStylesAdded); err != nil {
+			// sb.defaultColumnCellMetadataAdded 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 {
 				return nil, err
 			}
 			continue

+ 6 - 0
stream_style.go

@@ -74,6 +74,12 @@ func init() {
 	StreamStyleDefaultDate = MakeDateStyle(DefaultFont(), DefaultFill(), DefaultAlignment(), DefaultBorder())
 
 	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}
 }
 
 // MakeStyle creates a new StreamStyle and add it to the styles that will be streamed.

+ 116 - 44
stream_test.go

@@ -239,7 +239,7 @@ func (s *StreamSuite) TestXlsxStreamWrite(t *C) {
 		if TestsShouldMakeRealFiles {
 			filePath = fmt.Sprintf("Workbook%d.xlsx", i)
 		}
-		err := writeStreamFile(filePath, &buffer, testCase.sheetNames, testCase.workbookData, testCase.headerTypes, TestsShouldMakeRealFiles, false)
+		err := writeStreamFile(filePath, &buffer, testCase.sheetNames, testCase.workbookData, testCase.headerTypes, TestsShouldMakeRealFiles)
 		if err != testCase.expectedError && err.Error() != testCase.expectedError.Error() {
 			t.Fatalf("Error differs from expected error. Error: %v, Expected Error: %v ", err, testCase.expectedError)
 		}
@@ -270,11 +270,12 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 	// In case of issues you can use "Open XML SDK 2.5" to diagnose issues in generated XLSX files:
 	// https://www.microsoft.com/en-us/download/details.aspx?id=30425
 	testCases := []struct {
-		testName      string
-		sheetNames    []string
-		workbookData  [][][]string
-		headerTypes   [][]*CellType
-		expectedError error
+		testName             string
+		sheetNames           []string
+		workbookData         [][][]string
+		expectedWorkbookData [][][]string
+		headerTypes          [][]*CellMetadata
+		expectedError        error
 	}{
 		{
 			testName: "One Sheet",
@@ -284,11 +285,19 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 			workbookData: [][][]string{
 				{
 					{"Token", "Name", "Price", "SKU"},
-					{"123", "Taco", "300", "0000000123"},
+					{"123", "Taco", "300.0", "0000000123"},
+					{"123", "Taco", "string", "0000000123"},
 				},
 			},
-			headerTypes: [][]*CellType{
-				{nil, CellTypeString.Ptr(), CellTypeNumeric.Ptr(), CellTypeString.Ptr()},
+			expectedWorkbookData: [][][]string{
+				{
+					{"Token", "Name", "Price", "SKU"},
+					{"123", "Taco", "300.00", "0000000123"},
+					{"123", "Taco", "string", "0000000123"},
+				},
+			},
+			headerTypes: [][]*CellMetadata{
+				{DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultDecimalCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
 			},
 		},
 		{
@@ -299,11 +308,17 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 			workbookData: [][][]string{
 				{
 					{"Token"},
-					{"123"},
+					{"1234"},
 				},
 			},
-			headerTypes: [][]*CellType{
-				{CellTypeNumeric.Ptr()},
+			expectedWorkbookData: [][][]string{
+				{
+					{"Token"},
+					{"1234.00"},
+				},
+			},
+			headerTypes: [][]*CellMetadata{
+				{DefaultDecimalCellMetadata.Ptr()},
 			},
 		},
 		{
@@ -314,7 +329,7 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 			workbookData: [][][]string{
 				{
 					{"Token", "Name", "Price", "SKU"},
-					{"123", "Taco", "variable", "0000000123"},
+					{"123", "Taco", "300", "0000000123"},
 				},
 				{
 					{"Token", "Name", "Price", "SKU", "Stock"},
@@ -327,9 +342,25 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 					{"2357", "Margarita", "700"},
 				},
 			},
-			headerTypes: [][]*CellType{
-				{CellTypeNumeric.Ptr(), CellTypeString.Ptr(), CellTypeNumeric.Ptr(), CellTypeString.Ptr()},
-				{nil, CellTypeString.Ptr(), nil, CellTypeString.Ptr(), nil},
+			expectedWorkbookData: [][][]string{
+				{
+					{"Token", "Name", "Price", "SKU"},
+					{"123", "Taco", "300.00", "0000000123"},
+				},
+				{
+					{"Token", "Name", "Price", "SKU", "Stock"},
+					{"456", "Salsa", "200.00", "0346", "1"},
+					{"789", "Burritos", "400.00", "754", "3"},
+				},
+				{
+					{"Token", "Name", "Price"},
+					{"9853", "Guacamole", "500"},
+					{"2357", "Margarita", "700"},
+				},
+			},
+			headerTypes: [][]*CellMetadata{
+				{DefaultIntegerCellMetadata.Ptr(), nil, DefaultDecimalCellMetadata.Ptr(), nil},
+				{DefaultIntegerCellMetadata.Ptr(), nil, DefaultDecimalCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultIntegerCellMetadata.Ptr()},
 				{nil, nil, nil},
 			},
 		},
@@ -410,8 +441,8 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 				{{}},
 				{{}},
 			},
-			headerTypes: [][]*CellType{
-				{CellTypeNumeric.Ptr(), CellTypeString.Ptr(), CellTypeNumeric.Ptr(), CellTypeString.Ptr()},
+			headerTypes: [][]*CellMetadata{
+				{DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultIntegerCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
 				{nil},
 				{nil, nil},
 				{nil},
@@ -431,8 +462,8 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 				},
 				{{}},
 			},
-			headerTypes: [][]*CellType{
-				{CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeString.Ptr()},
+			headerTypes: [][]*CellMetadata{
+				{DefaultDateCellMetadata.Ptr(), DefaultDateCellMetadata.Ptr(), DefaultDateCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
 				{nil},
 			},
 		},
@@ -470,8 +501,8 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 					{"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: [][]*CellType{
-				{CellTypeNumeric.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeString.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr()},
+			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()},
 			},
 		},
 		{
@@ -490,12 +521,12 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 					{"123", `<?xml version="1.0" encoding="ISO-8859-1"?>`, "300", `<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>`},
 					// Upside down text and Right to Left Arabic text
 					{"123", `˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥
-					00˙Ɩ$-`, "300", `ﷺ`},
+						00˙Ɩ$-`, "300", `ﷺ`},
 					{"123", "Taco", "300", "0000000123"},
 				},
 			},
-			headerTypes: [][]*CellType{
-				{CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeDate.Ptr(), CellTypeString.Ptr(), CellTypeString.Ptr()},
+			headerTypes: [][]*CellMetadata{
+				{DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr(), DefaultStringCellMetadata.Ptr()},
 			},
 		},
 	}
@@ -506,7 +537,7 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 		if TestsShouldMakeRealFiles {
 			filePath = fmt.Sprintf("WorkbookTyped%d.xlsx", i)
 		}
-		err := writeStreamFile(filePath, &buffer, testCase.sheetNames, testCase.workbookData, testCase.headerTypes, TestsShouldMakeRealFiles, true)
+		err := writeStreamFileWithDefaultMetadata(filePath, &buffer, testCase.sheetNames, testCase.workbookData, testCase.headerTypes, TestsShouldMakeRealFiles)
 		if err != testCase.expectedError && err.Error() != testCase.expectedError.Error() {
 			t.Fatalf("Error differs from expected error. Error: %v, Expected Error: %v ", err, testCase.expectedError)
 		}
@@ -526,35 +557,29 @@ func (s *StreamSuite) TestXlsxStreamWriteWithDefaultCellType(t *C) {
 		if !reflect.DeepEqual(actualSheetNames, testCase.sheetNames) {
 			t.Fatal("Expected sheet names to be equal")
 		}
-		if !reflect.DeepEqual(actualWorkbookData, testCase.workbookData) {
+		if !reflect.DeepEqual(actualWorkbookData, testCase.expectedWorkbookData) {
 			t.Fatal("Expected workbook data to be equal")
 		}
-		/*if len(testCase.headerTypes) != 0 {
-			testCaseLogic(true)
-		}*/
 	}
 }
 
 // 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 *C, workbookCellTypes [][][]CellType, headerTypes [][]*CellType, workbookData [][][]string) {
+func verifyCellTypesInColumnMatchHeaderType(t *C, workbookCellTypes [][][]CellType, headerMetadata [][]*CellMetadata, workbookData [][][]string) {
 
 	numSheets := len(workbookCellTypes)
-	numHeaders := len(headerTypes)
+	numHeaders := len(headerMetadata)
 	if numSheets != numHeaders {
 		t.Fatalf("Number of sheets in workbook: %d not equal to number of sheet headers: %d", numSheets, numHeaders)
 	}
 
-	for sheetI, headers := range headerTypes {
+	for sheetI, headers := range headerMetadata {
 		var sanitizedHeaders []CellType
-		for _, headerType := range headers {
-			// if `Col.defaultCellType` is `nil` in `StreamFile.writeWithColumnDefaultCellType` we give it a
-			// `CellTypeString` by default but then later `StreamFile.makeXlsxCell` actually encodes `CellTypeString` as
-			// `CellTypeInline` before marshalling
-			if headerType == (*CellType)(nil) || *headerType == CellTypeString {
+		for _, header := range headers {
+			if header == (*CellMetadata)(nil) || header.cellType == CellTypeString {
 				sanitizedHeaders = append(sanitizedHeaders, CellTypeInline)
 			} else {
-				sanitizedHeaders = append(sanitizedHeaders, *headerType)
+				sanitizedHeaders = append(sanitizedHeaders, header.cellType)
 			}
 		}
 
@@ -629,7 +654,7 @@ func (s *StreamSuite) TestXlsxStyleBehavior(t *C) {
 }
 
 // writeStreamFile will write the file using this stream package
-func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string, workbookData [][][]string, headerTypes [][]*CellType, shouldMakeRealFiles bool, useHeaderTypeAsCellType bool) error {
+func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string, workbookData [][][]string, headerTypes [][]*CellType, shouldMakeRealFiles bool) error {
 	var file *StreamFileBuilder
 	var err error
 	if shouldMakeRealFiles {
@@ -666,11 +691,58 @@ func writeStreamFile(filePath string, fileBuffer io.Writer, sheetNames []string,
 			if i == 0 {
 				continue
 			}
-			if useHeaderTypeAsCellType {
-				err = streamFile.WriteWithDefaultCellType(row)
-			} else {
-				err = streamFile.Write(row)
+			err = streamFile.Write(row)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	err = streamFile.Close()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 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 {
+	var file *StreamFileBuilder
+	var err error
+	if shouldMakeRealFiles {
+		file, err = NewStreamFileBuilderForPath(filePath)
+		if err != nil {
+			return err
+		}
+	} else {
+		file = NewStreamFileBuilder(fileBuffer)
+	}
+	for i, sheetName := range sheetNames {
+		header := workbookData[i][0]
+		var sheetHeaderTypes []*CellMetadata
+		if i < len(headerMetadata) {
+			sheetHeaderTypes = headerMetadata[i]
+		}
+		err := file.AddSheetWithDefaultColumnMetadata(sheetName, header, sheetHeaderTypes)
+		if err != nil {
+			return err
+		}
+	}
+	streamFile, err := file.Build()
+	if err != nil {
+		return err
+	}
+	for i, sheetData := range workbookData {
+		if i != 0 {
+			err = streamFile.NextSheet()
+			if err != nil {
+				return err
+			}
+		}
+		for i, row := range sheetData {
+			if i == 0 {
+				continue
 			}
+			err = streamFile.WriteWithColumnDefaultMetadata(row)
 			if err != nil {
 				return err
 			}