Browse Source

Merge pull request #1 from tealeg/master

Adding setColWidth Function
Artem Chernyak 11 years ago
parent
commit
6c7150dea6
25 changed files with 2049 additions and 439 deletions
  1. 2 2
      README.org
  2. 126 5
      cell.go
  3. 30 5
      cell_test.go
  4. 9 4
      col.go
  5. 50 21
      file.go
  6. 83 138
      file_test.go
  7. 90 41
      lib.go
  8. 222 9
      lib_test.go
  9. 2 6
      reftable_test.go
  10. 3 3
      row.go
  11. 52 16
      sheet.go
  12. 17 54
      sheet_test.go
  13. 20 1
      style.go
  14. 10 0
      style_test.go
  15. BIN
      testdocs/empty_rows.xlsx
  16. BIN
      testdocs/testcelltypes.xlsx
  17. 102 0
      write.go
  18. 86 0
      write_test.go
  19. 3 1
      xmlContentTypes.go
  20. 6 5
      xmlContentTypes_test.go
  21. 587 93
      xmlStyle.go
  22. 285 0
      xmlStyle_test.go
  23. 25 7
      xmlWorkbook.go
  24. 10 16
      xmlWorkbook_test.go
  25. 229 12
      xmlWorksheet.go

+ 2 - 2
README.org

@@ -27,8 +27,8 @@ import (
 
 func main() {
     excelFileName := "/home/tealeg/foo.xlsx"
-    xlFile, error := xlsx.OpenFile(excelFileName)
-    if error != nil {
+    xlFile, err := xlsx.OpenFile(excelFileName)
+    if err != nil {
         ...
     }
     for _, sheet := range xlFile.Sheets {

+ 126 - 5
cell.go

@@ -6,14 +6,28 @@ import (
 	"strconv"
 )
 
+type CellType int
+
+const (
+	CellTypeString CellType = iota
+	CellTypeFormula
+	CellTypeNumeric
+	CellTypeBool
+	CellTypeInline
+	CellTypeError
+)
+
 // 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
-	style    Style
+	formula  string
+	style    *Style
 	numFmt   string
 	date1904 bool
 	Hidden   bool
+	cellType CellType
 }
 
 // CellInterface defines the public API of the Cell.
@@ -22,8 +36,19 @@ type CellInterface interface {
 	FormattedValue() string
 }
 
-func NewCell() *Cell {
-	return &Cell{style: *NewStyle()}
+func NewCell(r *Row) *Cell {
+	return &Cell{style: NewStyle(), Row: r}
+}
+
+func (c *Cell) Type() CellType {
+	return c.cellType
+}
+
+// Set string
+func (c *Cell) SetString(s string) {
+	c.Value = s
+	c.formula = ""
+	c.cellType = CellTypeString
 }
 
 // String returns the value of a Cell as a string.
@@ -31,13 +56,109 @@ func (c *Cell) String() string {
 	return c.FormattedValue()
 }
 
+// Set float
+func (c *Cell) SetFloat(n float64) {
+	c.SetFloatWithFormat(n, "0.00e+00")
+}
+
+/*
+	Set float with format. The followings are samples of format samples.
+
+	* "0.00e+00"
+	* "0", "#,##0"
+	* "0.00", "#,##0.00", "@"
+	* "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)"
+	* "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)"
+	* "0%", "0.00%"
+	* "0.00e+00", "##0.0e+0"
+*/
+func (c *Cell) SetFloatWithFormat(n float64, format string) {
+	// tmp value. final value is formatted by FormattedValue() method
+	c.Value = fmt.Sprintf("%e", n)
+	c.numFmt = format
+	c.Value = c.FormattedValue()
+	c.formula = ""
+	c.cellType = CellTypeNumeric
+}
+
+// Returns the value of cell as a number
+func (c *Cell) Float() (float64, error) {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return math.NaN(), err
+	}
+	return f, nil
+}
+
+// Set a 64-bit integer
+func (c *Cell) SetInt64(n int64) {
+	c.Value = fmt.Sprintf("%d", n)
+	c.numFmt = "0"
+	c.formula = ""
+	c.cellType = CellTypeNumeric
+}
+
+// Returns the value of cell as 64-bit integer
+func (c *Cell) Int64() (int64, error) {
+	f, err := strconv.ParseInt(c.Value, 10, 64)
+	if err != nil {
+		return -1, err
+	}
+	return f, nil
+}
+
+// Set integer
+func (c *Cell) SetInt(n int) {
+	c.Value = fmt.Sprintf("%d", n)
+	c.numFmt = "0"
+	c.formula = ""
+	c.cellType = CellTypeNumeric
+}
+
+// Returns the value of cell as integer
+// Has max 53 bits of precision
+// See: float64(int64(math.MaxInt))
+func (c *Cell) Int() (int, error) {
+	f, err := strconv.ParseFloat(c.Value, 64)
+	if err != nil {
+		return -1, err
+	}
+	return int(f), nil
+}
+
+// Set boolean
+func (c *Cell) SetBool(b bool) {
+	if b {
+		c.Value = "1"
+	} else {
+		c.Value = "0"
+	}
+	c.cellType = CellTypeBool
+}
+
+// Get boolean
+func (c *Cell) Bool() bool {
+	return c.Value == "1"
+}
+
+// Set formula
+func (c *Cell) SetFormula(formula string) {
+	c.formula = formula
+	c.cellType = CellTypeFormula
+}
+
+// Returns formula
+func (c *Cell) Formula() string {
+	return c.formula
+}
+
 // GetStyle returns the Style associated with a Cell
-func (c *Cell) GetStyle() Style {
+func (c *Cell) GetStyle() *Style {
 	return c.style
 }
 
 // SetStyle sets the style of a cell.
-func (c *Cell) SetStyle(style Style) {
+func (c *Cell) SetStyle(style *Style) {
 	c.style = style
 }
 

+ 30 - 5
cell_test.go

@@ -22,7 +22,7 @@ func (s *CellSuite) TestValueSet(c *C) {
 // Test that GetStyle correctly converts the xlsxStyle.Fonts.
 func (s *CellSuite) TestGetStyleWithFonts(c *C) {
 	font := NewFont(10, "Calibra")
-	style := *NewStyle()
+	style := NewStyle()
 	style.Font = *font
 
 	cell := &Cell{Value: "123", style: style}
@@ -39,7 +39,7 @@ func (s *CellSuite) TestSetStyleWithFonts(c *C) {
 	row := sheet.AddRow()
 	cell := row.AddCell()
 	font := NewFont(12, "Calibra")
-	style := *NewStyle()
+	style := NewStyle()
 	style.Font = *font
 	cell.SetStyle(style)
 	style = cell.GetStyle()
@@ -51,7 +51,7 @@ func (s *CellSuite) TestSetStyleWithFonts(c *C) {
 // Test that GetStyle correctly converts the xlsxStyle.Fills.
 func (s *CellSuite) TestGetStyleWithFills(c *C) {
 	fill := *NewFill("solid", "FF000000", "00FF0000")
-	style := *NewStyle()
+	style := NewStyle()
 	style.Fill = fill
 	cell := &Cell{Value: "123", style: style}
 	style = cell.GetStyle()
@@ -68,7 +68,7 @@ func (s *CellSuite) TestSetStyleWithFills(c *C) {
 	row := sheet.AddRow()
 	cell := row.AddCell()
 	fill := NewFill("solid", "00FF0000", "FF000000")
-	style := *NewStyle()
+	style := NewStyle()
 	style.Fill = *fill
 	cell.SetStyle(style)
 	style = cell.GetStyle()
@@ -82,7 +82,7 @@ func (s *CellSuite) TestSetStyleWithFills(c *C) {
 // Test that GetStyle correctly converts the xlsxStyle.Borders.
 func (s *CellSuite) TestGetStyleWithBorders(c *C) {
 	border := *NewBorder("thin", "thin", "thin", "thin")
-	style := *NewStyle()
+	style := NewStyle()
 	style.Border = border
 	cell := Cell{Value: "123", style: style}
 	style = cell.GetStyle()
@@ -255,3 +255,28 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	smallCell.numFmt = "yyyy-mm-dd hh:mm:ss"
 	c.Assert(smallCell.FormattedValue(), Equals, "1899-12-30 00:14:47")
 }
+
+// test setters and getters
+func (s *CellSuite) TestSetterGetters(c *C) {
+	cell := Cell{}
+
+	cell.SetString("hello world")
+	c.Assert(cell.String(), Equals, "hello world")
+	c.Assert(cell.Type(), Equals, CellTypeString)
+
+	cell.SetInt(1024)
+	intValue, _ := cell.Int()
+	c.Assert(intValue, Equals, 1024)
+	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+
+	cell.SetFloat(1.024)
+	float, _ := cell.Float()
+	intValue, _ = cell.Int() // convert
+	c.Assert(float, Equals, 1.024)
+	c.Assert(intValue, Equals, 1)
+	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+
+	cell.SetFormula("10+20")
+	c.Assert(cell.Formula(), Equals, "10+20")
+	c.Assert(cell.Type(), Equals, CellTypeFormula)
+}

+ 9 - 4
col.go

@@ -1,8 +1,13 @@
 package xlsx
 
+// Default column width in excel
+const ColWidth = 9.5
+
 type Col struct {
-	Min    int
-	Max    int
-	Hidden bool
-	Width  float64
+	Min       int
+	Max       int
+	Hidden    bool
+	Width     float64
+	Collapsed bool
+	//	Style     int
 }

+ 50 - 21
file.go

@@ -13,7 +13,6 @@ import (
 // to the user.
 type File struct {
 	worksheets     map[string]*zip.File
-	numFmtRefTable map[int]xlsxNumFmt
 	referenceTable *RefTable
 	Date1904       bool
 	styles         *xlsxStyleSheet
@@ -31,13 +30,14 @@ func NewFile() (file *File) {
 
 // OpenFile() take the name of an XLSX file and returns a populated
 // xlsx.File struct for it.
-func OpenFile(filename string) (*File, error) {
+func OpenFile(filename string) (file *File, err error) {
 	var f *zip.ReadCloser
-	f, err := zip.OpenReader(filename)
+	f, err = zip.OpenReader(filename)
 	if err != nil {
 		return nil, err
 	}
-	return ReadZip(f)
+	file, err = ReadZip(f)
+	return
 }
 
 // A convenient wrapper around File.ToSlice, FileToSlice will
@@ -64,21 +64,32 @@ func FileToSlice(path string) ([][][]string, error) {
 
 // Save the File to an xlsx file at the provided path.
 func (f *File) Save(path string) (err error) {
-	var parts map[string]string
 	var target *os.File
-	var zipWriter *zip.Writer
 
-	parts, err = f.MarshallParts()
+	target, err = os.Create(path)
 	if err != nil {
 		return
 	}
 
-	target, err = os.Create(path)
+	err = f.Write(target)
+	if err != nil {
+		return
+	}
+
+	return target.Close()
+}
+
+// Write the File to io.Writer as xlsx
+func (f *File) Write(writer io.Writer) (err error) {
+	var parts map[string]string
+	var zipWriter *zip.Writer
+
+	parts, err = f.MarshallParts()
 	if err != nil {
 		return
 	}
 
-	zipWriter = zip.NewWriter(target)
+	zipWriter = zip.NewWriter(writer)
 
 	for partName, part := range parts {
 		var writer io.Writer
@@ -91,17 +102,15 @@ func (f *File) Save(path string) (err error) {
 			return
 		}
 	}
+
 	err = zipWriter.Close()
-	if err != nil {
-		return
-	}
 
-	return target.Close()
+	return
 }
 
 // Add a new Sheet, with the provided name, to a File
 func (f *File) AddSheet(sheetName string) (sheet *Sheet) {
-	sheet = &Sheet{Name: sheetName}
+	sheet = &Sheet{Name: sheetName, File: f}
 	f.Sheet[sheetName] = sheet
 	f.Sheets = append(f.Sheets, sheet)
 	return sheet
@@ -112,12 +121,28 @@ func (f *File) makeWorkbook() xlsxWorkbook {
 	workbook = xlsxWorkbook{}
 	workbook.FileVersion = xlsxFileVersion{}
 	workbook.FileVersion.AppName = "Go XLSX"
-	workbook.WorkbookPr = xlsxWorkbookPr{BackupFile: false}
+	workbook.WorkbookPr = xlsxWorkbookPr{
+		BackupFile:  false,
+		ShowObjects: "all"}
 	workbook.BookViews = xlsxBookViews{}
 	workbook.BookViews.WorkBookView = make([]xlsxWorkBookView, 1)
-	workbook.BookViews.WorkBookView[0] = xlsxWorkBookView{}
+	workbook.BookViews.WorkBookView[0] = xlsxWorkBookView{
+		ActiveTab:            0,
+		FirstSheet:           0,
+		ShowHorizontalScroll: true,
+		ShowSheetTabs:        true,
+		ShowVerticalScroll:   true,
+		TabRatio:             204,
+		WindowHeight:         8192,
+		WindowWidth:          16384,
+		XWindow:              "0",
+		YWindow:              "0"}
 	workbook.Sheets = xlsxSheets{}
 	workbook.Sheets.Sheet = make([]xlsxSheet, len(f.Sheets))
+	workbook.CalcPr.IterateCount = 100
+	workbook.CalcPr.RefMode = "A1"
+	workbook.CalcPr.Iterate = false
+	workbook.CalcPr.IterateDelta = 0.001
 	return workbook
 }
 
@@ -133,7 +158,7 @@ func (f *File) MarshallParts() (map[string]string, error) {
 	var types xlsxTypes = MakeDefaultContentTypes()
 
 	marshal := func(thing interface{}) (string, error) {
-		body, err := xml.MarshalIndent(thing, "  ", "  ")
+		body, err := xml.Marshal(thing)
 		if err != nil {
 			return "", err
 		}
@@ -144,9 +169,12 @@ func (f *File) MarshallParts() (map[string]string, error) {
 	workbook = f.makeWorkbook()
 	sheetIndex := 1
 
-	styles := &xlsxStyleSheet{}
+	if f.styles == nil {
+		f.styles = newXlsxStyleSheet()
+	}
+	f.styles.reset()
 	for _, sheet := range f.Sheets {
-		xSheet := sheet.makeXLSXSheet(refTable, styles)
+		xSheet := sheet.makeXLSXSheet(refTable, f.styles)
 		rId := fmt.Sprintf("rId%d", sheetIndex)
 		sheetId := strconv.Itoa(sheetIndex)
 		sheetPath := fmt.Sprintf("worksheets/sheet%d.xml", sheetIndex)
@@ -160,7 +188,8 @@ func (f *File) MarshallParts() (map[string]string, error) {
 		workbook.Sheets.Sheet[sheetIndex-1] = xlsxSheet{
 			Name:    sheet.Name,
 			SheetId: sheetId,
-			Id:      rId}
+			Id:      rId,
+			State:   "visible"}
 		parts[partName], err = marshal(xSheet)
 		if err != nil {
 			return parts, err
@@ -197,7 +226,7 @@ func (f *File) MarshallParts() (map[string]string, error) {
 		return parts, err
 	}
 
-	parts["xl/styles.xml"], err = marshal(styles)
+	parts["xl/styles.xml"], err = f.styles.Marshal()
 	if err != nil {
 		return parts, err
 	}

+ 83 - 138
file_test.go

@@ -144,7 +144,7 @@ func (l *FileSuite) TestReadWorkbookRelationsFromZipFile(c *C) {
 func (l *FileSuite) TestGetStyleFromZipFile(c *C) {
 	var xlsxFile *File
 	var err error
-	var style Style
+	var style *Style
 
 	xlsxFile, err = OpenFile("./testdocs/testfile.xlsx")
 	c.Assert(err, IsNil)
@@ -240,28 +240,18 @@ func (l *FileSuite) TestMarshalWorkbook(c *C) {
 	workbook.Sheets.Sheet[0] = xlsxSheet{
 		Name:    "MyFirstSheet",
 		SheetId: "1",
-		Id:      "rId1"}
+		Id:      "rId1",
+		State:   "visible"}
 
 	workbook.Sheets.Sheet[1] = xlsxSheet{
 		Name:    "MySecondSheet",
 		SheetId: "2",
-		Id:      "rId2"}
+		Id:      "rId2",
+		State:   "visible"}
 
 	expectedWorkbook := `<?xml version="1.0" encoding="UTF-8"?>
-   <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-      <fileVersion appName="Go XLSX"></fileVersion>
-      <workbookPr date1904="false"></workbookPr>
-      <bookViews>
-         <workbookView></workbookView>
-      </bookViews>
-      <sheets>
-         <sheet name="MyFirstSheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1"></sheet>
-         <sheet name="MySecondSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2"></sheet>
-      </sheets>
-      <definedNames></definedNames>
-      <calcPr></calcPr>
-   </workbook>`
-	output, err := xml.MarshalIndent(workbook, "   ", "   ")
+<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fileVersion appName="Go XLSX"></fileVersion><workbookPr showObjects="all" date1904="false"></workbookPr><workbookProtection></workbookProtection><bookViews><workbookView showHorizontalScroll="true" showVerticalScroll="true" showSheetTabs="true" tabRatio="204" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"></workbookView></bookViews><sheets><sheet name="MyFirstSheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1" state="visible"></sheet><sheet name="MySecondSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2" state="visible"></sheet></sheets><definedNames></definedNames><calcPr iterateCount="100" refMode="A1" iterateDelta="0.001"></calcPr></workbook>`
+	output, err := xml.Marshal(workbook)
 	c.Assert(err, IsNil)
 	stringOutput := xml.Header + string(output)
 	c.Assert(stringOutput, Equals, expectedWorkbook)
@@ -274,46 +264,22 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	sheet1 := f.AddSheet("MySheet")
 	row1 := sheet1.AddRow()
 	cell1 := row1.AddCell()
-	cell1.Value = "A cell!"
+	cell1.SetString("A cell!")
 	sheet2 := f.AddSheet("AnotherSheet")
 	row2 := sheet2.AddRow()
 	cell2 := row2.AddCell()
-	cell2.Value = "A cell!"
+	cell2.SetString("A cell!")
 	parts, err := f.MarshallParts()
 	c.Assert(err, IsNil)
 	c.Assert(len(parts), Equals, 11)
 
 	// sheets
 	expectedSheet1 := `<?xml version="1.0" encoding="UTF-8"?>
-  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <dimension ref="A1:A1"></dimension>
-    <cols>
-      <col min="1" max="1" width="9.5"></col>
-    </cols>
-    <sheetData>
-      <row r="1">
-        <c r="A1" s="0" t="s">
-          <v>0</v>
-        </c>
-      </row>
-    </sheetData>
-  </worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="0" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
 	c.Assert(parts["xl/worksheets/sheet1.xml"], Equals, expectedSheet1)
 
 	expectedSheet2 := `<?xml version="1.0" encoding="UTF-8"?>
-  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <dimension ref="A1:A1"></dimension>
-    <cols>
-      <col min="1" max="1" width="9.5"></col>
-    </cols>
-    <sheetData>
-      <row r="1">
-        <c r="A1" s="1" t="s">
-          <v>0</v>
-        </c>
-      </row>
-    </sheetData>
-  </worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="0" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
 	c.Assert(parts["xl/worksheets/sheet2.xml"], Equals, expectedSheet2)
 
 	// .rels.xml
@@ -661,56 +627,22 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 
 	// sharedStrings.xml
 	expectedXLSXSST := `<?xml version="1.0" encoding="UTF-8"?>
-  <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1">
-    <si>
-      <t>A cell!</t>
-    </si>
-  </sst>`
+<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1"><si><t>A cell!</t></si></sst>`
 	c.Assert(parts["xl/sharedStrings.xml"], Equals, expectedXLSXSST)
 
 	// workbook.xml.rels
 	expectedXLSXWorkbookRels := `<?xml version="1.0" encoding="UTF-8"?>
-  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
-    <Relationship Id="rId1" Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship>
-    <Relationship Id="rId2" Target="worksheets/sheet2.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship>
-    <Relationship Id="rId3" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"></Relationship>
-    <Relationship Id="rId4" Target="theme/theme1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"></Relationship>
-    <Relationship Id="rId5" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"></Relationship>
-  </Relationships>`
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship><Relationship Id="rId2" Target="worksheets/sheet2.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship><Relationship Id="rId3" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"></Relationship><Relationship Id="rId4" Target="theme/theme1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"></Relationship><Relationship Id="rId5" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"></Relationship></Relationships>`
 	c.Assert(parts["xl/_rels/workbook.xml.rels"], Equals, expectedXLSXWorkbookRels)
 
 	// workbook.xml
 	expectedWorkbook := `<?xml version="1.0" encoding="UTF-8"?>
-  <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <fileVersion appName="Go XLSX"></fileVersion>
-    <workbookPr date1904="false"></workbookPr>
-    <bookViews>
-      <workbookView></workbookView>
-    </bookViews>
-    <sheets>
-      <sheet name="MySheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1"></sheet>
-      <sheet name="AnotherSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2"></sheet>
-    </sheets>
-    <definedNames></definedNames>
-    <calcPr></calcPr>
-  </workbook>`
+<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fileVersion appName="Go XLSX"></fileVersion><workbookPr showObjects="all" date1904="false"></workbookPr><workbookProtection></workbookProtection><bookViews><workbookView showHorizontalScroll="true" showVerticalScroll="true" showSheetTabs="true" tabRatio="204" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"></workbookView></bookViews><sheets><sheet name="MySheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1" state="visible"></sheet><sheet name="AnotherSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2" state="visible"></sheet></sheets><definedNames></definedNames><calcPr iterateCount="100" refMode="A1" iterateDelta="0.001"></calcPr></workbook>`
 	c.Assert(parts["xl/workbook.xml"], Equals, expectedWorkbook)
 
 	// [Content_Types].xml
 	expectedContentTypes := `<?xml version="1.0" encoding="UTF-8"?>
-  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
-    <Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override>
-    <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"></Override>
-    <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"></Override>
-    <Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override>
-    <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"></Override>
-    <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"></Override>
-    <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"></Override>
-    <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"></Override>
-    <Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"></Override>
-    <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Default>
-    <Default Extension="xml" ContentType="application/xml"></Default>
-  </Types>`
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"></Override><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"></Override><Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override><Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"></Override><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"></Override><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"></Override><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"></Override><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"></Override><Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"></Override><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Default><Default Extension="xml" ContentType="application/xml"></Default></Types>`
 	c.Assert(parts["[Content_Types].xml"], Equals, expectedContentTypes)
 
 	// styles.xml
@@ -718,61 +650,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="2">
-      <font>
-        <sz val="12"></sz>
-        <name val="Verdana"></name>
-        <family val="0"></family>
-        <charset val="0"></charset>
-        <color></color>
-      </font>
-      <font>
-        <sz val="12"></sz>
-        <name val="Verdana"></name>
-        <family val="0"></family>
-        <charset val="0"></charset>
-        <color></color>
-      </font>
-    </fonts>
-    <fills count="2">
-      <fill>
-        <patternFill>
-          <fgColor></fgColor>
-          <bgColor></bgColor>
-        </patternFill>
-      </fill>
-      <fill>
-        <patternFill>
-          <fgColor></fgColor>
-          <bgColor></bgColor>
-        </patternFill>
-      </fill>
-    </fills>
-    <borders count="2">
-      <border>
-        <left></left>
-        <right></right>
-        <top></top>
-        <bottom></bottom>
-      </border>
-      <border>
-        <left></left>
-        <right></right>
-        <top></top>
-        <bottom></bottom>
-      </border>
-    </borders>
-    <cellStyleXfs count="2">
-      <xf applyAlignment="false" applyBorder="false" applyFont="false" applyFill="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
-      <xf applyAlignment="false" applyBorder="false" applyFont="false" applyFill="false" applyProtection="false" borderId="1" fillId="1" fontId="1" numFmtId="0"></xf>
-    </cellStyleXfs>
-    <cellXfs count="2">
-      <xf applyAlignment="false" applyBorder="false" applyFont="false" applyFill="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"></xf>
-      <xf applyAlignment="false" applyBorder="false" applyFont="false" applyFill="false" applyProtection="false" borderId="1" fillId="1" fontId="1" numFmtId="0"></xf>
-    </cellXfs>
-    <numFmts count="0"></numFmts>
-  </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="1"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill></fills><borders count="1"><border><left style="none"/><right style="none"/><top style="none"/><bottom style="none"/></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 
@@ -837,3 +715,70 @@ func fileToSliceCheckOutput(c *C, output [][][]string) {
 	c.Assert(len(output[1]), Equals, 0)
 	c.Assert(len(output[2]), Equals, 0)
 }
+
+func (l *FileSuite) TestReadWorkbookWithTypes(c *C) {
+	var xlsxFile *File
+	var err error
+
+	xlsxFile, err = OpenFile("./testdocs/testcelltypes.xlsx")
+	c.Assert(err, IsNil)
+	c.Assert(len(xlsxFile.Sheets), Equals, 1)
+	sheet := xlsxFile.Sheet["Sheet1"]
+	c.Assert(len(sheet.Rows), Equals, 8)
+	c.Assert(len(sheet.Rows[0].Cells), Equals, 2)
+
+	// string 1
+	c.Assert(sheet.Rows[0].Cells[0].Type(), Equals, CellTypeString)
+	c.Assert(sheet.Rows[0].Cells[0].String(), Equals, "hello world")
+
+	// string 2
+	c.Assert(sheet.Rows[1].Cells[0].Type(), Equals, CellTypeString)
+	c.Assert(sheet.Rows[1].Cells[0].String(), Equals, "日本語")
+
+	// integer
+	c.Assert(sheet.Rows[2].Cells[0].Type(), Equals, CellTypeNumeric)
+	intValue, _ := sheet.Rows[2].Cells[0].Int()
+	c.Assert(intValue, Equals, 12345)
+
+	// float
+	c.Assert(sheet.Rows[3].Cells[0].Type(), Equals, CellTypeNumeric)
+	floatValue, _ := sheet.Rows[3].Cells[0].Float()
+	c.Assert(floatValue, Equals, 1.024)
+
+	// Now it can't detect date
+	c.Assert(sheet.Rows[4].Cells[0].Type(), Equals, CellTypeNumeric)
+	intValue, _ = sheet.Rows[4].Cells[0].Int()
+	c.Assert(intValue, Equals, 40543)
+
+	// bool
+	c.Assert(sheet.Rows[5].Cells[0].Type(), Equals, CellTypeBool)
+	c.Assert(sheet.Rows[5].Cells[0].Bool(), Equals, true)
+
+	// formula
+	c.Assert(sheet.Rows[6].Cells[0].Type(), Equals, CellTypeFormula)
+	c.Assert(sheet.Rows[6].Cells[0].Formula(), Equals, "10+20")
+	c.Assert(sheet.Rows[6].Cells[0].Value, Equals, "30")
+
+	// error
+	c.Assert(sheet.Rows[7].Cells[0].Type(), Equals, CellTypeError)
+	c.Assert(sheet.Rows[7].Cells[0].Formula(), Equals, "10/0")
+	c.Assert(sheet.Rows[7].Cells[0].Value, Equals, "#DIV/0!")
+}
+
+func (s *SliceReaderSuite) TestFileWithEmptyRows(c *C) {
+	f, err := OpenFile("./testdocs/empty_rows.xlsx")
+	c.Assert(err, IsNil)
+	sheet, ok := f.Sheet["EmptyRows"]
+	c.Assert(ok, Equals, true)
+	c.Assert(sheet.Cell(0, 0).String(), Equals, "")
+	c.Assert(sheet.Cell(2, 0).String(), Equals, "A3")
+}
+
+func (s *SliceReaderSuite) TestFileWithEmptyCols(c *C) {
+  f, err := OpenFile("./testdocs/empty_rows.xlsx")
+  c.Assert(err, IsNil)
+  sheet, ok := f.Sheet["EmptyCols"]
+  c.Assert(ok, Equals, true)
+  c.Assert(sheet.Cell(0, 0).String(), Equals, "")
+  c.Assert(sheet.Cell(0, 2).String(), Equals, "C1")
+}

+ 90 - 41
lib.go

@@ -282,13 +282,17 @@ func makeRowFromRaw(rawrow xlsxRow) *Row {
 	upper = -1
 
 	for _, rawcell := range rawrow.C {
-		x, _, error := getCoordsFromCellIDString(rawcell.R)
-		if error != nil {
-			panic(fmt.Sprintf("Invalid Cell Coord, %s\n", rawcell.R))
-		}
-		if x > upper {
-			upper = x
+		if rawcell.R != "" {
+			x, _, error := getCoordsFromCellIDString(rawcell.R)
+			if error != nil {
+				panic(fmt.Sprintf("Invalid Cell Coord, %s\n", rawcell.R))
+			}
+			if x > upper {
+				upper = x
+			}
+			continue
 		}
+		upper++
 	}
 	upper++
 
@@ -301,11 +305,16 @@ func makeRowFromRaw(rawrow xlsxRow) *Row {
 	return row
 }
 
-// getValueFromCellData attempts to extract a valid value, usable in
+func makeEmptyRow() *Row {
+	row := new(Row)
+	row.Cells = make([]*Cell, 0)
+	return row
+}
+
+// fillCellData attempts to extract a valid value, usable in
 // CSV form from the raw cell value.  Note - this is not actually
 // general enough - we should support retaining tabs and newlines.
-func getValueFromCellData(rawcell xlsxC, reftable *RefTable) string {
-	var value string = ""
+func fillCellData(rawcell xlsxC, reftable *RefTable, cell *Cell) {
 	var data string = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
@@ -315,17 +324,34 @@ func getValueFromCellData(rawcell xlsxC, reftable *RefTable) string {
 			if error != nil {
 				panic(error)
 			}
-			value = reftable.ResolveSharedString(ref)
+			cell.Value = reftable.ResolveSharedString(ref)
+			cell.cellType = CellTypeString
+		case "b": // Boolean
+			cell.Value = vval
+			cell.cellType = CellTypeBool
+		case "e": // Error
+			cell.Value = vval
+			cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+			cell.cellType = CellTypeError
 		default:
-			value = vval
+			if len(rawcell.F) == 0 {
+				// Numeric
+				cell.Value = vval
+				cell.cellType = CellTypeNumeric
+			} else {
+				// Formula
+				cell.Value = vval
+				cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+				cell.cellType = CellTypeFormula
+			}
 		}
 	}
-	return value
 }
 
 // readRowsFromSheet is an internal helper function that extracts the
-// rows from a XSLXWorksheet, poulates them with Cells and resolves
+// rows from a XSLXWorksheet, populates them with Cells and resolves
 // the value references from the reference table and stores them in
+// the rows and columns.
 func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, int, int) {
 	var rows []*Row
 	var cols []*Col
@@ -347,8 +373,8 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 	if err != nil {
 		panic(err.Error())
 	}
-	rowCount = (maxRow - minRow) + 1
-	colCount = (maxCol - minCol) + 1
+	rowCount = maxRow + 1
+	colCount = maxCol + 1
 	rows = make([]*Row, rowCount)
 	cols = make([]*Col, colCount)
 	insertRowIndex = minRow
@@ -369,17 +395,23 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 			cols[i-1] = &Col{
 				Min:    rawcol.Min,
 				Max:    rawcol.Max,
-				Hidden: rawcol.Hidden}
+				Hidden: rawcol.Hidden,
+				Width:  rawcol.Width}
 		}
 	}
 
+	// insert leading empty rows that is in front of minRow
+	for rowIndex := 0; rowIndex < minRow; rowIndex++ {
+		rows[rowIndex] = makeEmptyRow()
+	}
+
 	for rowIndex := 0; rowIndex < len(Worksheet.SheetData.Row); rowIndex++ {
 		rawrow := Worksheet.SheetData.Row[rowIndex]
 		// Some spreadsheets will omit blank rows from the
 		// stored data
 		for rawrow.R > (insertRowIndex + 1) {
 			// Put an empty Row into the array
-			rows[insertRowIndex-minRow] = new(Row)
+			rows[insertRowIndex-minRow] = makeEmptyRow()
 			insertRowIndex++
 		}
 		// range is not empty and only one range exist
@@ -402,18 +434,19 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 				row.Cells[insertColIndex-minCol] = new(Cell)
 				insertColIndex++
 			}
-			cellX := insertColIndex - minCol
-			row.Cells[cellX].Value = getValueFromCellData(rawcell, reftable)
+			cellX := insertColIndex
+			cell := row.Cells[cellX]
+			fillCellData(rawcell, reftable, cell)
 			if file.styles != nil {
-				row.Cells[cellX].style = file.styles.getStyle(rawcell.S)
-				row.Cells[cellX].numFmt = file.styles.getNumberFormat(rawcell.S, file.numFmtRefTable)
+				cell.style = file.styles.getStyle(rawcell.S)
+				cell.numFmt = file.styles.getNumberFormat(rawcell.S)
 			}
-			row.Cells[cellX].date1904 = file.Date1904
-			row.Cells[cellX].Hidden = rawrow.Hidden || (len(cols) > cellX && cols[cellX].Hidden)
+			cell.date1904 = file.Date1904
+			cell.Hidden = rawrow.Hidden || (len(cols) > cellX && cell.Hidden)
 			insertColIndex++
 		}
-		if len(rows) > insertRowIndex-minRow {
-			rows[insertRowIndex-minRow] = row
+		if len(rows) > insertRowIndex {
+			rows[insertRowIndex] = row
 		}
 		insertRowIndex++
 	}
@@ -439,7 +472,9 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 		return
 	}
 	sheet := new(Sheet)
+	sheet.File = fi
 	sheet.Rows, sheet.Cols, sheet.MaxCol, sheet.MaxRow = readRowsFromSheet(worksheet, fi)
+	sheet.Hidden = rsheet.State == sheetStateHidden || rsheet.State == sheetStateVeryHidden
 	result.Sheet = sheet
 	sc <- result
 }
@@ -449,28 +484,41 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 // Sheet objects stored in the Sheets slice of a xlsx.File struct.
 func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]string) (map[string]*Sheet, []*Sheet, error) {
 	var workbook *xlsxWorkbook
-	var error error
+	var err error
 	var rc io.ReadCloser
 	var decoder *xml.Decoder
 	var sheetCount int
 	workbook = new(xlsxWorkbook)
-	rc, error = f.Open()
-	if error != nil {
-		return nil, nil, error
+	rc, err = f.Open()
+	if err != nil {
+		return nil, nil, err
 	}
 	decoder = xml.NewDecoder(rc)
-	error = decoder.Decode(workbook)
-	if error != nil {
-		return nil, nil, error
+	err = decoder.Decode(workbook)
+	if err != nil {
+		return nil, nil, err
 	}
 	file.Date1904 = workbook.WorkbookPr.Date1904
 	sheetCount = len(workbook.Sheets.Sheet)
 	sheetsByName := make(map[string]*Sheet, sheetCount)
 	sheets := make([]*Sheet, sheetCount)
 	sheetChan := make(chan *indexedSheet, sheetCount)
-	for i, rawsheet := range workbook.Sheets.Sheet {
-		go readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap)
-	}
+	defer close(sheetChan)
+
+	go func() {
+		defer func() {
+			if e := recover(); e != nil {
+				err = fmt.Errorf("%v", e)
+				result := &indexedSheet{Index: -1, Sheet: nil, Error: err}
+				sheetChan <- result
+			}
+		}()
+		err = nil
+		for i, rawsheet := range workbook.Sheets.Sheet {
+			readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap)
+		}
+	}()
+
 	for j := 0; j < sheetCount; j++ {
 		sheet := <-sheetChan
 		if sheet.Error != nil {
@@ -526,21 +574,21 @@ func readStylesFromZipFile(f *zip.File) (*xlsxStyleSheet, error) {
 	if error != nil {
 		return nil, error
 	}
-	style = new(xlsxStyleSheet)
+	style = newXlsxStyleSheet()
 	decoder = xml.NewDecoder(rc)
 	error = decoder.Decode(style)
 	if error != nil {
 		return nil, error
 	}
+	buildNumFmtRefTable(style)
 	return style, nil
 }
 
-func buildNumFmtRefTable(style *xlsxStyleSheet) map[int]xlsxNumFmt {
-	refTable := make(map[int]xlsxNumFmt)
+func buildNumFmtRefTable(style *xlsxStyleSheet) {
 	for _, numFmt := range style.NumFmts.NumFmt {
-		refTable[numFmt.NumFmtId] = numFmt
+		// We do this for the side effect of populating the NumFmtRefTable.
+		style.addNumFmt(numFmt)
 	}
-	return refTable
 }
 
 type WorkBookRels map[string]string
@@ -640,7 +688,8 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 	var workbookRels *zip.File
 	var worksheets map[string]*zip.File
 
-	file = new(File)
+	file = NewFile()
+	// file.numFmtRefTable = make(map[int]xlsxNumFmt, 1)
 	worksheets = make(map[string]*zip.File, len(r.File))
 	for _, v = range r.File {
 		switch v.Name {

+ 222 - 9
lib_test.go

@@ -32,16 +32,11 @@ func (l *LibSuite) TestWorkBookRelsMarshal(c *C) {
 	var rels WorkBookRels = make(WorkBookRels)
 	rels["rId1"] = "worksheets/sheet.xml"
 	expectedXML := `<?xml version="1.0" encoding="UTF-8"?>
-  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
-    <Relationship Id="rId1" Target="worksheets/sheet.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship>
-    <Relationship Id="rId2" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"></Relationship>
-    <Relationship Id="rId3" Target="theme/theme1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"></Relationship>
-    <Relationship Id="rId4" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"></Relationship>
-  </Relationships>`
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Target="worksheets/sheet.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"></Relationship><Relationship Id="rId2" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"></Relationship><Relationship Id="rId3" Target="theme/theme1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"></Relationship><Relationship Id="rId4" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"></Relationship></Relationships>`
 	xRels := rels.MakeXLSXWorkbookRels()
 
 	output := bytes.NewBufferString(xml.Header)
-	body, err := xml.MarshalIndent(xRels, "  ", "  ")
+	body, err := xml.Marshal(xRels)
 	c.Assert(err, IsNil)
 	c.Assert(body, NotNil)
 	_, err = output.Write(body)
@@ -361,9 +356,83 @@ func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {
 
 	file := new(File)
 	file.referenceTable = MakeSharedStringRefTable(sst)
-	_, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
-	c.Assert(maxRows, Equals, 2)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxRows, Equals, 5)
 	c.Assert(maxCols, Equals, 1)
+
+	c.Assert(len(rows[0].Cells), Equals, 0)
+	c.Assert(len(rows[1].Cells), Equals, 0)
+	c.Assert(len(rows[2].Cells), Equals, 0)
+	c.Assert(len(rows[3].Cells), Equals, 1)
+	c.Assert(rows[3].Cells[0].String(), Equals, "ABC")
+	c.Assert(len(rows[4].Cells), Equals, 1)
+	c.Assert(rows[4].Cells[0].String(), Equals, "DEF")
+}
+
+func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyCols(c *C) {
+	var sharedstringsXML = bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="2" uniqueCount="2"><si><t>ABC</t></si><si><t>DEF</t></si></sst>`)
+	var sheetxml = bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
+  <dimension ref="C1:D2"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="A2" sqref="A2"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15" x14ac:dyDescent="0"/>
+  <cols>
+  	<col min="3" max="3" width="17" customWidth="1"/>
+  	<col min="4" max="4" width="18" customWidth="1"/>
+  </cols>
+  <sheetData>
+    <row r="1" spans="3:4">
+      <c r="C1" t="s"><v>0</v></c>
+      <c r="D1" t="s"><v>1</v></c>
+    </row>
+    <row r="2" spans="3:4">
+      <c r="C2" t="s"><v>0</v></c>
+      <c r="D2" t="s"><v>1</v></c>
+    </row>
+  </sheetData>
+  <pageMargins left="0.75" right="0.75" top="1" bottom="1" header="0.5" footer="0.5"/>
+  <pageSetup paperSize="9" orientation="portrait" horizontalDpi="4294967292" verticalDpi="4294967292"/>
+  <extLst>
+    <ext uri="{64002731-A6B0-56B0-2670-7721B7C09600}" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main">
+      <mx:PLV Mode="0" OnePage="0" WScale="0"/>
+    </ext>
+  </extLst>
+</worksheet>
+`)
+	worksheet := new(xlsxWorksheet)
+	err := xml.NewDecoder(sheetxml).Decode(worksheet)
+	c.Assert(err, IsNil)
+	sst := new(xlsxSST)
+	err = xml.NewDecoder(sharedstringsXML).Decode(sst)
+	c.Assert(err, IsNil)
+
+	file := new(File)
+	file.referenceTable = MakeSharedStringRefTable(sst)
+	rows, cols, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxRows, Equals, 2)
+	c.Assert(maxCols, Equals, 4)
+
+	c.Assert(len(rows[0].Cells), Equals, 4)
+	c.Assert(rows[0].Cells[0].String(), Equals, "")
+	c.Assert(rows[0].Cells[1].String(), Equals, "")
+	c.Assert(rows[0].Cells[2].String(), Equals, "ABC")
+	c.Assert(rows[0].Cells[3].String(), Equals, "DEF")
+	c.Assert(len(rows[1].Cells), Equals, 4)
+	c.Assert(rows[1].Cells[0].String(), Equals, "")
+	c.Assert(rows[1].Cells[1].String(), Equals, "")
+	c.Assert(rows[1].Cells[2].String(), Equals, "ABC")
+	c.Assert(rows[1].Cells[3].String(), Equals, "DEF")
+
+	c.Assert(len(cols), Equals, 4)
+	c.Assert(cols[0].Width, Equals, 0.0)
+	c.Assert(cols[1].Width, Equals, 0.0)
+	c.Assert(cols[2].Width, Equals, 17.0)
+	c.Assert(cols[3].Width, Equals, 18.0)
 }
 
 func (l *LibSuite) TestReadRowsFromSheetWithEmptyCells(c *C) {
@@ -614,3 +683,147 @@ func (l *LibSuite) TestReadRowsFromSheetWithMultipleSpans(c *C) {
 	c.Assert(cell4.Value, Equals, "Bar")
 
 }
+
+func (l *LibSuite) TestReadRowsFromSheetWithMultipleTypes(c *C) {
+	var sharedstringsXML = bytes.NewBufferString(`
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="4" uniqueCount="4">
+  <si>
+    <t>Hello World</t>
+  </si>
+</sst>`)
+	var sheetxml = bytes.NewBufferString(`
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+           xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
+  <dimension ref="A1:F1"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="C1" sqref="C1"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+  <sheetData>
+    <row r="1" spans="1:6">
+      <c r="A1" t="s">
+        <v>0</v>
+      </c>
+      <c r="B1">
+        <v>12345</v>
+      </c>
+      <c r="C1">
+        <v>1.024</v>
+      </c>
+      <c r="D1" t="b">
+        <v>1</v>
+      </c>
+      <c r="E1">
+      	<f>10+20</f>
+        <v>30</v>
+      </c>
+      <c r="F1" t="e">
+      	<f>10/0</f>
+        <v>#DIV/0!</v>
+      </c>
+    </row>
+  </sheetData>
+  <pageMargins left="0.7" right="0.7"
+               top="0.78740157499999996"
+               bottom="0.78740157499999996"
+               header="0.3"
+               footer="0.3"/>
+</worksheet>`)
+	worksheet := new(xlsxWorksheet)
+	err := xml.NewDecoder(sheetxml).Decode(worksheet)
+	c.Assert(err, IsNil)
+	sst := new(xlsxSST)
+	err = xml.NewDecoder(sharedstringsXML).Decode(sst)
+	c.Assert(err, IsNil)
+	file := new(File)
+	file.referenceTable = MakeSharedStringRefTable(sst)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxRows, Equals, 1)
+	c.Assert(maxCols, Equals, 6)
+	row := rows[0]
+	c.Assert(len(row.Cells), Equals, 6)
+
+	cell1 := row.Cells[0]
+	c.Assert(cell1.Type(), Equals, CellTypeString)
+	c.Assert(cell1.String(), Equals, "Hello World")
+
+	cell2 := row.Cells[1]
+	c.Assert(cell2.Type(), Equals, CellTypeNumeric)
+	intValue, _ := cell2.Int()
+	c.Assert(intValue, Equals, 12345)
+
+	cell3 := row.Cells[2]
+	c.Assert(cell3.Type(), Equals, CellTypeNumeric)
+	float, _ := cell3.Float()
+	c.Assert(float, Equals, 1.024)
+
+	cell4 := row.Cells[3]
+	c.Assert(cell4.Type(), Equals, CellTypeBool)
+	c.Assert(cell4.Bool(), Equals, true)
+
+	cell5 := row.Cells[4]
+	c.Assert(cell5.Type(), Equals, CellTypeFormula)
+	c.Assert(cell5.Formula(), Equals, "10+20")
+	c.Assert(cell5.Value, Equals, "30")
+
+	cell6 := row.Cells[5]
+	c.Assert(cell6.Type(), Equals, CellTypeError)
+	c.Assert(cell6.Formula(), Equals, "10/0")
+	c.Assert(cell6.Value, Equals, "#DIV/0!")
+}
+
+// When converting the xlsxRow to a Row we create a as many cells as we find.
+func (l *LibSuite) TestReadRowFromRaw(c *C) {
+	var rawRow xlsxRow
+	var cell xlsxC
+	var row *Row
+
+	rawRow = xlsxRow{}
+	cell = xlsxC{R: "A1"}
+	cell = xlsxC{R: "A2"}
+	rawRow.C = append(rawRow.C, cell)
+	row = makeRowFromRaw(rawRow)
+	c.Assert(row, NotNil)
+	c.Assert(row.Cells, HasLen, 1)
+}
+
+// When a cell claims it is at a position greater than its ordinal
+// position in the file we make up the missing cells.
+func (l *LibSuite) TestReadRowFromRawWithMissingCells(c *C) {
+	var rawRow xlsxRow
+	var cell xlsxC
+	var row *Row
+
+	rawRow = xlsxRow{}
+	cell = xlsxC{R: "A1"}
+	rawRow.C = append(rawRow.C, cell)
+	cell = xlsxC{R: "E1"}
+	rawRow.C = append(rawRow.C, cell)
+	row = makeRowFromRaw(rawRow)
+	c.Assert(row, NotNil)
+	c.Assert(row.Cells, HasLen, 5)
+}
+
+// We can cope with missing coordinate references
+func (l *LibSuite) TestReadRowFromRawWithPartialCoordinates(c *C) {
+	var rawRow xlsxRow
+	var cell xlsxC
+	var row *Row
+
+	rawRow = xlsxRow{}
+	cell = xlsxC{R: "A1"}
+	rawRow.C = append(rawRow.C, cell)
+	cell = xlsxC{}
+	rawRow.C = append(rawRow.C, cell)
+	cell = xlsxC{R: "Z:1"}
+	rawRow.C = append(rawRow.C, cell)
+	cell = xlsxC{}
+	rawRow.C = append(rawRow.C, cell)
+	row = makeRowFromRaw(rawRow)
+	c.Assert(row, NotNil)
+	c.Assert(row.Cells, HasLen, 27)
+}

+ 2 - 6
reftable_test.go

@@ -92,18 +92,14 @@ func (s *RefTableSuite) TestMarshalSST(c *C) {
 	sst := refTable.makeXLSXSST()
 
 	output := bytes.NewBufferString(xml.Header)
-	body, err := xml.MarshalIndent(sst, "  ", "  ")
+	body, err := xml.Marshal(sst)
 	c.Assert(err, IsNil)
 	c.Assert(body, NotNil)
 	_, err = output.Write(body)
 	c.Assert(err, IsNil)
 
 	expectedXLSXSST := `<?xml version="1.0" encoding="UTF-8"?>
-  <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1">
-    <si>
-      <t>Foo</t>
-    </si>
-  </sst>`
+<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1"><si><t>Foo</t></si></sst>`
 	c.Assert(output.String(), Equals, expectedXLSXSST)
 }
 

+ 3 - 3
row.go

@@ -3,12 +3,12 @@ package xlsx
 type Row struct {
 	Cells  []*Cell
 	Hidden bool
-	sheet  *Sheet
+	Sheet  *Sheet
 }
 
 func (r *Row) AddCell() *Cell {
-	cell := NewCell()
+	cell := NewCell(r)
 	r.Cells = append(r.Cells, cell)
-	r.sheet.maybeAddCol(len(r.Cells))
+	r.Sheet.maybeAddCol(len(r.Cells))
 	return cell
 }

+ 52 - 16
sheet.go

@@ -5,22 +5,21 @@ import (
 	"strconv"
 )
 
-// Default column width in excel
-const colWidth = 9.5
-
 // 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   []*Col
 	MaxRow int
 	MaxCol int
+	Hidden bool
 }
 
 // Add a new Row to a Sheet
 func (s *Sheet) AddRow() *Row {
-	row := &Row{sheet: s}
+	row := &Row{Sheet: s}
 	s.Rows = append(s.Rows, row)
 	if len(s.Rows) > s.MaxRow {
 		s.MaxRow = len(s.Rows)
@@ -31,7 +30,14 @@ func (s *Sheet) AddRow() *Row {
 // Make sure we always have as many Cols as we do cells.
 func (s *Sheet) maybeAddCol(cellCount int) {
 	if cellCount > s.MaxCol {
-		s.Cols = append(s.Cols, &Col{Min: cellCount, Max: cellCount, Hidden: false})
+		col := &Col{
+			Min:       cellCount,
+			Max:       cellCount,
+			Hidden:    false,
+			Collapsed: false,
+			// Style:     0,
+			Width: ColWidth}
+		s.Cols = append(s.Cols, col)
 		s.MaxCol = cellCount
 	}
 }
@@ -55,10 +61,11 @@ func (sh *Sheet) Cell(row, col int) *Cell {
 
 // Dump sheet to it's XML representation, intended for internal use only
 func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxWorksheet {
-	worksheet := &xlsxWorksheet{}
+	worksheet := newXlsxWorksheet()
 	xSheet := xlsxSheetData{}
 	maxRow := 0
 	maxCell := 0
+	XfId := 0
 	for r, row := range s.Rows {
 		if r > maxRow {
 			maxRow = r
@@ -67,6 +74,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		xRow.R = r + 1
 		for c, cell := range row.Cells {
 			style := cell.GetStyle()
+			if style != nil {
 			xFont, xFill, xBorder, xCellStyleXf, xCellXf := style.makeXLSXStyleElements()
 			fontId := styles.addFont(xFont)
 			fillId := styles.addFill(xFill)
@@ -74,22 +82,41 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			xCellStyleXf.FontId = fontId
 			xCellStyleXf.FillId = fillId
 			xCellStyleXf.BorderId = borderId
+			xCellStyleXf.NumFmtId = 0 // General
 			xCellXf.FontId = fontId
 			xCellXf.FillId = fillId
 			xCellXf.BorderId = borderId
-			styleXfId := styles.addCellStyleXf(xCellStyleXf)
-			XfId := styles.addCellXf(xCellXf)
-			if styleXfId != XfId {
-				panic("StyleXFId != XfId, this should never happen.")
+			xCellXf.NumFmtId = 0 // General
+			styles.addCellStyleXf(xCellStyleXf)
+			XfId = styles.addCellXf(xCellXf)
 			}
 			if c > maxCell {
 				maxCell = c
 			}
 			xC := xlsxC{}
 			xC.R = fmt.Sprintf("%s%d", numericToLetters(c), r+1)
-			xC.V = strconv.Itoa(refTable.AddString(cell.Value))
-			xC.T = "s" // Hardcode string type, for now.
-			xC.S = XfId
+			switch cell.cellType {
+			case CellTypeString:
+				xC.V = strconv.Itoa(refTable.AddString(cell.Value))
+				xC.T = "s"
+				xC.S = XfId
+			case CellTypeBool:
+				xC.V = cell.Value
+				xC.T = "b"
+				xC.S = XfId
+			case CellTypeNumeric:
+				xC.V = cell.Value
+				xC.S = XfId
+			case CellTypeFormula:
+				xC.V = cell.Value
+				xC.F = cell.formula
+				xC.S = XfId
+			case CellTypeError:
+				xC.V = cell.Value
+				xC.F = cell.formula
+				xC.T = "e"
+				xC.S = XfId
+			}
 			xRow.C = append(xRow.C, xC)
 		}
 		xSheet.Row = append(xSheet.Row, xRow)
@@ -97,16 +124,25 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 
 	worksheet.Cols = xlsxCols{Col: []xlsxCol{}}
 	for _, col := range s.Cols {
+		if col.Width == 0 {
+			col.Width = ColWidth
+		}
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 			xlsxCol{Min: col.Min,
-				Max:    col.Max,
-				Hidden: col.Hidden,
-				Width:  colWidth})
+				Max:       col.Max,
+				Hidden:    col.Hidden,
+				Width:     col.Width,
+				Collapsed: col.Collapsed,
+				// Style:     col.Style
+			})
 	}
 	worksheet.SheetData = xSheet
 	dimension := xlsxDimension{}
 	dimension.Ref = fmt.Sprintf("A1:%s%d",
 		numericToLetters(maxCell), maxRow+1)
+	if dimension.Ref == "A1:A1" {
+		dimension.Ref = "A1"
+	}
 	worksheet.Dimension = dimension
 	return worksheet
 }

+ 17 - 54
sheet_test.go

@@ -28,9 +28,9 @@ func (s *SheetSuite) TestMakeXLSXSheetFromRows(c *C) {
 	cell := row.AddCell()
 	cell.Value = "A cell!"
 	refTable := NewSharedStringRefTable()
-	styles := &xlsxStyleSheet{}
+	styles := newXlsxStyleSheet()
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
-	c.Assert(xSheet.Dimension.Ref, Equals, "A1:A1")
+	c.Assert(xSheet.Dimension.Ref, Equals, "A1")
 	c.Assert(xSheet.SheetData.Row, HasLen, 1)
 	xRow := xSheet.SheetData.Row[0]
 	c.Assert(xRow.R, Equals, 1)
@@ -58,7 +58,7 @@ func (s *SheetSuite) TestMakeXLSXSheetAlsoPopulatesXLSXSTyles(c *C) {
 
 	cell1 := row.AddCell()
 	cell1.Value = "A cell!"
-	style1 := *NewStyle()
+	style1 := NewStyle()
 	style1.Font = *NewFont(10, "Verdana")
 	style1.Fill = *NewFill("solid", "FFFFFFFF", "00000000")
 	style1.Border = *NewBorder("none", "thin", "none", "thin")
@@ -67,55 +67,46 @@ func (s *SheetSuite) TestMakeXLSXSheetAlsoPopulatesXLSXSTyles(c *C) {
 	// We need a second style to check that Xfs are populated correctly.
 	cell2 := row.AddCell()
 	cell2.Value = "Another cell!"
-	style2 := *NewStyle()
+	style2 := NewStyle()
 	style2.Font = *NewFont(10, "Verdana")
 	style2.Fill = *NewFill("solid", "FFFFFFFF", "00000000")
 	style2.Border = *NewBorder("none", "thin", "none", "thin")
 	cell2.SetStyle(style2)
 
 	refTable := NewSharedStringRefTable()
-	styles := &xlsxStyleSheet{}
+	styles := newXlsxStyleSheet()
 	worksheet := sheet.makeXLSXSheet(refTable, styles)
 
-	c.Assert(styles.Fonts.Count, Equals, 2)
+	c.Assert(styles.Fonts.Count, Equals, 1)
 	c.Assert(styles.Fonts.Font[0].Sz.Val, Equals, "10")
 	c.Assert(styles.Fonts.Font[0].Name.Val, Equals, "Verdana")
 
-	c.Assert(styles.Fills.Count, Equals, 2)
+	c.Assert(styles.Fills.Count, Equals, 1)
 	c.Assert(styles.Fills.Fill[0].PatternFill.PatternType, Equals, "solid")
 	c.Assert(styles.Fills.Fill[0].PatternFill.FgColor.RGB, Equals, "FFFFFFFF")
 	c.Assert(styles.Fills.Fill[0].PatternFill.BgColor.RGB, Equals, "00000000")
 
-	c.Assert(styles.Borders.Count, Equals, 2)
+	c.Assert(styles.Borders.Count, Equals, 1)
 	c.Assert(styles.Borders.Border[0].Left.Style, Equals, "none")
 	c.Assert(styles.Borders.Border[0].Right.Style, Equals, "thin")
 	c.Assert(styles.Borders.Border[0].Top.Style, Equals, "none")
 	c.Assert(styles.Borders.Border[0].Bottom.Style, Equals, "thin")
 
-	c.Assert(styles.CellStyleXfs.Count, Equals, 2)
+	c.Assert(styles.CellStyleXfs.Count, Equals, 1)
 	// The 0th CellStyleXf could just be getting the zero values by default
 	c.Assert(styles.CellStyleXfs.Xf[0].FontId, Equals, 0)
 	c.Assert(styles.CellStyleXfs.Xf[0].FillId, Equals, 0)
 	c.Assert(styles.CellStyleXfs.Xf[0].BorderId, Equals, 0)
-	// The 1st element cannot get initialised this way by accident.
-	c.Assert(styles.CellStyleXfs.Xf[1].FontId, Equals, 1)
-	c.Assert(styles.CellStyleXfs.Xf[1].FillId, Equals, 1)
-	c.Assert(styles.CellStyleXfs.Xf[1].BorderId, Equals, 1)
 
-	c.Assert(styles.CellXfs.Count, Equals, 2)
+	c.Assert(styles.CellXfs.Count, Equals, 1)
 	c.Assert(styles.CellXfs.Xf[0].FontId, Equals, 0)
 	c.Assert(styles.CellXfs.Xf[0].FillId, Equals, 0)
 	c.Assert(styles.CellXfs.Xf[0].BorderId, Equals, 0)
-	// As above, we need the 1st element to make the test fail
-	// when it should.
-	c.Assert(styles.CellXfs.Xf[1].FontId, Equals, 1)
-	c.Assert(styles.CellXfs.Xf[1].FillId, Equals, 1)
-	c.Assert(styles.CellXfs.Xf[1].BorderId, Equals, 1)
 
 	// Finally we check that the cell points to the right CellXf /
 	// CellStyleXf.
 	c.Assert(worksheet.SheetData.Row[0].C[0].S, Equals, 0)
-	c.Assert(worksheet.SheetData.Row[0].C[1].S, Equals, 1)
+	c.Assert(worksheet.SheetData.Row[0].C[1].S, Equals, 0)
 }
 
 func (s *SheetSuite) TestMarshalSheet(c *C) {
@@ -125,29 +116,17 @@ func (s *SheetSuite) TestMarshalSheet(c *C) {
 	cell := row.AddCell()
 	cell.Value = "A cell!"
 	refTable := NewSharedStringRefTable()
-	styles := &xlsxStyleSheet{}
+	styles := newXlsxStyleSheet()
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
 
 	output := bytes.NewBufferString(xml.Header)
-	body, err := xml.MarshalIndent(xSheet, "  ", "  ")
+	body, err := xml.Marshal(xSheet)
 	c.Assert(err, IsNil)
 	c.Assert(body, NotNil)
 	_, err = output.Write(body)
 	c.Assert(err, IsNil)
 	expectedXLSXSheet := `<?xml version="1.0" encoding="UTF-8"?>
-  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <dimension ref="A1:A1"></dimension>
-    <cols>
-      <col min="1" max="1" width="9.5"></col>
-    </cols>
-    <sheetData>
-      <row r="1">
-        <c r="A1" s="0" t="s">
-          <v>0</v>
-        </c>
-      </row>
-    </sheetData>
-  </worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="0" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
 	c.Assert(output.String(), Equals, expectedXLSXSheet)
 }
 
@@ -160,32 +139,16 @@ func (s *SheetSuite) TestMarshalSheetWithMultipleCells(c *C) {
 	cell = row.AddCell()
 	cell.Value = "A cell (with value 2)!"
 	refTable := NewSharedStringRefTable()
-	styles := &xlsxStyleSheet{}
+	styles := newXlsxStyleSheet()
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
 
 	output := bytes.NewBufferString(xml.Header)
-	body, err := xml.MarshalIndent(xSheet, "  ", "  ")
+	body, err := xml.Marshal(xSheet)
 	c.Assert(err, IsNil)
 	c.Assert(body, NotNil)
 	_, err = output.Write(body)
 	c.Assert(err, IsNil)
 	expectedXLSXSheet := `<?xml version="1.0" encoding="UTF-8"?>
-  <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <dimension ref="A1:B1"></dimension>
-    <cols>
-      <col min="1" max="1" width="9.5"></col>
-      <col min="2" max="2" width="9.5"></col>
-    </cols>
-    <sheetData>
-      <row r="1">
-        <c r="A1" s="0" t="s">
-          <v>0</v>
-        </c>
-        <c r="B1" s="1" t="s">
-          <v>1</v>
-        </c>
-      </row>
-    </sheetData>
-  </worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1:B1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" width="9.5"></col><col collapsed="false" hidden="false" max="2" min="2" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="0" t="s"><v>0</v></c><c r="B1" s="0" t="s"><v>1</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
 	c.Assert(output.String(), Equals, expectedXLSXSheet)
 }

+ 20 - 1
style.go

@@ -15,7 +15,11 @@ type Style struct {
 
 // Return a new Style structure initialised with the default values.
 func NewStyle() *Style {
-	return &Style{Font: *NewFont(12, "Verdana")}
+	return &Style{
+		Font:   *DefaulFont(),
+		Border: *DefaulBorder(),
+		Fill:   *DefaulFill(),
+	}
 }
 
 // Generate the underlying XLSX style elements that correspond to the Style.
@@ -42,9 +46,11 @@ func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBo
 	xCellXf.ApplyBorder = style.ApplyBorder
 	xCellXf.ApplyFill = style.ApplyFill
 	xCellXf.ApplyFont = style.ApplyFont
+	xCellXf.NumFmtId = 0
 	xCellStyleXf.ApplyBorder = style.ApplyBorder
 	xCellStyleXf.ApplyFill = style.ApplyFill
 	xCellStyleXf.ApplyFont = style.ApplyFont
+	xCellStyleXf.NumFmtId = 0
 	return
 }
 
@@ -84,3 +90,16 @@ type Font struct {
 func NewFont(size int, name string) *Font {
 	return &Font{Size: size, Name: name}
 }
+
+func DefaulFont() *Font {
+	return NewFont(12, "Verdana")
+}
+
+func DefaulFill() *Fill {
+	return NewFill("none", "FFFFFFFF", "00000000")
+
+}
+
+func DefaulBorder() *Border {
+	return NewBorder("none", "none", "none", "none")
+}

+ 10 - 0
style_test.go

@@ -13,6 +13,13 @@ func (s *StyleSuite) TestNewStyle(c *C) {
 	c.Assert(style, NotNil)
 }
 
+func (s *StyleSuite) TestNewStyleDefaults(c *C) {
+	style := NewStyle()
+	c.Assert(style.Font, Equals, *DefaulFont())
+	c.Assert(style.Fill, Equals, *DefaulFill())
+	c.Assert(style.Border, Equals, *DefaulBorder())
+}
+
 func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	style := NewStyle()
 	font := *NewFont(12, "Verdana")
@@ -23,8 +30,11 @@ func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	style.Border = border
 	style.ApplyBorder = true
 	style.ApplyFill = true
+
 	style.ApplyFont = true
 	xFont, xFill, xBorder, xCellStyleXf, xCellXf := style.makeXLSXStyleElements()
+	// c.Assert(xNumFmt.NumFmtId, Equals, 164)
+	// c.Assert(xNumFmt.FormatCode, Equals, "GENERAL")
 	c.Assert(xFont.Sz.Val, Equals, "12")
 	c.Assert(xFont.Name.Val, Equals, "Verdana")
 	c.Assert(xFill.PatternFill.PatternType, Equals, "solid")

BIN
testdocs/empty_rows.xlsx


BIN
testdocs/testcelltypes.xlsx


+ 102 - 0
write.go

@@ -0,0 +1,102 @@
+package xlsx
+
+import "reflect"
+
+// Writes an array to row r. Accepts a pointer to array type 'e',
+// and writes the number of columns to write, 'cols'. If 'cols' is < 0,
+// the entire array will be written if possible. Retuens -1 if the 'e'
+// doesn't point to an array, otherwise the number of columns written.
+func (r *Row) WriteSlice(e interface{}, cols int) int {
+	if cols == 0 {
+		return cols
+	}
+
+	t := reflect.TypeOf(e).Elem()
+	if t.Kind() != reflect.Slice { // is 'e' even a slice?
+		return -1
+	}
+
+	// it's a slice, so open up its values
+	v := reflect.ValueOf(e).Elem()
+
+	n := v.Len()
+	if cols < n && cols > 0 {
+		n = cols
+	}
+
+	var i int
+	switch t.Elem().Kind() { // underlying type of slice
+	case reflect.String:
+		for i = 0; i < n; i++ {
+			cell := r.AddCell()
+			cell.SetString(v.Index(i).Interface().(string))
+		}
+	case reflect.Int, reflect.Int8,
+		reflect.Int16, reflect.Int32:
+		for i = 0; i < n; i++ {
+			cell := r.AddCell()
+			cell.SetInt(v.Index(i).Interface().(int))
+		}
+	case reflect.Int64:
+		for i = 0; i < n; i++ {
+			cell := r.AddCell()
+			cell.SetInt64(v.Index(i).Interface().(int64))
+		}
+	case reflect.Bool:
+		for i = 0; i < n; i++ {
+			cell := r.AddCell()
+			cell.SetBool(v.Index(i).Interface().(bool))
+		}
+	case reflect.Float64, reflect.Float32:
+		for i = 0; i < n; i++ {
+			cell := r.AddCell()
+			cell.SetFloat(v.Index(i).Interface().(float64))
+		}
+	}
+
+	return i
+}
+
+// Writes a struct to row r. Accepts a pointer to struct type 'e',
+// and the number of columns to write, `cols`. If 'cols' is < 0,
+// the entire struct will be written if possible. Returns -1 if the 'e'
+// doesn't point to a struct, otherwise the number of columns written
+func (r *Row) WriteStruct(e interface{}, cols int) int {
+	if cols == 0 {
+		return cols
+	}
+
+	v := reflect.ValueOf(e).Elem()
+	if v.Kind() != reflect.Struct {
+		return -1 // bail if it's not a struct
+	}
+
+	n := v.NumField() // number of fields in struct
+	if cols < n && cols > 0 {
+		n = cols
+	}
+
+	var k int
+	for i := 0; i < n; i, k = i+1, k+1 {
+		f := v.Field(i).Kind()
+		cell := r.AddCell()
+
+		switch f {
+		case reflect.Int, reflect.Int8,
+			reflect.Int16, reflect.Int32:
+			cell.SetInt(v.Field(i).Interface().(int))
+		case reflect.Int64:
+			cell.SetInt64(v.Field(i).Interface().(int64))
+		case reflect.String:
+			cell.SetString(v.Field(i).Interface().(string))
+		case reflect.Float64, reflect.Float32:
+			cell.SetFloat(v.Field(i).Interface().(float64))
+		case reflect.Bool:
+			cell.SetBool(v.Field(i).Interface().(bool))
+		default:
+			k-- // nothing set so reset to previous
+		}
+	}
+
+	return k
+}

+ 86 - 0
write_test.go

@@ -0,0 +1,86 @@
+package xlsx
+
+import (
+	. "gopkg.in/check.v1"
+)
+
+type WriteSuite struct{}
+
+var _ = Suite(&WriteSuite{})
+
+// Test if we can write a struct to a row
+func (r *RowSuite) TestWriteStruct(c *C) {
+	var f *File
+	f = NewFile()
+	sheet := f.AddSheet("Test1")
+	row := sheet.AddRow()
+	type e struct {
+		FirstName string
+		Age       int
+		GPA       float64
+		LikesPHP  bool
+	}
+	testStruct := e{
+		"Eric",
+		20,
+		3.94,
+		false,
+	}
+	row.WriteStruct(&testStruct, -1)
+	c.Assert(row, NotNil)
+
+	c0 := row.Cells[0].String()
+	c1, e1 := row.Cells[1].Int()
+	c2, e2 := row.Cells[2].Float()
+	c3 := row.Cells[3].Bool()
+
+	c.Assert(c0, Equals, "Eric")
+	c.Assert(c1, Equals, 20)
+	c.Assert(c2, Equals, 3.94)
+	c.Assert(c3, Equals, false)
+
+	c.Assert(e1, Equals, nil)
+	c.Assert(e2, Equals, nil)
+}
+
+// Test if we can write a slice to a row
+func (r *RowSuite) TestWriteSlice(c *C) {
+	var f *File
+	f = NewFile()
+	sheet := f.AddSheet("Test1")
+
+	type strA []string
+	type intA []int
+	type floatA []float64
+	type boolA []bool
+
+	s0 := strA{"Eric"}
+	row0 := sheet.AddRow()
+	row0.WriteSlice(&s0, -1)
+	c.Assert(row0, NotNil)
+	c0 := row0.Cells[0].String()
+	c.Assert(c0, Equals, "Eric")
+
+	s1 := intA{10}
+	row1 := sheet.AddRow()
+	row1.WriteSlice(&s1, -1)
+	c.Assert(row1, NotNil)
+	c1, e1 := row1.Cells[0].Int()
+	c.Assert(e1, Equals, nil)
+	c.Assert(c1, Equals, 10)
+
+	s2 := floatA{3.94}
+	row2 := sheet.AddRow()
+	row2.WriteSlice(&s2, -1)
+	c.Assert(row2, NotNil)
+	c2, e2 := row2.Cells[0].Float()
+	c.Assert(e2, Equals, nil)
+	c.Assert(c2, Equals, 3.94)
+
+	s3 := boolA{true}
+	row3 := sheet.AddRow()
+	row3.WriteSlice(&s3, -1)
+	c.Assert(row3, NotNil)
+	c3 := row3.Cells[0].Bool()
+	c.Assert(c3, Equals, true)
+}

+ 3 - 1
xmlContentTypes.go

@@ -22,7 +22,7 @@ type xlsxDefault struct {
 }
 
 func MakeDefaultContentTypes() (types xlsxTypes) {
-	types.Overrides = make([]xlsxOverride, 7)
+	types.Overrides = make([]xlsxOverride, 8)
 	types.Defaults = make([]xlsxDefault, 2)
 
 	types.Overrides[0].PartName = "/_rels/.rels"
@@ -39,6 +39,8 @@ func MakeDefaultContentTypes() (types xlsxTypes) {
 	types.Overrides[5].ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
 	types.Overrides[6].PartName = "/xl/workbook.xml"
 	types.Overrides[6].ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
+	types.Overrides[7].PartName = "/xl/theme/theme1.xml"
+	types.Overrides[7].ContentType = "application/vnd.openxmlformats-officedocument.theme+xml"
 
 	types.Defaults[0].Extension = "rels"
 	types.Defaults[0].ContentType = "application/vnd.openxmlformats-package.relationships+xml"

+ 6 - 5
xmlContentTypes_test.go

@@ -2,6 +2,7 @@ package xlsx
 
 import (
 	"encoding/xml"
+
 	. "gopkg.in/check.v1"
 )
 
@@ -13,19 +14,17 @@ func (l *ContentTypesSuite) TestMarshalContentTypes(c *C) {
 	var types xlsxTypes = xlsxTypes{}
 	types.Overrides = make([]xlsxOverride, 1)
 	types.Overrides[0] = xlsxOverride{PartName: "/_rels/.rels", ContentType: "application/vnd.openxmlformats-package.relationships+xml"}
-	output, err := xml.MarshalIndent(types, "   ", "   ")
+	output, err := xml.Marshal(types)
 	stringOutput := xml.Header + string(output)
 	c.Assert(err, IsNil)
 	expectedContentTypes := `<?xml version="1.0" encoding="UTF-8"?>
-   <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
-      <Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override>
-   </Types>`
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"></Override></Types>`
 	c.Assert(stringOutput, Equals, expectedContentTypes)
 }
 
 func (l *ContentTypesSuite) TestMakeDefaultContentTypes(c *C) {
 	var types xlsxTypes = MakeDefaultContentTypes()
-	c.Assert(len(types.Overrides), Equals, 7)
+	c.Assert(len(types.Overrides), Equals, 8)
 	c.Assert(types.Overrides[0].PartName, Equals, "/_rels/.rels")
 	c.Assert(types.Overrides[0].ContentType, Equals, "application/vnd.openxmlformats-package.relationships+xml")
 	c.Assert(types.Overrides[1].PartName, Equals, "/docProps/app.xml")
@@ -40,6 +39,8 @@ func (l *ContentTypesSuite) TestMakeDefaultContentTypes(c *C) {
 	c.Assert(types.Overrides[5].ContentType, Equals, "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml")
 	c.Assert(types.Overrides[6].PartName, Equals, "/xl/workbook.xml")
 	c.Assert(types.Overrides[6].ContentType, Equals, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml")
+	c.Assert(types.Overrides[7].PartName, Equals, "/xl/theme/theme1.xml")
+	c.Assert(types.Overrides[7].ContentType, Equals, "application/vnd.openxmlformats-officedocument.theme+xml")
 
 	c.Assert(types.Defaults[0].Extension, Equals, "rels")
 	c.Assert(types.Defaults[0].ContentType, Equals, "application/vnd.openxmlformats-package.relationships+xml")

+ 587 - 93
xmlStyle.go

@@ -9,8 +9,10 @@ package xlsx
 
 import (
 	"encoding/xml"
+	"fmt"
 	"strconv"
 	"strings"
+	"sync"
 )
 
 // xlsxStyle directly maps the styleSheet element in the namespace
@@ -26,6 +28,312 @@ type xlsxStyleSheet struct {
 	CellStyleXfs xlsxCellStyleXfs `xml:"cellStyleXfs,omitempty"`
 	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
 	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
+
+	styleCache map[int]*Style // `-`
+	numFmtRefTable map[int]xlsxNumFmt `xml:"-"`
+	lock       *sync.RWMutex
+}
+
+func newXlsxStyleSheet() *xlsxStyleSheet {
+	stylesheet := new(xlsxStyleSheet)
+	stylesheet.styleCache = make(map[int]*Style)
+	stylesheet.lock = new(sync.RWMutex)
+	return stylesheet
+}
+
+func (styles *xlsxStyleSheet) reset() {
+	styles.Fonts = xlsxFonts{}
+	styles.Fills = xlsxFills{}
+	styles.Borders = xlsxBorders{}
+	styles.CellStyleXfs = xlsxCellStyleXfs{}
+	styles.CellXfs = xlsxCellXfs{}
+	styles.NumFmts = xlsxNumFmts{}
+}
+
+func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
+	styles.lock.RLock()
+	style, ok := styles.styleCache[styleIndex]
+	styles.lock.RUnlock()
+	if ok {
+		return
+	}
+	var styleXf xlsxXf
+	style = &Style{}
+	style.Border = Border{}
+	style.Fill = Fill{}
+	style.Font = Font{}
+
+	xfCount := styles.CellXfs.Count
+	if styleIndex > -1 && xfCount > 0 && styleIndex <= xfCount {
+		xf := styles.CellXfs.Xf[styleIndex]
+
+		// Google docs can produce output that has fewer
+		// CellStyleXfs than CellXfs - this copes with that.
+		if styleIndex < styles.CellStyleXfs.Count {
+			styleXf = styles.CellStyleXfs.Xf[styleIndex]
+		} else {
+			styleXf = xlsxXf{}
+		}
+
+		style.ApplyBorder = xf.ApplyBorder || styleXf.ApplyBorder
+		style.ApplyFill = xf.ApplyFill || styleXf.ApplyFill
+		style.ApplyFont = xf.ApplyFont || styleXf.ApplyFont
+
+		if xf.BorderId > -1 && xf.BorderId < styles.Borders.Count {
+			var border xlsxBorder
+			border = styles.Borders.Border[xf.BorderId]
+			style.Border.Left = border.Left.Style
+			style.Border.Right = border.Right.Style
+			style.Border.Top = border.Top.Style
+			style.Border.Bottom = border.Bottom.Style
+		}
+
+		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
+			xFill := styles.Fills.Fill[xf.FillId]
+			style.Fill.PatternType = xFill.PatternFill.PatternType
+			style.Fill.FgColor = xFill.PatternFill.FgColor.RGB
+			style.Fill.BgColor = xFill.PatternFill.BgColor.RGB
+		}
+
+		if xf.FontId > -1 && xf.FontId < styles.Fonts.Count {
+			xfont := styles.Fonts.Font[xf.FontId]
+			style.Font.Size, _ = strconv.Atoi(xfont.Sz.Val)
+			style.Font.Name = xfont.Name.Val
+			style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
+			style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
+		}
+		styles.lock.Lock()
+		styles.styleCache[styleIndex] = style
+		styles.lock.Unlock()
+	}
+	return style
+
+}
+
+// Excel styles can reference number formats that are built-in, all of which
+// have an id less than 164. This is a possibly incomplete list comprised of as
+// many of them as I could find.
+func getBuiltinNumberFormat(numFmtId int) string {
+	switch numFmtId {
+	case 1:
+		return "0"
+	case 2:
+		return "0.00"
+	case 3:
+		return "#,##0"
+	case 4:
+		return "#,##0.00"
+	case 9:
+		return "0%"
+	case 10:
+		return "0.00%"
+	case 11:
+		return "0.00E+00"
+	case 12:
+		return "# ?/?"
+	case 13:
+		return "# ??/??"
+	case 14:
+		return "mm-dd-yy"
+	case 15:
+		return "d-mmm-yy"
+	case 16:
+		return "d-mmm"
+	case 17:
+		return "mmm-yy"
+	case 18:
+		return "h:mm AM/PM"
+	case 19:
+		return "h:mm:ss AM/PM"
+	case 20:
+		return "h:mm"
+	case 21:
+		return "h:mm:ss"
+	case 22:
+		return "m/d/yy h:mm"
+	case 37:
+		return "#,##0 ;(#,##0)"
+	case 39:
+		return "#,##0.00;(#,##0.00)"
+	case 40:
+		return "#,##0.00;[Red](#,##0.00)"
+	case 41:
+		return `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`
+	case 42:
+		return `_("$"* #,##0_);_("$* \(#,##0\);_("$"* "-"_);_(@_)`
+	case 43:
+		return `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`
+	case 44:
+		return `_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)`
+	case 45:
+		return "mm:ss"
+	case 46:
+		return "[h]:mm:ss"
+	case 47:
+		return "mmss.0"
+	case 48:
+		return "##0.0E+0"
+	case 49:
+		return "@"
+	}
+	return ""
+}
+
+func (styles *xlsxStyleSheet) getNumberFormat(styleIndex int) string {
+	if styles.CellXfs.Xf == nil || styles.numFmtRefTable == nil {
+		return ""
+	}
+	var numberFormat string = ""
+	if styleIndex > -1 && styleIndex <= styles.CellXfs.Count {
+		xf := styles.CellXfs.Xf[styleIndex]
+		if xf.NumFmtId < 164 {
+			return getBuiltinNumberFormat(xf.NumFmtId)
+		} else {
+			numFmt := styles.numFmtRefTable[xf.NumFmtId]
+			numberFormat = numFmt.FormatCode
+		}
+	}
+	return strings.ToLower(numberFormat)
+}
+
+func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
+	var font xlsxFont
+	if xFont.Name.Val == "" {
+		return 0
+	}
+	for index, font = range styles.Fonts.Font {
+		if font.Equals(xFont) {
+			return index
+		}
+	}
+	styles.Fonts.Font = append(styles.Fonts.Font, xFont)
+	index = styles.Fonts.Count
+	styles.Fonts.Count += 1
+	return
+}
+
+func (styles *xlsxStyleSheet) addFill(xFill xlsxFill) (index int) {
+	var fill xlsxFill
+	for index, fill = range styles.Fills.Fill {
+		if fill.Equals(xFill) {
+			return index
+		}
+	}
+	styles.Fills.Fill = append(styles.Fills.Fill, xFill)
+	index = styles.Fills.Count
+	styles.Fills.Count += 1
+	return
+}
+
+func (styles *xlsxStyleSheet) addBorder(xBorder xlsxBorder) (index int) {
+	var border xlsxBorder
+	for index, border = range styles.Borders.Border {
+		if border.Equals(xBorder) {
+			return index
+		}
+	}
+	styles.Borders.Border = append(styles.Borders.Border, xBorder)
+	index = styles.Borders.Count
+	styles.Borders.Count += 1
+	return
+}
+
+func (styles *xlsxStyleSheet) addCellStyleXf(xCellStyleXf xlsxXf) (index int) {
+	var cellStyleXf xlsxXf
+	for index, cellStyleXf = range styles.CellStyleXfs.Xf {
+		if cellStyleXf.Equals(xCellStyleXf) {
+			return index
+		}
+	}
+	styles.CellStyleXfs.Xf = append(styles.CellStyleXfs.Xf, xCellStyleXf)
+	index = styles.CellStyleXfs.Count
+	styles.CellStyleXfs.Count += 1
+	return
+}
+
+func (styles *xlsxStyleSheet) addCellXf(xCellXf xlsxXf) (index int) {
+	var cellXf xlsxXf
+	for index, cellXf = range styles.CellXfs.Xf {
+		if cellXf.Equals(xCellXf) {
+			return index
+		}
+	}
+
+	styles.CellXfs.Xf = append(styles.CellXfs.Xf, xCellXf)
+	index = styles.CellXfs.Count
+	styles.CellXfs.Count += 1
+	return
+}
+
+func (styles *xlsxStyleSheet) addNumFmt(xNumFmt xlsxNumFmt) (index int) {
+	numFmt, ok := styles.numFmtRefTable[xNumFmt.NumFmtId]
+	if !ok {
+		if styles.numFmtRefTable == nil {
+			styles.numFmtRefTable = make(map[int]xlsxNumFmt)
+		}
+		styles.NumFmts.NumFmt = append(styles.NumFmts.NumFmt, xNumFmt)
+		styles.numFmtRefTable[xNumFmt.NumFmtId] = xNumFmt
+		index = styles.NumFmts.Count
+		styles.NumFmts.Count += 1
+		return
+	}
+	numFmt.FormatCode = xNumFmt.FormatCode
+	return
+}
+
+func (styles *xlsxStyleSheet) Marshal() (result string, err error) {
+	var xNumFmts string
+	var xfonts string
+	var xfills string
+	var xborders string
+	var xcellStyleXfs string
+	var xcellXfs string
+
+	var outputFontMap map[int]int = make(map[int]int)
+	var outputFillMap map[int]int = make(map[int]int)
+	var outputBorderMap map[int]int = make(map[int]int)
+
+	result = xml.Header
+	result += `<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
+
+	xNumFmts, err = styles.NumFmts.Marshal()
+	if err != nil {
+		return
+	}
+	result += xNumFmts
+
+	xfonts, err = styles.Fonts.Marshal(outputFontMap)
+	if err != nil {
+		return
+	}
+	result += xfonts
+
+	xfills, err = styles.Fills.Marshal(outputFillMap)
+	if err != nil {
+		return
+	}
+	result += xfills
+
+	xborders, err = styles.Borders.Marshal(outputBorderMap)
+	if err != nil {
+		return
+	}
+	result += xborders
+
+	xcellStyleXfs, err = styles.CellStyleXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+	if err != nil {
+		return
+	}
+	result += xcellStyleXfs
+
+	xcellXfs, err = styles.CellXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+	if err != nil {
+		return
+	}
+	result += xcellXfs
+
+	result += `</styleSheet>`
+	return
 }
 
 // xlsxNumFmts directly maps the numFmts element in the namespace
@@ -37,13 +345,33 @@ type xlsxNumFmts struct {
 	NumFmt []xlsxNumFmt `xml:"numFmt,omitempty"`
 }
 
+func (numFmts *xlsxNumFmts) Marshal() (result string, err error) {
+	if numFmts.Count > 0 {
+		result = fmt.Sprintf(`<numFmts count="%d">`, numFmts.Count)
+		for _, numFmt := range numFmts.NumFmt {
+			var xNumFmt string
+			xNumFmt, err = numFmt.Marshal()
+			if err != nil {
+				return
+			}
+			result += xNumFmt
+		}
+		result += `</numFmts>`
+	}
+	return
+}
+
 // xlsxNumFmt directly maps the numFmt element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxNumFmt struct {
-	NumFmtId   int    `xml:"numFmtId,omitempty"`
-	FormatCode string `xml:"formatCode,omitempty"`
+	NumFmtId   int    `xml:"numFmtId,attr,omitempty"`
+	FormatCode string `xml:"formatCode,attr,omitempty"`
+}
+
+func (numFmt *xlsxNumFmt) Marshal() (result string, err error) {
+	return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, numFmt.FormatCode), nil
 }
 
 // xlsxFonts directly maps the fonts element in the namespace
@@ -51,10 +379,36 @@ type xlsxNumFmt struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxFonts struct {
+	XMLName xml.Name `xml:"fonts"`
+
 	Count int        `xml:"count,attr"`
 	Font  []xlsxFont `xml:"font,omitempty"`
 }
 
+func (fonts *xlsxFonts) Marshal(outputFontMap map[int]int) (result string, err error) {
+	emittedCount := 0
+	subparts := ""
+
+	for i, font := range fonts.Font {
+		var xfont string
+		xfont, err = font.Marshal()
+		if err != nil {
+			return
+		}
+		if xfont != "" {
+			outputFontMap[i] = emittedCount
+			emittedCount += 1
+			subparts += xfont
+		}
+	}
+	if emittedCount > 0 {
+		result = fmt.Sprintf(`<fonts count="%d">`, fonts.Count)
+		result += subparts
+		result += `</fonts>`
+	}
+	return
+}
+
 // xlsxFont directly maps the font element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -67,6 +421,31 @@ type xlsxFont struct {
 	Color   xlsxColor `xml:"color,omitempty"`
 }
 
+func (font *xlsxFont) Equals(other xlsxFont) bool {
+	return font.Sz.Equals(other.Sz) && font.Name.Equals(other.Name) && font.Family.Equals(other.Family) && font.Charset.Equals(other.Charset) && font.Color.Equals(other.Color)
+}
+
+func (font *xlsxFont) Marshal() (result string, err error) {
+	result = `<font>`
+	if font.Sz.Val != "" {
+		result += fmt.Sprintf(`<sz val="%s"/>`, font.Sz.Val)
+	}
+	if font.Name.Val != "" {
+		result += fmt.Sprintf(`<name val="%s"/>`, font.Name.Val)
+	}
+	if font.Family.Val != "" {
+		result += fmt.Sprintf(`<family val="%s"/>`, font.Family.Val)
+	}
+	if font.Charset.Val != "" {
+		result += fmt.Sprintf(`<charset val="%s"/>`, font.Charset.Val)
+	}
+	if font.Color.RGB != "" {
+		result += fmt.Sprintf(`<color rgb="%s"/>`, font.Color.RGB)
+	}
+	result += `</font>`
+	return
+}
+
 // xlsxVal directly maps the val element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -75,6 +454,10 @@ type xlsxVal struct {
 	Val string `xml:"val,attr,omitempty"`
 }
 
+func (val *xlsxVal) Equals(other xlsxVal) bool {
+	return val.Val == other.Val
+}
+
 // xlsxFills directly maps the fills element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -84,6 +467,29 @@ type xlsxFills struct {
 	Fill  []xlsxFill `xml:"fill,omitempty"`
 }
 
+func (fills *xlsxFills) Marshal(outputFillMap map[int]int) (result string, err error) {
+	emittedCount := 0
+	subparts := ""
+	for i, fill := range fills.Fill {
+		var xfill string
+		xfill, err = fill.Marshal()
+		if err != nil {
+			return
+		}
+		if xfill != "" {
+			outputFillMap[i] = emittedCount
+			emittedCount += 1
+			subparts += xfill
+		}
+	}
+	if emittedCount > 0 {
+		result = fmt.Sprintf(`<fills count="%d">`, emittedCount)
+		result += subparts
+		result += `</fills>`
+	}
+	return
+}
+
 // xlsxFill directly maps the fill element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -92,6 +498,25 @@ type xlsxFill struct {
 	PatternFill xlsxPatternFill `xml:"patternFill,omitempty"`
 }
 
+func (fill *xlsxFill) Equals(other xlsxFill) bool {
+	return fill.PatternFill.Equals(other.PatternFill)
+}
+
+func (fill *xlsxFill) Marshal() (result string, err error) {
+	if fill.PatternFill.PatternType != "" {
+		var xpatternFill string
+		result = `<fill>`
+
+		xpatternFill, err = fill.PatternFill.Marshal()
+		if err != nil {
+			return
+		}
+		result += xpatternFill
+		result += `</fill>`
+	}
+	return
+}
+
 // xlsxPatternFill directly maps the patternFill element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -102,6 +527,31 @@ type xlsxPatternFill struct {
 	BgColor     xlsxColor `xml:"bgColor,omitempty"`
 }
 
+func (patternFill *xlsxPatternFill) Equals(other xlsxPatternFill) bool {
+	return patternFill.PatternType == other.PatternType && patternFill.FgColor.Equals(other.FgColor) && patternFill.BgColor.Equals(other.BgColor)
+}
+
+func (patternFill *xlsxPatternFill) Marshal() (result string, err error) {
+	result = fmt.Sprintf(`<patternFill patternType="%s"`, patternFill.PatternType)
+	ending := `/>`
+	terminator := ""
+	subparts := ""
+	if patternFill.FgColor.RGB != "" {
+		ending = `>`
+		terminator = "</patternFill>"
+		subparts += fmt.Sprintf(`<fgColor rgb="%s"/>`, patternFill.FgColor.RGB)
+	}
+	if patternFill.BgColor.RGB != "" {
+		ending = `>`
+		terminator = "</patternFill>"
+		subparts += fmt.Sprintf(`<bgColor rgb="%s"/>`, patternFill.BgColor.RGB)
+	}
+	result += ending
+	result += subparts
+	result += terminator
+	return
+}
+
 // xlsxColor is a common mapping used for both the fgColor and bgColor
 // elements in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
@@ -111,6 +561,10 @@ type xlsxColor struct {
 	RGB string `xml:"rgb,attr,omitempty"`
 }
 
+func (color *xlsxColor) Equals(other xlsxColor) bool {
+	return color.RGB == other.RGB
+}
+
 // xlsxBorders directly maps the borders element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -120,6 +574,30 @@ type xlsxBorders struct {
 	Border []xlsxBorder `xml:"border,omitempty"`
 }
 
+func (borders *xlsxBorders) Marshal(outputBorderMap map[int]int) (result string, err error) {
+	result = ""
+	emittedCount := 0
+	subparts := ""
+	for i, border := range borders.Border {
+		var xborder string
+		xborder, err = border.Marshal()
+		if err != nil {
+			return
+		}
+		if xborder != "" {
+			outputBorderMap[i] = emittedCount
+			emittedCount += 1
+			subparts += xborder
+		}
+	}
+	if emittedCount > 0 {
+		result += fmt.Sprintf(`<borders count="%d">`, emittedCount)
+		result += subparts
+		result += `</borders>`
+	}
+	return
+}
+
 // xlsxBorder directly maps the border element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -131,6 +609,37 @@ type xlsxBorder struct {
 	Bottom xlsxLine `xml:"bottom,omitempty"`
 }
 
+func (border *xlsxBorder) Equals(other xlsxBorder) bool {
+	return border.Left.Equals(other.Left) && border.Right.Equals(other.Right) && border.Top.Equals(other.Top) && border.Bottom.Equals(other.Bottom)
+}
+
+func (border *xlsxBorder) Marshal() (result string, err error) {
+	emit := false
+	subparts := ""
+	if border.Left.Style != "" {
+		emit = true
+		subparts += fmt.Sprintf(`<left style="%s"/>`, border.Left.Style)
+	}
+	if border.Right.Style != "" {
+		emit = true
+		subparts += fmt.Sprintf(`<right style="%s"/>`, border.Right.Style)
+	}
+	if border.Top.Style != "" {
+		emit = true
+		subparts += fmt.Sprintf(`<top style="%s"/>`, border.Top.Style)
+	}
+	if border.Bottom.Style != "" {
+		emit = true
+		subparts += fmt.Sprintf(`<bottom style="%s"/>`, border.Bottom.Style)
+	}
+	if emit {
+		result += `<border>`
+		result += subparts
+		result += `</border>`
+	}
+	return
+}
+
 // xlsxLine directly maps the line style element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -139,6 +648,10 @@ type xlsxLine struct {
 	Style string `xml:"style,attr,omitempty"`
 }
 
+func (line *xlsxLine) Equals(other xlsxLine) bool {
+	return line.Style == other.Style
+}
+
 // xlsxCellStyleXfs directly maps the cellStyleXfs element in the
 // namespace http://schemas.openxmlformats.org/spreadsheetml/2006/main
 // - currently I have not checked it for completeness - it does as
@@ -148,6 +661,22 @@ type xlsxCellStyleXfs struct {
 	Xf    []xlsxXf `xml:"xf,omitempty"`
 }
 
+func (cellStyleXfs *xlsxCellStyleXfs) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
+	if cellStyleXfs.Count > 0 {
+		result = fmt.Sprintf(`<cellStyleXfs count="%d">`, cellStyleXfs.Count)
+		for _, xf := range cellStyleXfs.Xf {
+			var xxf string
+			xxf, err = xf.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+			if err != nil {
+				return
+			}
+			result += xxf
+		}
+		result += `</cellStyleXfs>`
+	}
+	return
+}
+
 // xlsxCellXfs directly maps the cellXfs element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -157,6 +686,22 @@ type xlsxCellXfs struct {
 	Xf    []xlsxXf `xml:"xf,omitempty"`
 }
 
+func (cellXfs *xlsxCellXfs) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
+	if cellXfs.Count > 0 {
+		result = fmt.Sprintf(`<cellXfs count="%d">`, cellXfs.Count)
+		for _, xf := range cellXfs.Xf {
+			var xxf string
+			xxf, err = xf.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+			if err != nil {
+				return
+			}
+			result += xxf
+		}
+		result += `</cellXfs>`
+	}
+	return
+}
+
 // xlsxXf directly maps the xf element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -171,7 +716,32 @@ type xlsxXf struct {
 	FillId          int           `xml:"fillId,attr"`
 	FontId          int           `xml:"fontId,attr"`
 	NumFmtId        int           `xml:"numFmtId,attr"`
-	alignment       xlsxAlignment `xml:"alignment"`
+	Alignment       xlsxAlignment `xml:"alignment"`
+}
+
+func (xf *xlsxXf) Equals(other xlsxXf) bool {
+	return xf.ApplyAlignment == other.ApplyAlignment &&
+		xf.ApplyBorder == other.ApplyBorder &&
+		xf.ApplyFont == other.ApplyFont &&
+		xf.ApplyFill == other.ApplyFill &&
+		xf.ApplyProtection == other.ApplyProtection &&
+		xf.BorderId == other.BorderId &&
+		xf.FillId == other.FillId &&
+		xf.FontId == other.FontId &&
+		xf.NumFmtId == other.NumFmtId &&
+		xf.Alignment.Equals(other.Alignment)
+}
+
+func (xf *xlsxXf) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
+	var xAlignment string
+	result = fmt.Sprintf(`<xf applyAlignment="%b" applyBorder="%b" applyFont="%b" applyFill="%b" applyProtection="%b" borderId="%d" fillId="%d" fontId="%d" numFmtId="%d">`, bool2Int(xf.ApplyAlignment), bool2Int(xf.ApplyBorder), bool2Int(xf.ApplyFont), bool2Int(xf.ApplyFill), bool2Int(xf.ApplyProtection), outputBorderMap[xf.BorderId], outputFillMap[xf.FillId], outputFontMap[xf.FontId], xf.NumFmtId)
+	xAlignment, err = xf.Alignment.Marshal()
+	if err != nil {
+		return
+	}
+	result += xAlignment
+	result += `</xf>`
+	return
 }
 
 type xlsxAlignment struct {
@@ -183,99 +753,23 @@ type xlsxAlignment struct {
 	WrapText     bool   `xml:"wrapText,attr"`
 }
 
-func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style Style) {
-	var styleXf xlsxXf
-	style = Style{}
-	style.Border = Border{}
-	style.Fill = Fill{}
-	style.Font = Font{}
-
-	xfCount := styles.CellXfs.Count
-	if styleIndex > -1 && xfCount > 0 && styleIndex <= xfCount {
-		xf := styles.CellXfs.Xf[styleIndex]
-
-		// Google docs can produce output that has fewer
-		// CellStyleXfs than CellXfs - this copes with that.
-		if styleIndex < styles.CellStyleXfs.Count {
-			styleXf = styles.CellStyleXfs.Xf[styleIndex]
-		} else {
-			styleXf = xlsxXf{}
-		}
-
-		style.ApplyBorder = xf.ApplyBorder || styleXf.ApplyBorder
-		style.ApplyFill = xf.ApplyFill || styleXf.ApplyFill
-		style.ApplyFont = xf.ApplyFont || styleXf.ApplyFont
-
-		if xf.BorderId > -1 && xf.BorderId < styles.Borders.Count {
-			style.Border.Left = styles.Borders.Border[xf.BorderId].Left.Style
-			style.Border.Right = styles.Borders.Border[xf.BorderId].Right.Style
-			style.Border.Top = styles.Borders.Border[xf.BorderId].Top.Style
-			style.Border.Bottom = styles.Borders.Border[xf.BorderId].Bottom.Style
-		}
-
-		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
-			xFill := styles.Fills.Fill[xf.FillId]
-			style.Fill.PatternType = xFill.PatternFill.PatternType
-			style.Fill.FgColor = xFill.PatternFill.FgColor.RGB
-			style.Fill.BgColor = xFill.PatternFill.BgColor.RGB
-		}
-
-		if xf.FontId > -1 && xf.FontId < styles.Fonts.Count {
-			xfont := styles.Fonts.Font[xf.FontId]
-			style.Font.Size, _ = strconv.Atoi(xfont.Sz.Val)
-			style.Font.Name = xfont.Name.Val
-			style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
-			style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
-		}
-	}
-	return style
-
-}
-
-func (styles *xlsxStyleSheet) getNumberFormat(styleIndex int, numFmtRefTable map[int]xlsxNumFmt) string {
-	if styles.CellXfs.Xf == nil {
-		return ""
-	}
-	var numberFormat string = ""
-	if styleIndex > -1 && styleIndex <= styles.CellXfs.Count {
-		xf := styles.CellXfs.Xf[styleIndex]
-		numFmt := numFmtRefTable[xf.NumFmtId]
-		numberFormat = numFmt.FormatCode
-	}
-	return strings.ToLower(numberFormat)
+func (alignment *xlsxAlignment) Equals(other xlsxAlignment) bool {
+	return alignment.Horizontal == other.Horizontal &&
+		alignment.Indent == other.Indent &&
+		alignment.ShrinkToFit == other.ShrinkToFit &&
+		alignment.TextRotation == other.TextRotation &&
+		alignment.Vertical == other.Vertical &&
+		alignment.WrapText == other.WrapText
 }
 
-func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
-	styles.Fonts.Font = append(styles.Fonts.Font, xFont)
-	index = styles.Fonts.Count
-	styles.Fonts.Count += 1
+func (alignment *xlsxAlignment) Marshal() (result string, err error) {
+	result = fmt.Sprintf(`<alignment horizontal="%s" indent="%d" shrinkToFit="%b" textRotation="%d" vertical="%s" wrapText="%b"/>`, alignment.Horizontal, alignment.Indent, bool2Int(alignment.ShrinkToFit), alignment.TextRotation, alignment.Vertical, bool2Int(alignment.WrapText))
 	return
 }
 
-func (styles *xlsxStyleSheet) addFill(xFill xlsxFill) (index int) {
-	styles.Fills.Fill = append(styles.Fills.Fill, xFill)
-	index = styles.Fills.Count
-	styles.Fills.Count += 1
-	return
-}
-
-func (styles *xlsxStyleSheet) addBorder(xBorder xlsxBorder) (index int) {
-	styles.Borders.Border = append(styles.Borders.Border, xBorder)
-	index = styles.Borders.Count
-	styles.Borders.Count += 1
-	return
-}
-
-func (styles *xlsxStyleSheet) addCellStyleXf(xCellStyleXf xlsxXf) (index int) {
-	styles.CellStyleXfs.Xf = append(styles.CellStyleXfs.Xf, xCellStyleXf)
-	index = styles.CellStyleXfs.Count
-	styles.CellStyleXfs.Count += 1
-	return
-}
-
-func (styles *xlsxStyleSheet) addCellXf(xCellXf xlsxXf) (index int) {
-	styles.CellXfs.Xf = append(styles.CellXfs.Xf, xCellXf)
-	index = styles.CellXfs.Count
-	styles.CellXfs.Count += 1
-	return
+func bool2Int(b bool) int {
+	if b {
+		return 1
+	}
+	return 0
 }

+ 285 - 0
xmlStyle_test.go

@@ -0,0 +1,285 @@
+package xlsx
+
+import (
+	. "gopkg.in/check.v1"
+)
+
+type XMLStyleSuite struct{}
+
+var _ = Suite(&XMLStyleSuite{})
+
+// Test we produce valid output for an empty style file.
+func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
+	styles := newXlsxStyleSheet()
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"></styleSheet>`)
+}
+
+// Test we produce valid output for a style file with one font definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
+	styles := newXlsxStyleSheet()
+	styles.Fonts = xlsxFonts{}
+	styles.Fonts.Count = 1
+	styles.Fonts.Font = make([]xlsxFont, 1)
+	font := xlsxFont{}
+	font.Sz.Val = "10"
+	font.Name.Val = "Andale Mono"
+	styles.Fonts.Font[0] = font
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="10"/><name val="Andale Mono"/></font></fonts></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+// Test we produce valid output for a style file with one fill definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
+	styles := newXlsxStyleSheet()
+	styles.Fills = xlsxFills{}
+	styles.Fills.Count = 1
+	styles.Fills.Fill = make([]xlsxFill, 1)
+	fill := xlsxFill{}
+	patternFill := xlsxPatternFill{
+		PatternType: "solid",
+		FgColor:     xlsxColor{RGB: "#FFFFFF"},
+		BgColor:     xlsxColor{RGB: "#000000"}}
+	fill.PatternFill = patternFill
+	styles.Fills.Fill[0] = fill
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fills count="1"><fill><patternFill patternType="solid"><fgColor rgb="#FFFFFF"/><bgColor rgb="#000000"/></patternFill></fill></fills></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+// Test we produce valid output for a style file with one border definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
+	styles := newXlsxStyleSheet()
+	styles.Borders = xlsxBorders{}
+	styles.Borders.Count = 1
+	styles.Borders.Border = make([]xlsxBorder, 1)
+	border := xlsxBorder{}
+	border.Left.Style = "solid"
+	border.Top.Style = "none"
+	styles.Borders.Border[0] = border
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"/><top style="none"/></border></borders></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+// Test we produce valid output for a style file with one cellStyleXf definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
+	styles := newXlsxStyleSheet()
+	styles.CellStyleXfs = xlsxCellStyleXfs{}
+	styles.CellStyleXfs.Count = 1
+	styles.CellStyleXfs.Xf = make([]xlsxXf, 1)
+	xf := xlsxXf{}
+	xf.ApplyAlignment = true
+	xf.ApplyBorder = true
+	xf.ApplyFont = true
+	xf.ApplyFill = true
+	xf.ApplyProtection = true
+	xf.BorderId = 0
+	xf.FillId = 0
+	xf.FontId = 0
+	xf.NumFmtId = 0
+	xf.Alignment = xlsxAlignment{
+		Horizontal:   "left",
+		Indent:       1,
+		ShrinkToFit:  true,
+		TextRotation: 0,
+		Vertical:     "middle",
+		WrapText:     false}
+	styles.CellStyleXfs.Xf[0] = xf
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><cellStyleXfs count="1"><xf applyAlignment="1" applyBorder="1" applyFont="1" applyFill="1" applyProtection="1" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="left" indent="1" shrinkToFit="1" textRotation="0" vertical="middle" wrapText="0"/></xf></cellStyleXfs></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+// Test we produce valid output for a style file with one cellXf
+// definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellXf(c *C) {
+	styles := newXlsxStyleSheet()
+	styles.CellXfs = xlsxCellXfs{}
+	styles.CellXfs.Count = 1
+	styles.CellXfs.Xf = make([]xlsxXf, 1)
+	xf := xlsxXf{}
+	xf.ApplyAlignment = true
+	xf.ApplyBorder = true
+	xf.ApplyFont = true
+	xf.ApplyFill = true
+	xf.ApplyProtection = true
+	xf.BorderId = 0
+	xf.FillId = 0
+	xf.FontId = 0
+	xf.NumFmtId = 0
+	xf.Alignment = xlsxAlignment{
+		Horizontal:   "left",
+		Indent:       1,
+		ShrinkToFit:  true,
+		TextRotation: 0,
+		Vertical:     "middle",
+		WrapText:     false}
+	styles.CellXfs.Xf[0] = xf
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><cellXfs count="1"><xf applyAlignment="1" applyBorder="1" applyFont="1" applyFill="1" applyProtection="1" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="left" indent="1" shrinkToFit="1" textRotation="0" vertical="middle" wrapText="0"/></xf></cellXfs></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+// Test we produce valid output for a style file with one NumFmt
+// definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithANumFmt(c *C) {
+	styles := &xlsxStyleSheet{}
+	styles.NumFmts = xlsxNumFmts{}
+	styles.NumFmts.NumFmt = make([]xlsxNumFmt, 0)
+	numFmt := xlsxNumFmt{NumFmtId: 164, FormatCode: "GENERAL"}
+	styles.addNumFmt(numFmt)
+
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><numFmts count="1"><numFmt numFmtId="164" formatCode="GENERAL"/></numFmts></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
+func (x *XMLStyleSuite) TestFontEquals(c *C) {
+	fontA := xlsxFont{Sz: xlsxVal{Val: "11"},
+		Color:  xlsxColor{RGB: "FFFF0000"},
+		Name:   xlsxVal{Val: "Calibri"},
+		Family: xlsxVal{Val: "2"}}
+	fontB := xlsxFont{Sz: xlsxVal{Val: "11"},
+		Color:  xlsxColor{RGB: "FFFF0000"},
+		Name:   xlsxVal{Val: "Calibri"},
+		Family: xlsxVal{Val: "2"}}
+
+	c.Assert(fontA.Equals(fontB), Equals, true)
+	fontB.Sz.Val = "12"
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.Sz.Val = "11"
+	fontB.Color.RGB = "12345678"
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.Color.RGB = "FFFF0000"
+	fontB.Name.Val = "Arial"
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.Name.Val = "Calibri"
+	fontB.Family.Val = "1"
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.Family.Val = "2"
+	// For sanity
+	c.Assert(fontA.Equals(fontB), Equals, true)
+}
+
+func (x *XMLStyleSuite) TestFillEquals(c *C) {
+	fillA := xlsxFill{PatternFill: xlsxPatternFill{
+		PatternType: "solid",
+		FgColor:     xlsxColor{RGB: "FFFF0000"},
+		BgColor:     xlsxColor{RGB: "0000FFFF"}}}
+	fillB := xlsxFill{PatternFill: xlsxPatternFill{
+		PatternType: "solid",
+		FgColor:     xlsxColor{RGB: "FFFF0000"},
+		BgColor:     xlsxColor{RGB: "0000FFFF"}}}
+	c.Assert(fillA.Equals(fillB), Equals, true)
+	fillB.PatternFill.PatternType = "gray125"
+	c.Assert(fillA.Equals(fillB), Equals, false)
+	fillB.PatternFill.PatternType = "solid"
+	fillB.PatternFill.FgColor.RGB = "00FF00FF"
+	c.Assert(fillA.Equals(fillB), Equals, false)
+	fillB.PatternFill.FgColor.RGB = "FFFF0000"
+	fillB.PatternFill.BgColor.RGB = "12456789"
+	c.Assert(fillA.Equals(fillB), Equals, false)
+	fillB.PatternFill.BgColor.RGB = "0000FFFF"
+	// For sanity
+	c.Assert(fillA.Equals(fillB), Equals, true)
+}
+
+func (x *XMLStyleSuite) TestBorderEquals(c *C) {
+	borderA := xlsxBorder{Left: xlsxLine{Style: "none"},
+		Right:  xlsxLine{Style: "none"},
+		Top:    xlsxLine{Style: "none"},
+		Bottom: xlsxLine{Style: "none"}}
+	borderB := xlsxBorder{Left: xlsxLine{Style: "none"},
+		Right:  xlsxLine{Style: "none"},
+		Top:    xlsxLine{Style: "none"},
+		Bottom: xlsxLine{Style: "none"}}
+	c.Assert(borderA.Equals(borderB), Equals, true)
+	borderB.Left.Style = "thin"
+	c.Assert(borderA.Equals(borderB), Equals, false)
+	borderB.Left.Style = "none"
+	borderB.Right.Style = "thin"
+	c.Assert(borderA.Equals(borderB), Equals, false)
+	borderB.Right.Style = "none"
+	borderB.Top.Style = "thin"
+	c.Assert(borderA.Equals(borderB), Equals, false)
+	borderB.Top.Style = "none"
+	borderB.Bottom.Style = "thin"
+	c.Assert(borderA.Equals(borderB), Equals, false)
+	borderB.Bottom.Style = "none"
+	// for sanity
+	c.Assert(borderA.Equals(borderB), Equals, true)
+}
+
+func (x *XMLStyleSuite) TestXfEquals(c *C) {
+	xfA := xlsxXf{
+		ApplyAlignment:  true,
+		ApplyBorder:     true,
+		ApplyFont:       true,
+		ApplyFill:       true,
+		ApplyProtection: true,
+		BorderId:        0,
+		FillId:          0,
+		FontId:          0,
+		NumFmtId:        0}
+	xfB := xlsxXf{
+		ApplyAlignment:  true,
+		ApplyBorder:     true,
+		ApplyFont:       true,
+		ApplyFill:       true,
+		ApplyProtection: true,
+		BorderId:        0,
+		FillId:          0,
+		FontId:          0,
+		NumFmtId:        0}
+	c.Assert(xfA.Equals(xfB), Equals, true)
+	xfB.ApplyAlignment = false
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.ApplyAlignment = true
+	xfB.ApplyBorder = false
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.ApplyBorder = true
+	xfB.ApplyFont = false
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.ApplyFont = true
+	xfB.ApplyFill = false
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.ApplyFill = true
+	xfB.ApplyProtection = false
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.ApplyProtection = true
+	xfB.BorderId = 1
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.BorderId = 0
+	xfB.FillId = 1
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.FillId = 0
+	xfB.FontId = 1
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.FontId = 0
+	xfB.NumFmtId = 1
+	c.Assert(xfA.Equals(xfB), Equals, false)
+	xfB.NumFmtId = 0
+	// for sanity
+	c.Assert(xfA.Equals(xfB), Equals, true)
+}

+ 25 - 7
xmlWorkbook.go

@@ -7,6 +7,14 @@ import (
 	"io"
 )
 
+const (
+	// sheet state values as defined by
+	// http://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetstatevalues.aspx
+	sheetStateVisible    = "visible"
+	sheetStateHidden     = "hidden"
+	sheetStateVeryHidden = "veryHidden"
+)
+
 // xmlxWorkbookRels contains xmlxWorkbookRelations
 // which maps sheet id and sheet XML
 type xlsxWorkbookRels struct {
@@ -26,13 +34,22 @@ type xlsxWorkbookRelation struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxWorkbook struct {
-	XMLName      xml.Name         `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"`
-	FileVersion  xlsxFileVersion  `xml:"fileVersion"`
-	WorkbookPr   xlsxWorkbookPr   `xml:"workbookPr"`
-	BookViews    xlsxBookViews    `xml:"bookViews"`
-	Sheets       xlsxSheets       `xml:"sheets"`
-	DefinedNames xlsxDefinedNames `xml:"definedNames"`
-	CalcPr       xlsxCalcPr       `xml:"calcPr"`
+	XMLName            xml.Name               `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main workbook"`
+	FileVersion        xlsxFileVersion        `xml:"fileVersion"`
+	WorkbookPr         xlsxWorkbookPr         `xml:"workbookPr"`
+	WorkbookProtection xlsxWorkbookProtection `xml:"workbookProtection"`
+	BookViews          xlsxBookViews          `xml:"bookViews"`
+	Sheets             xlsxSheets             `xml:"sheets"`
+	DefinedNames       xlsxDefinedNames       `xml:"definedNames"`
+	CalcPr             xlsxCalcPr             `xml:"calcPr"`
+}
+
+// xlsxWorkbookProtection directly maps the workbookProtection element from the
+// namespace http://schemas.openxmlformats.org/spreadsheetml/2006/main
+// - currently I have not checked it for completeness - it does as
+// much as I need.
+type xlsxWorkbookProtection struct {
+	// We don't need this, yet.
 }
 
 // xlsxFileVersion directly maps the fileVersion element from the
@@ -98,6 +115,7 @@ type xlsxSheet struct {
 	Name    string `xml:"name,attr,omitempty"`
 	SheetId string `xml:"sheetId,attr,omitempty"`
 	Id      string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
+	State   string `xml:"state,attr,omitempty"`
 }
 
 // xlsxDefinedNames directly maps the definedNames element from the

+ 10 - 16
xmlWorkbook_test.go

@@ -3,6 +3,7 @@ package xlsx
 import (
 	"bytes"
 	"encoding/xml"
+
 	. "gopkg.in/check.v1"
 )
 
@@ -34,13 +35,16 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
           <sheets>
             <sheet name="Sheet1"
                    sheetId="1"
-                   r:id="rId1"/>
+                   r:id="rId1"
+                   state="visible"/>
             <sheet name="Sheet2"
                    sheetId="2"
-                   r:id="rId2"/>
+                   r:id="rId2"
+                   state="hidden"/>
             <sheet name="Sheet3"
                    sheetId="3"
-                   r:id="rId3"/>
+                   r:id="rId3"
+                   state="veryHidden"/>
           </sheets>
           <definedNames>
             <definedName name="monitors"
@@ -69,6 +73,7 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
 	c.Assert(sheet.Id, Equals, "rId1")
 	c.Assert(sheet.Name, Equals, "Sheet1")
 	c.Assert(sheet.SheetId, Equals, "1")
+	c.Assert(sheet.State, Equals, "visible")
 	c.Assert(workbook.DefinedNames.DefinedName, HasLen, 1)
 	dname := workbook.DefinedNames.DefinedName[0]
 	c.Assert(dname.Data, Equals, "Sheet1!$A$1533")
@@ -91,19 +96,8 @@ func (w *WorkbookSuite) TestMarshallWorkbook(c *C) {
 	workbook.Sheets.Sheet = make([]xlsxSheet, 1)
 	workbook.Sheets.Sheet[0] = xlsxSheet{Name: "sheet1", SheetId: "1", Id: "rId2"}
 
-	body, err := xml.MarshalIndent(workbook, "  ", "  ")
+	body, err := xml.Marshal(workbook)
 	c.Assert(err, IsNil)
-	expectedWorkbook := `  <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
-    <fileVersion appName="xlsx"></fileVersion>
-    <workbookPr date1904="false"></workbookPr>
-    <bookViews>
-      <workbookView></workbookView>
-    </bookViews>
-    <sheets>
-      <sheet name="sheet1" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2"></sheet>
-    </sheets>
-    <definedNames></definedNames>
-    <calcPr></calcPr>
-  </workbook>`
+	expectedWorkbook := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fileVersion appName="xlsx"></fileVersion><workbookPr date1904="false"></workbookPr><workbookProtection></workbookProtection><bookViews><workbookView></workbookView></bookViews><sheets><sheet name="sheet1" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2"></sheet></sheets><definedNames></definedNames><calcPr></calcPr></workbook>`
 	c.Assert(string(body), Equals, expectedWorkbook)
 }

+ 229 - 12
xmlWorksheet.go

@@ -9,10 +9,159 @@ 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"`
-	Dimension xlsxDimension `xml:"dimension"`
-	Cols      xlsxCols      `xml:"cols,omitempty"`
-	SheetData xlsxSheetData `xml:"sheetData"`
+	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"`
+	SheetData     xlsxSheetData     `xml:"sheetData"`
+	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
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxHeaderFooter struct {
+	DifferentFirst   bool            `xml:"differentFirst,attr"`
+	DifferentOddEven bool            `xml:"differentOddEven,attr"`
+	OddHeader        []xlsxOddHeader `xml:"oddHeader"`
+	OddFooter        []xlsxOddFooter `xml:"oddFooter"`
+}
+
+// xlsxOddHeader directly maps the oddHeader element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxOddHeader struct {
+	Content string `xml:",chardata"`
+}
+
+// xlsxOddFooter directly maps the oddFooter element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxOddFooter struct {
+	Content string `xml:",chardata"`
+}
+
+// xlsxPageSetUp directly maps the pageSetup element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxPageSetUp struct {
+	PaperSize          string  `xml:"paperSize,attr"`
+	Scale              int     `xml:"scale,attr"`
+	FirstPageNumber    int     `xml:"firstPageNumber,attr"`
+	FitToWidth         int     `xml:"fitToWidth,attr"`
+	FitToHeight        int     `xml:"fitToHeight,attr"`
+	PageOrder          string  `xml:"pageOrder,attr"`
+	Orientation        string  `xml:"orientation,attr"`
+	UsePrinterDefaults bool    `xml:"usePrinterDefaults,attr"`
+	BlackAndWhite      bool    `xml:"blackAndWhite,attr"`
+	Draft              bool    `xml:"draft,attr"`
+	CellComments       string  `xml:"cellComments,attr"`
+	UseFirstPageNumber bool    `xml:"useFirstPageNumber,attr"`
+	HorizontalDPI      float32 `xml:"horizontalDpi,attr"`
+	VerticalDPI        float32 `xml:"verticalDpi,attr"`
+	Copies             int     `xml:"copies,attr"`
+}
+
+// xlsxPrintOptions directly maps the printOptions element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxPrintOptions struct {
+	Headings           bool `xml:"headings,attr"`
+	GridLines          bool `xml:"gridLines,attr"`
+	GridLinesSet       bool `xml:"gridLinesSet,attr"`
+	HorizontalCentered bool `xml:"horizontalCentered,attr"`
+	VerticalCentered   bool `xml:"verticalCentered,attr"`
+}
+
+// xlsxPageMargins directly maps the pageMargins element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxPageMargins struct {
+	Left   float64 `xml:"left,attr"`
+	Right  float64 `xml:"right,attr"`
+	Top    float64 `xml:"top,attr"`
+	Bottom float64 `xml:"bottom,attr"`
+	Header float64 `xml:"header,attr"`
+	Footer float64 `xml:"footer,attr"`
+}
+
+// xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSheetFormatPr struct {
+	DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
+}
+
+// xlsxSheetViews directly maps the sheetViews element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSheetViews struct {
+	SheetView []xlsxSheetView `xml:"sheetView"`
+}
+
+// xlsxSheetView directly maps the sheetView element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSheetView struct {
+	WindowProtection        bool            `xml:"windowProtection,attr"`
+	ShowFormulas            bool            `xml:"showFormulas,attr"`
+	ShowGridLines           bool            `xml:"showGridLines,attr"`
+	ShowRowColHeaders       bool            `xml:"showRowColHeaders,attr"`
+	ShowZeros               bool            `xml:"showZeros,attr"`
+	RightToLeft             bool            `xml:"rightToLeft,attr"`
+	TabSelected             bool            `xml:"tabSelected,attr"`
+	ShowOutlineSymbols      bool            `xml:"showOutlineSymbols,attr"`
+	DefaultGridColor        bool            `xml:"defaultGridColor,attr"`
+	View                    string          `xml:"view,attr"`
+	TopLeftCell             string          `xml:"topLeftCell,attr"`
+	ColorId                 int             `xml:"colorId,attr"`
+	ZoomScale               float64         `xml:"zoomScale,attr"`
+	ZoomScaleNormal         float64         `xml:"zoomScaleNormal,attr"`
+	ZoomScalePageLayoutView float64         `xml:"zoomScalePageLayoutView,attr"`
+	WorkbookViewId          int             `xml:"workbookViewId,attr"`
+	Selection               []xlsxSelection `xml:"selection"`
+}
+
+// xlsxSelection directly maps the selection element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSelection struct {
+	Pane         string `xml:"pane,attr"`
+	ActiveCell   string `xml:"activeCell,attr"`
+	ActiveCellId int    `xml:"activeCellId,attr"`
+	SQRef        string `xml:"sqref,attr"`
+}
+
+// xlsxSheetPr directly maps the sheetPr element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSheetPr struct {
+	FilterMode  bool              `xml:"filterMode,attr"`
+	PageSetUpPr []xlsxPageSetUpPr `xml:"pageSetUpPr"`
+}
+
+// xlsxPageSetUpPr directly maps the pageSetupPr element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxPageSetUpPr struct {
+	FitToPage bool `xml:"fitToPage,attr"`
 }
 
 // xlsxCols directly maps the cols element in the namespace
@@ -28,10 +177,12 @@ type xlsxCols struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxCol struct {
-	Min    int     `xml:"min,attr"`
-	Max    int     `xml:"max,attr"`
-	Hidden bool    `xml:"hidden,attr,omitempty"`
-	Width  float64 `xml:"width,attr,omitempty"`
+	Collapsed bool `xml:"collapsed,attr"`
+	Hidden    bool `xml:"hidden,attr"`
+	Max       int  `xml:"max,attr"`
+	Min       int  `xml:"min,attr"`
+	// Style     int     `xml:"style,attr"`
+	Width float64 `xml:"width,attr"`
 }
 
 // xlsxDimension directly maps the dimension element in the namespace
@@ -67,8 +218,74 @@ type xlsxRow struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxC struct {
-	R string `xml:"r,attr"` // Cell ID, e.g. A1
-	S int    `xml:"s,attr"` // Style reference.
-	T string `xml:"t,attr"` // Type.
-	V string `xml:"v"`      // Value
+	R string `xml:"r,attr"`           // Cell ID, e.g. A1
+	S int    `xml:"s,attr"`           // Style reference.
+	T string `xml:"t,attr,omitempty"` // Type.
+	V string `xml:"v"`                // Value
+	F string `xml:"f,omitempty"`      // Formula
+}
+
+// Create a new XLSX Worksheet with default values populated.
+// Strictly for internal use only!
+func newXlsxWorksheet() (worksheet *xlsxWorksheet) {
+	worksheet = &xlsxWorksheet{}
+	worksheet.SheetPr.FilterMode = false
+	worksheet.SheetPr.PageSetUpPr = make([]xlsxPageSetUpPr, 1)
+	worksheet.SheetPr.PageSetUpPr[0] = xlsxPageSetUpPr{FitToPage: false}
+	worksheet.SheetViews.SheetView = make([]xlsxSheetView, 1)
+	worksheet.SheetViews.SheetView[0] = xlsxSheetView{
+		ColorId:                 64,
+		DefaultGridColor:        true,
+		RightToLeft:             false,
+		Selection:               make([]xlsxSelection, 1),
+		ShowFormulas:            false,
+		ShowGridLines:           true,
+		ShowOutlineSymbols:      true,
+		ShowRowColHeaders:       true,
+		ShowZeros:               true,
+		TabSelected:             true,
+		TopLeftCell:             "A1",
+		View:                    "normal",
+		WindowProtection:        false,
+		WorkbookViewId:          0,
+		ZoomScale:               100,
+		ZoomScaleNormal:         100,
+		ZoomScalePageLayoutView: 100}
+	worksheet.SheetViews.SheetView[0].Selection[0] = xlsxSelection{
+		Pane:         "topLeft",
+		ActiveCell:   "A1",
+		ActiveCellId: 0,
+		SQRef:        "A1"}
+	worksheet.SheetFormatPr.DefaultRowHeight = 12.85
+	worksheet.PrintOptions.Headings = false
+	worksheet.PrintOptions.GridLines = false
+	worksheet.PrintOptions.GridLinesSet = true
+	worksheet.PrintOptions.HorizontalCentered = false
+	worksheet.PrintOptions.VerticalCentered = false
+	worksheet.PageMargins.Left = 0.7875
+	worksheet.PageMargins.Right = 0.7875
+	worksheet.PageMargins.Top = 1.05277777777778
+	worksheet.PageMargins.Bottom = 1.05277777777778
+	worksheet.PageMargins.Header = 0.7875
+	worksheet.PageMargins.Footer = 0.7875
+	worksheet.PageSetUp.PaperSize = "9"
+	worksheet.PageSetUp.Scale = 100
+	worksheet.PageSetUp.FirstPageNumber = 1
+	worksheet.PageSetUp.FitToWidth = 1
+	worksheet.PageSetUp.FitToHeight = 1
+	worksheet.PageSetUp.PageOrder = "downThenOver"
+	worksheet.PageSetUp.Orientation = "portrait"
+	worksheet.PageSetUp.UsePrinterDefaults = false
+	worksheet.PageSetUp.BlackAndWhite = false
+	worksheet.PageSetUp.Draft = false
+	worksheet.PageSetUp.CellComments = "none"
+	worksheet.PageSetUp.UseFirstPageNumber = true
+	worksheet.PageSetUp.HorizontalDPI = 300
+	worksheet.PageSetUp.VerticalDPI = 300
+	worksheet.PageSetUp.Copies = 1
+	worksheet.HeaderFooter.OddHeader = make([]xlsxOddHeader, 1)
+	worksheet.HeaderFooter.OddHeader[0] = xlsxOddHeader{Content: `&C&"Times New Roman,Regular"&12&A`}
+	worksheet.HeaderFooter.OddFooter = make([]xlsxOddFooter, 1)
+	worksheet.HeaderFooter.OddFooter[0] = xlsxOddFooter{Content: `&C&"Times New Roman,Regular"&12Page &P`}
+	return
 }