Browse Source

Get latest revision "github.com/tealeg/xlsx"

xormplus 7 years ago
parent
commit
955c21fb34

+ 84 - 164
vendor/github.com/tealeg/xlsx/cell.go

@@ -4,38 +4,55 @@ import (
 	"fmt"
 	"fmt"
 	"math"
 	"math"
 	"strconv"
 	"strconv"
-	"strings"
 	"time"
 	"time"
 )
 )
 
 
+const (
+	maxNonScientificNumber = 1e11
+	minNonScientificNumber = 1e-9
+)
+
 // CellType is an int type for storing metadata about the data type in the cell.
 // CellType is an int type for storing metadata about the data type in the cell.
 type CellType int
 type CellType int
 
 
-// Known types for cell values.
+// These are the cell types from the ST_CellType spec
 const (
 const (
 	CellTypeString CellType = iota
 	CellTypeString CellType = iota
-	CellTypeFormula
+	// CellTypeStringFormula is a specific format for formulas that return string values. Formulas that return numbers
+	// and booleans are stored as those types.
+	CellTypeStringFormula
 	CellTypeNumeric
 	CellTypeNumeric
 	CellTypeBool
 	CellTypeBool
+	// CellTypeInline is not respected on save, all inline string cells will be saved as SharedStrings
+	// when saving to an XLSX file. This the same behavior as that found in Excel.
 	CellTypeInline
 	CellTypeInline
 	CellTypeError
 	CellTypeError
+	// d (Date): Cell contains a date in the ISO 8601 format.
+	// That is the only mention of this format in the XLSX spec.
+	// Date seems to be unused by the current version of Excel, it stores dates as Numeric cells with a date format string.
+	// For now these cells will have their value output directly. It is unclear if the value is supposed to be parsed
+	// into a number and then formatted using the formatting or not.
 	CellTypeDate
 	CellTypeDate
-	CellTypeGeneral
 )
 )
 
 
+func (ct CellType) Ptr() *CellType {
+	return &ct
+}
+
 // Cell is a high level structure intended to provide user access to
 // Cell is a high level structure intended to provide user access to
 // the contents of Cell within an xlsx.Row.
 // the contents of Cell within an xlsx.Row.
 type Cell struct {
 type Cell struct {
-	Row      *Row
-	Value    string
-	formula  string
-	style    *Style
-	NumFmt   string
-	date1904 bool
-	Hidden   bool
-	HMerge   int
-	VMerge   int
-	cellType CellType
+	Row          *Row
+	Value        string
+	formula      string
+	style        *Style
+	NumFmt       string
+	parsedNumFmt *parsedNumberFormat
+	date1904     bool
+	Hidden       bool
+	HMerge       int
+	VMerge       int
+	cellType     CellType
 }
 }
 
 
 // CellInterface defines the public API of the Cell.
 // CellInterface defines the public API of the Cell.
@@ -46,7 +63,7 @@ type CellInterface interface {
 
 
 // NewCell creates a cell and adds it to a row.
 // NewCell creates a cell and adds it to a row.
 func NewCell(r *Row) *Cell {
 func NewCell(r *Row) *Cell {
-	return &Cell{Row: r}
+	return &Cell{Row: r, NumFmt: "general"}
 }
 }
 
 
 // Merge with other cells, horizontally and/or vertically.
 // Merge with other cells, horizontally and/or vertically.
@@ -107,15 +124,9 @@ func (c *Cell) GetTime(date1904 bool) (t time.Time, err error) {
 // SetFloatWithFormat sets the value of a cell to a float and applies
 // SetFloatWithFormat sets the value of a cell to a float and applies
 // formatting to the cell.
 // formatting to the cell.
 func (c *Cell) SetFloatWithFormat(n float64, format string) {
 func (c *Cell) SetFloatWithFormat(n float64, format string) {
-	// beauty the output when the float is small enough
-	if n != 0 && n < 0.00001 {
-		c.Value = strconv.FormatFloat(n, 'e', -1, 64)
-	} else {
-		c.Value = strconv.FormatFloat(n, 'f', -1, 64)
-	}
+	c.SetValue(n)
 	c.NumFmt = format
 	c.NumFmt = format
 	c.formula = ""
 	c.formula = ""
-	c.cellType = CellTypeNumeric
 }
 }
 
 
 var timeLocationUTC, _ = time.LoadLocation("UTC")
 var timeLocationUTC, _ = time.LoadLocation("UTC")
@@ -171,7 +182,7 @@ func (c *Cell) SetDateTimeWithFormat(n float64, format string) {
 	c.Value = strconv.FormatFloat(n, 'f', -1, 64)
 	c.Value = strconv.FormatFloat(n, 'f', -1, 64)
 	c.NumFmt = format
 	c.NumFmt = format
 	c.formula = ""
 	c.formula = ""
-	c.cellType = CellTypeDate
+	c.cellType = CellTypeNumeric
 }
 }
 
 
 // Float returns the value of cell as a number.
 // Float returns the value of cell as a number.
@@ -197,6 +208,20 @@ func (c *Cell) Int64() (int64, error) {
 	return f, nil
 	return f, nil
 }
 }
 
 
+// GeneralNumeric returns the value of the cell as a string. It is formatted very closely to the the XLSX spec for how
+// to display values when the storage type is Number and the format type is General. It is not 100% identical to the
+// spec but is as close as you can get using the built in Go formatting tools.
+func (c *Cell) GeneralNumeric() (string, error) {
+	return generalNumericScientific(c.Value, true)
+}
+
+// GeneralNumericWithoutScientific returns numbers that are always formatted as numbers, but it does not follow
+// the rules for when XLSX should switch to scientific notation, since sometimes scientific notation is not desired,
+// even if that is how the document is supposed to be formatted.
+func (c *Cell) GeneralNumericWithoutScientific() (string, error) {
+	return generalNumericScientific(c.Value, false)
+}
+
 // SetInt sets a cell's value to an integer.
 // SetInt sets a cell's value to an integer.
 func (c *Cell) SetInt(n int) {
 func (c *Cell) SetInt(n int) {
 	c.SetValue(n)
 	c.SetValue(n)
@@ -206,10 +231,19 @@ func (c *Cell) SetInt(n int) {
 func (c *Cell) SetValue(n interface{}) {
 func (c *Cell) SetValue(n interface{}) {
 	switch t := n.(type) {
 	switch t := n.(type) {
 	case time.Time:
 	case time.Time:
-		c.SetDateTime(n.(time.Time))
+		c.SetDateTime(t)
 		return
 		return
-	case int, int8, int16, int32, int64, float32, float64:
-		c.setGeneral(fmt.Sprintf("%v", n))
+	case int, int8, int16, int32, int64:
+		c.setNumeric(fmt.Sprintf("%d", n))
+	case float64:
+		// When formatting floats, do not use fmt.Sprintf("%v", n), this will cause numbers below 1e-4 to be printed in
+		// scientific notation. Scientific notation is not a valid way to store numbers in XML.
+		// Also not not use fmt.Sprintf("%f", n), this will cause numbers to be stored as X.XXXXXX. Which means that
+		// numbers will lose precision and numbers with fewer significant digits such as 0 will be stored as 0.000000
+		// which causes tests to fail.
+		c.setNumeric(strconv.FormatFloat(t, 'f', -1, 64))
+	case float32:
+		c.setNumeric(strconv.FormatFloat(float64(t), 'f', -1, 32))
 	case string:
 	case string:
 		c.SetString(t)
 		c.SetString(t)
 	case []byte:
 	case []byte:
@@ -221,12 +255,12 @@ func (c *Cell) SetValue(n interface{}) {
 	}
 	}
 }
 }
 
 
-// SetInt sets a cell's value to an integer.
-func (c *Cell) setGeneral(s string) {
+// setNumeric sets a cell's value to a number
+func (c *Cell) setNumeric(s string) {
 	c.Value = s
 	c.Value = s
 	c.NumFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
 	c.NumFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
 	c.formula = ""
 	c.formula = ""
-	c.cellType = CellTypeGeneral
+	c.cellType = CellTypeNumeric
 }
 }
 
 
 // Int returns the value of cell as integer.
 // Int returns the value of cell as integer.
@@ -259,7 +293,7 @@ func (c *Cell) Bool() bool {
 		return c.Value == "1"
 		return c.Value == "1"
 	}
 	}
 	// If numeric, base it on a non-zero.
 	// If numeric, base it on a non-zero.
-	if c.cellType == CellTypeNumeric || c.cellType == CellTypeGeneral {
+	if c.cellType == CellTypeNumeric {
 		return c.Value != "0"
 		return c.Value != "0"
 	}
 	}
 	// Return whether there's an empty string.
 	// Return whether there's an empty string.
@@ -269,7 +303,12 @@ func (c *Cell) Bool() bool {
 // SetFormula sets the format string for a cell.
 // SetFormula sets the format string for a cell.
 func (c *Cell) SetFormula(formula string) {
 func (c *Cell) SetFormula(formula string) {
 	c.formula = formula
 	c.formula = formula
-	c.cellType = CellTypeFormula
+	c.cellType = CellTypeNumeric
+}
+
+func (c *Cell) SetStringFormula(formula string) {
+	c.formula = formula
+	c.cellType = CellTypeStringFormula
 }
 }
 
 
 // Formula returns the formula string for the cell.
 // Formula returns the formula string for the cell.
@@ -311,143 +350,24 @@ func (c *Cell) formatToInt(format string) (string, error) {
 	return fmt.Sprintf(format, int(f)), nil
 	return fmt.Sprintf(format, int(f)), nil
 }
 }
 
 
+// getNumberFormat will update the parsedNumFmt struct if it has become out of date, since a cell's NumFmt string is a
+// public field that could be edited by clients.
+func (c *Cell) getNumberFormat() *parsedNumberFormat {
+	if c.parsedNumFmt == nil || c.parsedNumFmt.numFmt != c.NumFmt {
+		c.parsedNumFmt = parseFullNumberFormatString(c.NumFmt)
+	}
+	return c.parsedNumFmt
+}
+
 // FormattedValue returns a value, and possibly an error condition
 // FormattedValue returns a value, and possibly an error condition
 // from a Cell.  If it is possible to apply a format to the cell
 // from a Cell.  If it is possible to apply a format to the cell
 // value, it will do so, if not then an error will be returned, along
 // value, it will do so, if not then an error will be returned, along
 // with the raw value of the Cell.
 // with the raw value of the Cell.
 func (c *Cell) FormattedValue() (string, error) {
 func (c *Cell) FormattedValue() (string, error) {
-	var numberFormat = c.GetNumberFormat()
-	if isTimeFormat(numberFormat) {
-		return parseTime(c)
-	}
-	switch numberFormat {
-	case builtInNumFmt[builtInNumFmtIndex_GENERAL], builtInNumFmt[builtInNumFmtIndex_STRING]:
-		return c.Value, nil
-	case builtInNumFmt[builtInNumFmtIndex_INT], "#,##0":
-		return c.formatToInt("%d")
-	case builtInNumFmt[builtInNumFmtIndex_FLOAT], "#,##0.00":
-		return c.formatToFloat("%.2f")
-	case "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)":
-		f, err := strconv.ParseFloat(c.Value, 64)
-		if err != nil {
-			return c.Value, err
-		}
-		if f < 0 {
-			i := int(math.Abs(f))
-			return fmt.Sprintf("(%d)", i), nil
-		}
-		i := int(f)
-		return fmt.Sprintf("%d", i), nil
-	case "#,##0.00;(#,##0.00)", "#,##0.00;[red](#,##0.00)":
-		f, err := strconv.ParseFloat(c.Value, 64)
-		if err != nil {
-			return c.Value, err
-		}
-		if f < 0 {
-			return fmt.Sprintf("(%.2f)", f), nil
-		}
-		return fmt.Sprintf("%.2f", f), nil
-	case "0%":
-		f, err := strconv.ParseFloat(c.Value, 64)
-		if err != nil {
-			return c.Value, err
-		}
-		f = f * 100
-		return fmt.Sprintf("%d%%", int(f)), nil
-	case "0.00%":
-		f, err := strconv.ParseFloat(c.Value, 64)
-		if err != nil {
-			return c.Value, err
-		}
-		f = f * 100
-		return fmt.Sprintf("%.2f%%", f), nil
-	case "0.00e+00", "##0.0e+0":
-		return c.formatToFloat("%e")
+	fullFormat := c.getNumberFormat()
+	returnVal, err := fullFormat.FormatValue(c)
+	if fullFormat.parseEncounteredError != nil {
+		return returnVal, *fullFormat.parseEncounteredError
 	}
 	}
-	return c.Value, nil
-
-}
-
-// parseTime returns a string parsed using time.Time
-func parseTime(c *Cell) (string, error) {
-	f, err := strconv.ParseFloat(c.Value, 64)
-	if err != nil {
-		return c.Value, err
-	}
-	val := TimeFromExcelTime(f, c.date1904)
-	format := c.GetNumberFormat()
-
-	// Replace Excel placeholders with Go time placeholders.
-	// For example, replace yyyy with 2006. These are in a specific order,
-	// due to the fact that m is used in month, minute, and am/pm. It would
-	// be easier to fix that with regular expressions, but if it's possible
-	// to keep this simple it would be easier to maintain.
-	// Full-length month and days (e.g. March, Tuesday) have letters in them that would be replaced
-	// by other characters below (such as the 'h' in March, or the 'd' in Tuesday) below.
-	// First we convert them to arbitrary characters unused in Excel Date formats, and then at the end,
-	// turn them to what they should actually be.
-	// Based off: http://www.ozgrid.com/Excel/CustomFormats.htm
-	replacements := []struct{ xltime, gotime string }{
-		{"yyyy", "2006"},
-		{"yy", "06"},
-		{"mmmm", "%%%%"},
-		{"dddd", "&&&&"},
-		{"dd", "02"},
-		{"d", "2"},
-		{"mmm", "Jan"},
-		{"mmss", "0405"},
-		{"ss", "05"},
-		{"mm:", "04:"},
-		{":mm", ":04"},
-		{"mm", "01"},
-		{"am/pm", "pm"},
-		{"m/", "1/"},
-		{"%%%%", "January"},
-		{"&&&&", "Monday"},
-	}
-	// It is the presence of the "am/pm" indicator that determins
-	// if this is a 12 hour or 24 hours time format, not the
-	// number of 'h' characters.
-	if is12HourTime(format) {
-		format = strings.Replace(format, "hh", "03", 1)
-		format = strings.Replace(format, "h", "3", 1)
-	} else {
-		format = strings.Replace(format, "hh", "15", 1)
-		format = strings.Replace(format, "h", "15", 1)
-	}
-	for _, repl := range replacements {
-		format = strings.Replace(format, repl.xltime, repl.gotime, 1)
-	}
-	// If the hour is optional, strip it out, along with the
-	// possible dangling colon that would remain.
-	if val.Hour() < 1 {
-		format = strings.Replace(format, "]:", "]", 1)
-		format = strings.Replace(format, "[03]", "", 1)
-		format = strings.Replace(format, "[3]", "", 1)
-		format = strings.Replace(format, "[15]", "", 1)
-	} else {
-		format = strings.Replace(format, "[3]", "3", 1)
-		format = strings.Replace(format, "[15]", "15", 1)
-	}
-	return val.Format(format), nil
-}
-
-// isTimeFormat checks whether an Excel format string represents
-// a time.Time.
-func isTimeFormat(format string) bool {
-	dateParts := []string{
-		"yy", "hh", "h", "am/pm", "AM/PM", "A/P", "a/p", "ss", "mm", ":",
-	}
-	for _, part := range dateParts {
-		if strings.Contains(format, part) {
-			return true
-		}
-	}
-	return false
-}
-
-// is12HourTime checks whether an Excel time format string is a 12
-// hours form.
-func is12HourTime(format string) bool {
-	return strings.Contains(format, "am/pm") || strings.Contains(format, "AM/PM") || strings.Contains(format, "a/p") || strings.Contains(format, "A/P")
+	return returnVal, err
 }
 }

+ 12 - 7
vendor/github.com/tealeg/xlsx/col.go

@@ -11,25 +11,30 @@ type Col struct {
 	Collapsed    bool
 	Collapsed    bool
 	OutlineLevel uint8
 	OutlineLevel uint8
 	numFmt       string
 	numFmt       string
+	parsedNumFmt *parsedNumberFormat
 	style        *Style
 	style        *Style
 }
 }
 
 
+// SetType will set the format string of a column based on the type that you want to set it to.
+// This function does not really make a lot of sense.
 func (c *Col) SetType(cellType CellType) {
 func (c *Col) SetType(cellType CellType) {
 	switch cellType {
 	switch cellType {
 	case CellTypeString:
 	case CellTypeString:
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
-	case CellTypeBool:
-		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
 	case CellTypeNumeric:
 	case CellTypeNumeric:
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_INT]
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_INT]
-	case CellTypeDate:
-		c.numFmt = builtInNumFmt[builtInNumFmtIndex_DATE]
-	case CellTypeFormula:
-		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
+	case CellTypeBool:
+		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
+	case CellTypeInline:
+		c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
 	case CellTypeError:
 	case CellTypeError:
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
-	case CellTypeGeneral:
+	case CellTypeDate:
+		// Cells that are stored as dates are not properly supported in this library.
+		// They should instead be stored as a Numeric with a date format.
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
 		c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
+	case CellTypeStringFormula:
+		c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
 	}
 	}
 }
 }
 
 

+ 79 - 11
vendor/github.com/tealeg/xlsx/file.go

@@ -10,6 +10,7 @@ import (
 	"os"
 	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"unicode/utf8"
 )
 )
 
 
 // File is a high level structure providing a slice of Sheet structs
 // File is a high level structure providing a slice of Sheet structs
@@ -25,6 +26,8 @@ type File struct {
 	DefinedNames   []*xlsxDefinedName
 	DefinedNames   []*xlsxDefinedName
 }
 }
 
 
+const NoRowLimit int = -1
+
 // Create a new File
 // Create a new File
 func NewFile() *File {
 func NewFile() *File {
 	return &File{
 	return &File{
@@ -36,31 +39,48 @@ func NewFile() *File {
 
 
 // OpenFile() take the name of an XLSX file and returns a populated
 // OpenFile() take the name of an XLSX file and returns a populated
 // xlsx.File struct for it.
 // xlsx.File struct for it.
-func OpenFile(filename string) (file *File, err error) {
-	var f *zip.ReadCloser
-	f, err = zip.OpenReader(filename)
+func OpenFile(fileName string) (file *File, err error) {
+	return OpenFileWithRowLimit(fileName, NoRowLimit)
+}
+
+// OpenFileWithRowLimit() will open the file, but will only read the specified number of rows.
+// If you save this file, it will be truncated to the number of rows specified.
+func OpenFileWithRowLimit(fileName string, rowLimit int) (file *File, err error) {
+	var z *zip.ReadCloser
+	z, err = zip.OpenReader(fileName)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	file, err = ReadZip(f)
-	return
+	return ReadZipWithRowLimit(z, rowLimit)
 }
 }
 
 
 // OpenBinary() take bytes of an XLSX file and returns a populated
 // OpenBinary() take bytes of an XLSX file and returns a populated
 // xlsx.File struct for it.
 // xlsx.File struct for it.
 func OpenBinary(bs []byte) (*File, error) {
 func OpenBinary(bs []byte) (*File, error) {
+	return OpenBinaryWithRowLimit(bs, NoRowLimit)
+}
+
+// OpenBinaryWithRowLimit() take bytes of an XLSX file and returns a populated
+// xlsx.File struct for it.
+func OpenBinaryWithRowLimit(bs []byte, rowLimit int) (*File, error) {
 	r := bytes.NewReader(bs)
 	r := bytes.NewReader(bs)
-	return OpenReaderAt(r, int64(r.Len()))
+	return OpenReaderAtWithRowLimit(r, int64(r.Len()), rowLimit)
 }
 }
 
 
 // OpenReaderAt() take io.ReaderAt of an XLSX file and returns a populated
 // OpenReaderAt() take io.ReaderAt of an XLSX file and returns a populated
 // xlsx.File struct for it.
 // xlsx.File struct for it.
 func OpenReaderAt(r io.ReaderAt, size int64) (*File, error) {
 func OpenReaderAt(r io.ReaderAt, size int64) (*File, error) {
+	return OpenReaderAtWithRowLimit(r, size, NoRowLimit)
+}
+
+// OpenReaderAtWithRowLimit() take io.ReaderAt of an XLSX file and returns a populated
+// xlsx.File struct for it.
+func OpenReaderAtWithRowLimit(r io.ReaderAt, size int64, rowLimit int) (*File, error) {
 	file, err := zip.NewReader(r, size)
 	file, err := zip.NewReader(r, size)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return ReadZipReader(file)
+	return ReadZipReaderWithRowLimit(file, rowLimit)
 }
 }
 
 
 // A convenient wrapper around File.ToSlice, FileToSlice will
 // A convenient wrapper around File.ToSlice, FileToSlice will
@@ -85,6 +105,18 @@ func FileToSlice(path string) ([][][]string, error) {
 	return f.ToSlice()
 	return f.ToSlice()
 }
 }
 
 
+// FileToSliceUnmerged is a wrapper around File.ToSliceUnmerged.
+// It returns the raw data contained in an Excel XLSX file as three
+// dimensional slice. Merged cells will be unmerged. Covered cells become the
+// values of theirs origins.
+func FileToSliceUnmerged(path string) ([][][]string, error) {
+	f, err := OpenFile(path)
+	if err != nil {
+		return nil, err
+	}
+	return f.ToSliceUnmerged()
+}
+
 // Save the File to an xlsx file at the provided path.
 // Save the File to an xlsx file at the provided path.
 func (f *File) Save(path string) (err error) {
 func (f *File) Save(path string) (err error) {
 	target, err := os.Create(path)
 	target, err := os.Create(path)
@@ -123,8 +155,8 @@ func (f *File) AddSheet(sheetName string) (*Sheet, error) {
 	if _, exists := f.Sheet[sheetName]; exists {
 	if _, exists := f.Sheet[sheetName]; exists {
 		return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
 		return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
 	}
 	}
-	if len(sheetName) >= 31 {
-		return nil, fmt.Errorf("sheet name must be less than 31 characters long.  It is currently '%d' characters long", len(sheetName))
+	if utf8.RuneCountInString(sheetName) >= 31 {
+		return nil, fmt.Errorf("sheet name must be less than 31 characters long.  It is currently '%d' characters long", utf8.RuneCountInString(sheetName))
 	}
 	}
 	sheet := &Sheet{
 	sheet := &Sheet{
 		Name:     sheetName,
 		Name:     sheetName,
@@ -305,9 +337,9 @@ func (f *File) MarshallParts() (map[string]string, error) {
 //
 //
 // Here, value would be set to the raw value of the cell A1 in the
 // Here, value would be set to the raw value of the cell A1 in the
 // first sheet in the XLSX file.
 // first sheet in the XLSX file.
-func (file *File) ToSlice() (output [][][]string, err error) {
+func (f *File) ToSlice() (output [][][]string, err error) {
 	output = [][][]string{}
 	output = [][][]string{}
-	for _, sheet := range file.Sheets {
+	for _, sheet := range f.Sheets {
 		s := [][]string{}
 		s := [][]string{}
 		for _, row := range sheet.Rows {
 		for _, row := range sheet.Rows {
 			if row == nil {
 			if row == nil {
@@ -333,3 +365,39 @@ func (file *File) ToSlice() (output [][][]string, err error) {
 	}
 	}
 	return output, nil
 	return output, nil
 }
 }
+
+// ToSliceUnmerged returns the raw data contained in the File as three
+// dimensional slice (s. method ToSlice).
+// A covered cell become the value of its origin cell.
+// Example: table where A1:A2 merged.
+// | 01.01.2011 | Bread | 20 |
+// |            | Fish  | 70 |
+// This sheet will be converted to the slice:
+// [  [01.01.2011 Bread 20]
+// 		[01.01.2011 Fish 70] ]
+func (f *File) ToSliceUnmerged() (output [][][]string, err error) {
+	output, err = f.ToSlice()
+	if err != nil {
+		return nil, err
+	}
+
+	for s, sheet := range f.Sheets {
+		for r, row := range sheet.Rows {
+			for c, cell := range row.Cells {
+				if cell.HMerge > 0 {
+					for i := c + 1; i <= c+cell.HMerge; i++ {
+						output[s][r][i] = output[s][r][c]
+					}
+				}
+
+				if cell.VMerge > 0 {
+					for i := r + 1; i <= r+cell.VMerge; i++ {
+						output[s][i][c] = output[s][r][c]
+					}
+				}
+			}
+		}
+	}
+
+	return output, nil
+}

+ 644 - 0
vendor/github.com/tealeg/xlsx/format_code.go

@@ -0,0 +1,644 @@
+package xlsx
+
+import (
+	"errors"
+	"fmt"
+	"math"
+	"strconv"
+	"strings"
+)
+
+// Do not edit these attributes once this struct is created. This struct should only be created by
+// parseFullNumberFormatString() from a number format string. If the format for a cell needs to change, change
+// the number format string and getNumberFormat() will invalidate the old struct and re-parse the string.
+type parsedNumberFormat struct {
+	numFmt                        string
+	isTimeFormat                  bool
+	negativeFormatExpectsPositive bool
+	positiveFormat                *formatOptions
+	negativeFormat                *formatOptions
+	zeroFormat                    *formatOptions
+	textFormat                    *formatOptions
+	parseEncounteredError         *error
+}
+
+type formatOptions struct {
+	isTimeFormat        bool
+	showPercent         bool
+	fullFormatString    string
+	reducedFormatString string
+	prefix              string
+	suffix              string
+}
+
+// FormatValue returns a value, and possibly an error condition
+// from a Cell.  If it is possible to apply a format to the cell
+// value, it will do so, if not then an error will be returned, along
+// with the raw value of the Cell.
+//
+// This is the documentation of the "General" Format in the Office Open XML spec:
+//
+// Numbers
+// The application shall attempt to display the full number up to 11 digits (inc. decimal point). If the number is too
+// large*, the application shall attempt to show exponential format. If the number has too many significant digits, the
+// display shall be truncated. The optimal method of display is based on the available cell width. If the number cannot
+// be displayed using any of these formats in the available width, the application shall show "#" across the width of
+// the cell.
+//
+// Conditions for switching to exponential format:
+// 1. The cell value shall have at least five digits for xE-xx
+// 2. If the exponent is bigger than the size allowed, a floating point number cannot fit, so try exponential notation.
+// 3. Similarly, for negative exponents, check if there is space for even one (non-zero) digit in floating point format**.
+// 4. Finally, if there isn't room for all of the significant digits in floating point format (for a negative exponent),
+// exponential format shall display more digits if the exponent is less than -3. (The 3 is because E-xx takes 4
+// characters, and the leading 0 in floating point takes only 1 character. Thus, for an exponent less than -3, there is
+// more than 3 additional leading 0's, more than enough to compensate for the size of the E-xx.)
+//
+// Floating point rule:
+// For general formatting in cells, max overall length for cell display is 11, not including negative sign, but includes
+// leading zeros and decimal separator.***
+//
+// Added Notes:
+// * "If the number is too large" can also mean "if the number has more than 11 digits", so greater than or equal to
+// 1e11 and less than 1e-9.
+// ** Means that you should switch to scientific if there would be 9 zeros after the decimal (the decimal and first zero
+// count against the 11 character limit), so less than 1e9.
+// *** The way this is written, you can get numbers that are more than 11 characters because the golang Float fmt
+// does not support adjusting the precision while not padding with zeros, while also not switching to scientific
+// notation too early.
+func (fullFormat *parsedNumberFormat) FormatValue(cell *Cell) (string, error) {
+	switch cell.cellType {
+	case CellTypeError:
+		// The error type is what XLSX uses in error cases such as when formulas are invalid.
+		// There will be text in the cell's value that can be shown, something ugly like #NAME? or #######
+		return cell.Value, nil
+	case CellTypeBool:
+		if cell.Value == "0" {
+			return "FALSE", nil
+		} else if cell.Value == "1" {
+			return "TRUE", nil
+		} else {
+			return cell.Value, errors.New("invalid value in bool cell")
+		}
+	case CellTypeString:
+		fallthrough
+	case CellTypeInline:
+		fallthrough
+	case CellTypeStringFormula:
+		textFormat := cell.parsedNumFmt.textFormat
+		// This switch statement is only for String formats
+		switch textFormat.reducedFormatString {
+		case builtInNumFmt[builtInNumFmtIndex_GENERAL]: // General is literally "general"
+			return cell.Value, nil
+		case builtInNumFmt[builtInNumFmtIndex_STRING]: // String is "@"
+			return textFormat.prefix + cell.Value + textFormat.suffix, nil
+		case "":
+			// If cell is not "General" and there is not an "@" symbol in the format, then the cell's value is not
+			// used when determining what to display. It would be completely legal to have a format of "Error"
+			// for strings, and all values that are not numbers would show up as "Error". In that case, this code would
+			// have a prefix of "Error" and a reduced format string of "" (empty string).
+			return textFormat.prefix + textFormat.suffix, nil
+		default:
+			return cell.Value, errors.New("invalid or unsupported format, unsupported string format")
+		}
+	case CellTypeDate:
+		// These are dates that are stored in date format instead of being stored as numbers with a format to turn them
+		// into a date string.
+		return cell.Value, nil
+	case CellTypeNumeric:
+		return fullFormat.formatNumericCell(cell)
+	default:
+		return cell.Value, errors.New("unknown cell type")
+	}
+}
+
+func (fullFormat *parsedNumberFormat) formatNumericCell(cell *Cell) (string, error) {
+	rawValue := strings.TrimSpace(cell.Value)
+	// If there wasn't a value in the cell, it shouldn't have been marked as Numeric.
+	// It's better to support this case though.
+	if rawValue == "" {
+		return "", nil
+	}
+
+	if fullFormat.isTimeFormat {
+		return fullFormat.parseTime(rawValue, cell.date1904)
+	}
+	var numberFormat *formatOptions
+	floatVal, floatErr := strconv.ParseFloat(rawValue, 64)
+	if floatErr != nil {
+		return rawValue, floatErr
+	}
+	// Choose the correct format. There can be different formats for positive, negative, and zero numbers.
+	// Excel only uses the zero format if the value is literally zero, even if the number is so small that it shows
+	// up as "0" when the positive format is used.
+	if floatVal > 0 {
+		numberFormat = fullFormat.positiveFormat
+	} else if floatVal < 0 {
+		// If format string specified a different format for negative numbers, then the number should be made positive
+		// before getting formatted. The format string itself will contain formatting that denotes a negative number and
+		// this formatting will end up in the prefix or suffix. Commonly if there is a negative format specified, the
+		// number will get surrounded by parenthesis instead of showing it with a minus sign.
+		if fullFormat.negativeFormatExpectsPositive {
+			floatVal = math.Abs(floatVal)
+		}
+		numberFormat = fullFormat.negativeFormat
+	} else {
+		numberFormat = fullFormat.zeroFormat
+	}
+
+	// When showPercent is true, multiply the number by 100.
+	// The percent sign will be in the prefix or suffix already, so it does not need to be added in this function.
+	// The number format itself will be the same as any other number format once the value is multiplied by 100.
+	if numberFormat.showPercent {
+		floatVal = 100 * floatVal
+	}
+
+	// Only the most common format strings are supported here.
+	// Eventually this switch needs to be replaced with a more general solution.
+	// Some of these "supported" formats should have thousand separators, but don't get them since Go fmt
+	// doesn't have a way to request thousands separators.
+	// The only things that should be supported here are in the array formattingCharacters,
+	// everything else has been stripped out before and will be placed in the prefix or suffix.
+	// The formatting characters can have non-formatting characters mixed in with them and those should be maintained.
+	// However, at this time we fail to parse those formatting codes and they get replaced with "General"
+	var formattedNum string
+	switch numberFormat.reducedFormatString {
+	case builtInNumFmt[builtInNumFmtIndex_GENERAL]: // General is literally "general"
+		// prefix, showPercent, and suffix cannot apply to the general format
+		// The logic for showing numbers when the format is "general" is much more complicated than the rest of these.
+		generalFormatted, err := generalNumericScientific(cell.Value, true)
+		if err != nil {
+			return rawValue, nil
+		}
+		return generalFormatted, nil
+	case builtInNumFmt[builtInNumFmtIndex_STRING]: // String is "@"
+		formattedNum = cell.Value
+	case builtInNumFmt[builtInNumFmtIndex_INT], "#,##0": // Int is "0"
+		// Previously this case would cast to int and print with %d, but that will not round the value correctly.
+		formattedNum = fmt.Sprintf("%.0f", floatVal)
+	case "0.0", "#,##0.0":
+		formattedNum = fmt.Sprintf("%.1f", floatVal)
+	case builtInNumFmt[builtInNumFmtIndex_FLOAT], "#,##0.00": // Float is "0.00"
+		formattedNum = fmt.Sprintf("%.2f", floatVal)
+	case "0.000", "#,##0.000":
+		formattedNum = fmt.Sprintf("%.3f", floatVal)
+	case "0.0000", "#,##0.0000":
+		formattedNum = fmt.Sprintf("%.4f", floatVal)
+	case "0.00e+00", "##0.0e+0":
+		formattedNum = fmt.Sprintf("%e", floatVal)
+	case "":
+		// Do nothing.
+	default:
+		return rawValue, nil
+	}
+	return numberFormat.prefix + formattedNum + numberFormat.suffix, nil
+}
+
+func generalNumericScientific(value string, allowScientific bool) (string, error) {
+	if strings.TrimSpace(value) == "" {
+		return "", nil
+	}
+	f, err := strconv.ParseFloat(value, 64)
+	if err != nil {
+		return value, err
+	}
+	if allowScientific {
+		absF := math.Abs(f)
+		// When using General format, numbers that are less than 1e-9 (0.000000001) and greater than or equal to
+		// 1e11 (100,000,000,000) should be shown in scientific notation.
+		// Numbers less than the number after zero, are assumed to be zero.
+		if (absF >= math.SmallestNonzeroFloat64 && absF < minNonScientificNumber) || absF >= maxNonScientificNumber {
+			return strconv.FormatFloat(f, 'E', -1, 64), nil
+		}
+	}
+	// This format (fmt="f", prec=-1) will prevent padding with zeros and will never switch to scientific notation.
+	// However, it will show more than 11 characters for very precise numbers, and this cannot be changed.
+	// You could also use fmt="g", prec=11, which doesn't pad with zeros and allows the correct precision,
+	// but it will use scientific notation on numbers less than 1e-4. That value is hardcoded in Go and cannot be
+	// configured or disabled.
+	return strconv.FormatFloat(f, 'f', -1, 64), nil
+}
+
+// Format strings are a little strange to compare because empty string needs to be taken as general, and general needs
+// to be compared case insensitively.
+func compareFormatString(fmt1, fmt2 string) bool {
+	if fmt1 == fmt2 {
+		return true
+	}
+	if fmt1 == "" || strings.EqualFold(fmt1, "general") {
+		fmt1 = "general"
+	}
+	if fmt2 == "" || strings.EqualFold(fmt2, "general") {
+		fmt2 = "general"
+	}
+	return fmt1 == fmt2
+}
+
+func parseFullNumberFormatString(numFmt string) *parsedNumberFormat {
+	parsedNumFmt := &parsedNumberFormat{
+		numFmt: numFmt,
+	}
+	if isTimeFormat(numFmt) {
+		// Time formats cannot have multiple groups separated by semicolons, there is only one format.
+		// Strings are unaffected by the time format.
+		parsedNumFmt.isTimeFormat = true
+		parsedNumFmt.textFormat, _ = parseNumberFormatSection("general")
+		return parsedNumFmt
+	}
+
+	var fmtOptions []*formatOptions
+	formats, err := splitFormatOnSemicolon(numFmt)
+	if err == nil {
+		for _, formatSection := range formats {
+			parsedFormat, err := parseNumberFormatSection(formatSection)
+			if err != nil {
+				// If an invalid number section is found, fall back to general
+				parsedFormat = fallbackErrorFormat
+				parsedNumFmt.parseEncounteredError = &err
+			}
+			fmtOptions = append(fmtOptions, parsedFormat)
+		}
+	} else {
+		fmtOptions = append(fmtOptions, fallbackErrorFormat)
+		parsedNumFmt.parseEncounteredError = &err
+	}
+	if len(fmtOptions) > 4 {
+		fmtOptions = []*formatOptions{fallbackErrorFormat}
+		err = errors.New("invalid number format, too many format sections")
+		parsedNumFmt.parseEncounteredError = &err
+	}
+
+	if len(fmtOptions) == 1 {
+		// If there is only one option, it is used for all
+		parsedNumFmt.positiveFormat = fmtOptions[0]
+		parsedNumFmt.negativeFormat = fmtOptions[0]
+		parsedNumFmt.zeroFormat = fmtOptions[0]
+		if strings.Contains(fmtOptions[0].fullFormatString, "@") {
+			parsedNumFmt.textFormat = fmtOptions[0]
+		} else {
+			parsedNumFmt.textFormat, _ = parseNumberFormatSection("general")
+		}
+	} else if len(fmtOptions) == 2 {
+		// If there are two formats, the first is used for positive and zeros, the second gets used as a negative format,
+		// and strings are not formatted.
+		// When negative numbers now have their own format, they should become positive before having the format applied.
+		// The format will contain a negative sign if it is desired, but they may be colored red or wrapped in
+		// parenthesis instead.
+		parsedNumFmt.negativeFormatExpectsPositive = true
+		parsedNumFmt.positiveFormat = fmtOptions[0]
+		parsedNumFmt.negativeFormat = fmtOptions[1]
+		parsedNumFmt.zeroFormat = fmtOptions[0]
+		parsedNumFmt.textFormat, _ = parseNumberFormatSection("general")
+	} else if len(fmtOptions) == 3 {
+		// If there are three formats, the first is used for positive, the second gets used as a negative format,
+		// the third is for negative, and strings are not formatted.
+		parsedNumFmt.negativeFormatExpectsPositive = true
+		parsedNumFmt.positiveFormat = fmtOptions[0]
+		parsedNumFmt.negativeFormat = fmtOptions[1]
+		parsedNumFmt.zeroFormat = fmtOptions[2]
+		parsedNumFmt.textFormat, _ = parseNumberFormatSection("general")
+	} else {
+		// With four options, the first is positive, the second is negative, the third is zero, and the fourth is strings
+		// Negative numbers should be still become positive before having the negative formatting applied.
+		parsedNumFmt.negativeFormatExpectsPositive = true
+		parsedNumFmt.positiveFormat = fmtOptions[0]
+		parsedNumFmt.negativeFormat = fmtOptions[1]
+		parsedNumFmt.zeroFormat = fmtOptions[2]
+		parsedNumFmt.textFormat = fmtOptions[3]
+	}
+	return parsedNumFmt
+}
+
+// splitFormatOnSemicolon will split the format string into the format sections
+// This logic to split the different formats on semicolon is fully correct, and will skip all literal semicolons,
+// and will catch all breaking semicolons.
+func splitFormatOnSemicolon(format string) ([]string, error) {
+	var formats []string
+	prevIndex := 0
+	for i := 0; i < len(format); i++ {
+		if format[i] == ';' {
+			formats = append(formats, format[prevIndex:i])
+			prevIndex = i + 1
+		} else if format[i] == '\\' {
+			i++
+		} else if format[i] == '"' {
+			endQuoteIndex := strings.Index(format[i+1:], "\"")
+			if endQuoteIndex == -1 {
+				// This is an invalid format string, fall back to general
+				return nil, errors.New("invalid format string, unmatched double quote")
+			}
+			i += endQuoteIndex + 1
+		}
+	}
+	return append(formats, format[prevIndex:]), nil
+}
+
+var fallbackErrorFormat = &formatOptions{
+	fullFormatString:    "general",
+	reducedFormatString: "general",
+}
+
+// parseNumberFormatSection takes in individual format and parses out most of the options.
+// Some options are parsed, removed from the string, and set as settings on formatOptions.
+// There remainder of the format string is put in the reducedFormatString attribute, and supported values for these
+// are handled in a switch in the Cell.FormattedValue() function.
+// Ideally more and more of the format string would be parsed out here into settings until there is no remainder string
+// at all.
+// Features that this supports:
+// - Time formats are detected, and marked in the options. Time format strings are handled when doing the formatting.
+//   The logic to detect time formats is currently not correct, and can catch formats that are not time formats as well
+//   as miss formats that are time formats.
+// - Color formats are detected and removed.
+// - Currency annotations are handled properly.
+// - Literal strings wrapped in quotes are handled and put into prefix or suffix.
+// - Numbers that should be percent are detected and marked in the options.
+// - Conditionals are detected and removed, but they are not obeyed. The conditional groups will be used just like the
+//   positive;negative;zero;string format groups. Here is an example of a conditional format: "[Red][<=100];[Blue][>100]"
+// Decoding the actual number formatting portion is out of scope, that is placed into reducedFormatString and is used
+// when formatting the string. The string there will be reduced to only the things in the formattingCharacters array.
+// Everything not in that array has been parsed out and put into formatOptions.
+func parseNumberFormatSection(fullFormat string) (*formatOptions, error) {
+	reducedFormat := strings.TrimSpace(fullFormat)
+
+	// general is the only format that does not use the normal format symbols notations
+	if compareFormatString(reducedFormat, "general") {
+		return &formatOptions{
+			fullFormatString:    "general",
+			reducedFormatString: "general",
+		}, nil
+	}
+
+	prefix, reducedFormat, showPercent1, err := parseLiterals(reducedFormat)
+	if err != nil {
+		return nil, err
+	}
+
+	reducedFormat, suffixFormat := splitFormatAndSuffixFormat(reducedFormat)
+
+	suffix, remaining, showPercent2, err := parseLiterals(suffixFormat)
+	if err != nil {
+		return nil, err
+	}
+	if len(remaining) > 0 {
+		// This paradigm of codes consisting of literals, number formats, then more literals is not always correct, they can
+		// actually be intertwined. Though 99% of the time number formats will not do this.
+		// Excel uses this format string for Social Security Numbers: 000\-00\-0000
+		// and this for US phone numbers: [<=9999999]###\-####;\(###\)\ ###\-####
+		return nil, errors.New("invalid or unsupported format string")
+	}
+
+	return &formatOptions{
+		fullFormatString:    fullFormat,
+		isTimeFormat:        false,
+		reducedFormatString: reducedFormat,
+		prefix:              prefix,
+		suffix:              suffix,
+		showPercent:         showPercent1 || showPercent2,
+	}, nil
+}
+
+// formattingCharacters will be left in the reducedNumberFormat
+// It is important that these be looked for in order so that the slash cases are handled correctly.
+// / (slash) is a fraction format if preceded by 0, #, or ?, otherwise it is not a formatting character
+// E- E+ e- e+ are scientific notation, but E, e, -, + are not formatting characters independently
+// \ (back slash) makes the next character a literal (not formatting)
+// " Anything in double quotes is not a formatting character
+// _ (underscore) skips the width of the next character, so the next character cannot be formatting
+var formattingCharacters = []string{"0/", "#/", "?/", "E-", "E+", "e-", "e+", "0", "#", "?", ".", ",", "@", "*"}
+
+// The following are also time format characters, but since this is only used for detecting, not decoding, they are
+// redundant here: ee, gg, ggg, rr, ss, mm, hh, yyyy, dd, ddd, dddd, mm, mmm, mmmm, mmmmm, ss.0000, ss.000, ss.00, ss.0
+// The .00 type format is very tricky, because it only counts if it comes after ss or s or [ss] or [s]
+// .00 is actually a valid number format by itself.
+var timeFormatCharacters = []string{"m", "d", "yy", "h", "m", "AM/PM", "A/P", "am/pm", "a/p", "r", "g", "e", "b1", "b2", "[hh]", "[h]", "[mm]", "[m]",
+	"s.0000", "s.000", "s.00", "s.0", "s", "[ss].0000", "[ss].000", "[ss].00", "[ss].0", "[ss]", "[s].0000", "[s].000", "[s].00", "[s].0", "[s]"}
+
+func splitFormatAndSuffixFormat(format string) (string, string) {
+	var i int
+	for ; i < len(format); i++ {
+		curReducedFormat := format[i:]
+		var found bool
+		for _, special := range formattingCharacters {
+			if strings.HasPrefix(curReducedFormat, special) {
+				// Skip ahead if the special character was longer than length 1
+				i += len(special) - 1
+				found = true
+				break
+			}
+		}
+		if !found {
+			break
+		}
+	}
+	suffixFormat := format[i:]
+	format = format[:i]
+	return format, suffixFormat
+}
+
+func parseLiterals(format string) (string, string, bool, error) {
+	var prefix string
+	showPercent := false
+	for i := 0; i < len(format); i++ {
+		curReducedFormat := format[i:]
+		switch curReducedFormat[0] {
+		case '\\':
+			// If there is a slash, skip the next character, and add it to the prefix
+			if len(curReducedFormat) > 1 {
+				i++
+				prefix += curReducedFormat[1:2]
+			}
+		case '_':
+			// If there is an underscore, skip the next character, but don't add it to the prefix
+			if len(curReducedFormat) > 1 {
+				i++
+			}
+		case '*':
+			// Asterisks are used to repeat the next character to fill the full cell width.
+			// There isn't really a cell size in this context, so this will be ignored.
+		case '"':
+			// If there is a quote skip to the next quote, and add the quoted characters to the prefix
+			endQuoteIndex := strings.Index(curReducedFormat[1:], "\"")
+			if endQuoteIndex == -1 {
+				return "", "", false, errors.New("invalid formatting code, unmatched double quote")
+			}
+			prefix = prefix + curReducedFormat[1:endQuoteIndex+1]
+			i += endQuoteIndex + 1
+		case '%':
+			showPercent = true
+			prefix += "%"
+		case '[':
+			// Brackets can be currency annotations (e.g. [$$-409])
+			// color formats (e.g. [color1] through [color56], as well as [red] etc.)
+			// conditionals (e.g. [>100], the valid conditionals are =, >, <, >=, <=, <>)
+			bracketIndex := strings.Index(curReducedFormat, "]")
+			if bracketIndex == -1 {
+				return "", "", false, errors.New("invalid formatting code, invalid brackets")
+			}
+			// Currencies in Excel are annotated with this format: [$<Currency String>-<Language Info>]
+			// Currency String is something like $, ¥, €, or £
+			// Language Info is three hexadecimal characters
+			if len(curReducedFormat) > 2 && curReducedFormat[1] == '$' {
+				dashIndex := strings.Index(curReducedFormat, "-")
+				if dashIndex != -1 && dashIndex < bracketIndex {
+					// Get the currency symbol, and skip to the end of the currency format
+					prefix += curReducedFormat[2:dashIndex]
+				} else {
+					return "", "", false, errors.New("invalid formatting code, invalid currency annotation")
+				}
+			}
+			i += bracketIndex
+		case '$', '-', '+', '/', '(', ')', ':', '!', '^', '&', '\'', '~', '{', '}', '<', '>', '=', ' ':
+			// These symbols are allowed to be used as literal without escaping
+			prefix += curReducedFormat[0:1]
+		default:
+			for _, special := range formattingCharacters {
+				if strings.HasPrefix(curReducedFormat, special) {
+					// This means we found the start of the actual number formatting portion, and should return.
+					return prefix, format[i:], showPercent, nil
+				}
+			}
+			// Symbols that don't have meaning and aren't in the exempt literal characters and are not escaped.
+			return "", "", false, errors.New("invalid formatting code: unsupported or unescaped characters")
+		}
+	}
+	return prefix, "", showPercent, nil
+}
+
+// parseTime returns a string parsed using time.Time
+func (fullFormat *parsedNumberFormat) parseTime(value string, date1904 bool) (string, error) {
+	f, err := strconv.ParseFloat(value, 64)
+	if err != nil {
+		return value, err
+	}
+	val := TimeFromExcelTime(f, date1904)
+	format := fullFormat.numFmt
+	// Replace Excel placeholders with Go time placeholders.
+	// For example, replace yyyy with 2006. These are in a specific order,
+	// due to the fact that m is used in month, minute, and am/pm. It would
+	// be easier to fix that with regular expressions, but if it's possible
+	// to keep this simple it would be easier to maintain.
+	// Full-length month and days (e.g. March, Tuesday) have letters in them that would be replaced
+	// by other characters below (such as the 'h' in March, or the 'd' in Tuesday) below.
+	// First we convert them to arbitrary characters unused in Excel Date formats, and then at the end,
+	// turn them to what they should actually be.
+	// Based off: http://www.ozgrid.com/Excel/CustomFormats.htm
+	replacements := []struct{ xltime, gotime string }{
+		{"yyyy", "2006"},
+		{"yy", "06"},
+		{"mmmm", "%%%%"},
+		{"dddd", "&&&&"},
+		{"dd", "02"},
+		{"d", "2"},
+		{"mmm", "Jan"},
+		{"mmss", "0405"},
+		{"ss", "05"},
+		{"mm:", "04:"},
+		{":mm", ":04"},
+		{"mm", "01"},
+		{"am/pm", "pm"},
+		{"m/", "1/"},
+		{"%%%%", "January"},
+		{"&&&&", "Monday"},
+	}
+	// It is the presence of the "am/pm" indicator that determins
+	// if this is a 12 hour or 24 hours time format, not the
+	// number of 'h' characters.
+	if is12HourTime(format) {
+		format = strings.Replace(format, "hh", "03", 1)
+		format = strings.Replace(format, "h", "3", 1)
+	} else {
+		format = strings.Replace(format, "hh", "15", 1)
+		format = strings.Replace(format, "h", "15", 1)
+	}
+	for _, repl := range replacements {
+		format = strings.Replace(format, repl.xltime, repl.gotime, 1)
+	}
+	// If the hour is optional, strip it out, along with the
+	// possible dangling colon that would remain.
+	if val.Hour() < 1 {
+		format = strings.Replace(format, "]:", "]", 1)
+		format = strings.Replace(format, "[03]", "", 1)
+		format = strings.Replace(format, "[3]", "", 1)
+		format = strings.Replace(format, "[15]", "", 1)
+	} else {
+		format = strings.Replace(format, "[3]", "3", 1)
+		format = strings.Replace(format, "[15]", "15", 1)
+	}
+	return val.Format(format), nil
+}
+
+// isTimeFormat checks whether an Excel format string represents a time.Time.
+// This function is now correct, but it can detect time format strings that cannot be correctly handled by parseTime()
+func isTimeFormat(format string) bool {
+	var foundTimeFormatCharacters bool
+	for i := 0; i < len(format); i++ {
+		curReducedFormat := format[i:]
+		switch curReducedFormat[0] {
+		case '\\', '_':
+			// If there is a slash, skip the next character, and add it to the prefix
+			// If there is an underscore, skip the next character, but don't add it to the prefix
+			if len(curReducedFormat) > 1 {
+				i++
+			}
+		case '*':
+			// Asterisks are used to repeat the next character to fill the full cell width.
+			// There isn't really a cell size in this context, so this will be ignored.
+		case '"':
+			// If there is a quote skip to the next quote, and add the quoted characters to the prefix
+			endQuoteIndex := strings.Index(curReducedFormat[1:], "\"")
+			if endQuoteIndex == -1 {
+				// This is not any type of valid format.
+				return false
+			}
+			i += endQuoteIndex + 1
+		case '$', '-', '+', '/', '(', ')', ':', '!', '^', '&', '\'', '~', '{', '}', '<', '>', '=', ' ':
+			// These symbols are allowed to be used as literal without escaping
+		case ',':
+			// This is not documented in the XLSX spec as far as I can tell, but Excel and Numbers will include
+			// commas in number formats without escaping them, so this should be supported.
+		default:
+			foundInThisLoop := false
+			for _, special := range timeFormatCharacters {
+				if strings.HasPrefix(curReducedFormat, special) {
+					foundTimeFormatCharacters = true
+					foundInThisLoop = true
+					i += len(special) - 1
+					break
+				}
+			}
+			if foundInThisLoop {
+				continue
+			}
+			if curReducedFormat[0] == '[' {
+				// For number formats, this code would happen above in a case '[': section.
+				// However, for time formats it must happen after looking for occurrences in timeFormatCharacters
+				// because there are a few time formats that can be wrapped in brackets.
+
+				// Brackets can be currency annotations (e.g. [$$-409])
+				// color formats (e.g. [color1] through [color56], as well as [red] etc.)
+				// conditionals (e.g. [>100], the valid conditionals are =, >, <, >=, <=, <>)
+				bracketIndex := strings.Index(curReducedFormat, "]")
+				if bracketIndex == -1 {
+					// This is not any type of valid format.
+					return false
+				}
+				i += bracketIndex
+				continue
+			}
+			// Symbols that don't have meaning, aren't in the exempt literal characters, and aren't escaped are invalid.
+			// The string could still be a valid number format string.
+			return false
+		}
+	}
+	// If the string doesn't have any time formatting characters, it could technically be a time format, but it
+	// would be a pretty weak time format. A valid time format with no time formatting symbols will also be a number
+	// format with no number formatting symbols, which is essentially a constant string that does not depend on the
+	// cell's value in anyway. The downstream logic will do the right thing in that case if this returns false.
+	return foundTimeFormatCharacters
+}
+
+// is12HourTime checks whether an Excel time format string is a 12
+// hours form.
+func is12HourTime(format string) bool {
+	return strings.Contains(format, "am/pm") || strings.Contains(format, "AM/PM") || strings.Contains(format, "a/p") || strings.Contains(format, "A/P")
+}

+ 136 - 72
vendor/github.com/tealeg/xlsx/lib.go

@@ -2,6 +2,7 @@ package xlsx
 
 
 import (
 import (
 	"archive/zip"
 	"archive/zip"
+	"bytes"
 	"encoding/xml"
 	"encoding/xml"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
@@ -11,6 +12,10 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
+const (
+	sheetEnding = `</sheetData></worksheet>`
+)
+
 // XLSXReaderError is the standard error type for otherwise undefined
 // XLSXReaderError is the standard error type for otherwise undefined
 // errors in the XSLX reading process.
 // errors in the XSLX reading process.
 type XLSXReaderError struct {
 type XLSXReaderError struct {
@@ -25,7 +30,7 @@ func (e *XLSXReaderError) Error() string {
 
 
 // getRangeFromString is an internal helper function that converts
 // getRangeFromString is an internal helper function that converts
 // XLSX internal range syntax to a pair of integers.  For example,
 // XLSX internal range syntax to a pair of integers.  For example,
-// the range string "1:3" yield the upper and lower intergers 1 and 3.
+// the range string "1:3" yield the upper and lower integers 1 and 3.
 func getRangeFromString(rangeString string) (lower int, upper int, error error) {
 func getRangeFromString(rangeString string) (lower int, upper int, error error) {
 	var parts []string
 	var parts []string
 	parts = strings.SplitN(rangeString, ":", 2)
 	parts = strings.SplitN(rangeString, ":", 2)
@@ -46,9 +51,9 @@ func getRangeFromString(rangeString string) (lower int, upper int, error error)
 	return lower, upper, error
 	return lower, upper, error
 }
 }
 
 
-// lettersToNumeric is used to convert a character based column
+// ColLettersToIndex is used to convert a character based column
 // reference to a zero based numeric column identifier.
 // reference to a zero based numeric column identifier.
-func lettersToNumeric(letters string) int {
+func ColLettersToIndex(letters string) int {
 	sum, mul, n := 0, 1, 0
 	sum, mul, n := 0, 1, 0
 	for i := len(letters) - 1; i >= 0; i, mul, n = i-1, mul*26, 1 {
 	for i := len(letters) - 1; i >= 0; i, mul, n = i-1, mul*26, 1 {
 		c := letters[i]
 		c := letters[i]
@@ -134,9 +139,9 @@ func intToBase26(x int) (parts []int) {
 	return parts
 	return parts
 }
 }
 
 
-// numericToLetters is used to convert a zero based, numeric column
+// ColIndexToLetters is used to convert a zero based, numeric column
 // indentifier into a character code.
 // indentifier into a character code.
-func numericToLetters(colRef int) string {
+func ColIndexToLetters(colRef int) string {
 	parts := intToBase26(colRef)
 	parts := intToBase26(colRef)
 	return formatColumnName(smooshBase26Slice(parts))
 	return formatColumnName(smooshBase26Slice(parts))
 }
 }
@@ -172,14 +177,14 @@ func GetCoordsFromCellIDString(cellIDString string) (x, y int, error error) {
 		return x, y, error
 		return x, y, error
 	}
 	}
 	y -= 1 // Zero based
 	y -= 1 // Zero based
-	x = lettersToNumeric(letterPart)
+	x = ColLettersToIndex(letterPart)
 	return x, y, error
 	return x, y, error
 }
 }
 
 
 // GetCellIDStringFromCoords returns the Excel format cell name that
 // GetCellIDStringFromCoords returns the Excel format cell name that
 // represents a pair of zero based cartesian coordinates.
 // represents a pair of zero based cartesian coordinates.
 func GetCellIDStringFromCoords(x, y int) string {
 func GetCellIDStringFromCoords(x, y int) string {
-	letterPart := numericToLetters(x)
+	letterPart := ColIndexToLetters(x)
 	numericPart := y + 1
 	numericPart := y + 1
 	return fmt.Sprintf("%s%d", letterPart, numericPart)
 	return fmt.Sprintf("%s%d", letterPart, numericPart)
 }
 }
@@ -205,6 +210,7 @@ func getMaxMinFromDimensionRef(ref string) (minx, miny, maxx, maxy int, err erro
 // calculateMaxMinFromWorkSheet works out the dimensions of a spreadsheet
 // calculateMaxMinFromWorkSheet works out the dimensions of a spreadsheet
 // that doesn't have a DimensionRef set.  The only case currently
 // that doesn't have a DimensionRef set.  The only case currently
 // known where this is true is with XLSX exported from Google Docs.
 // known where this is true is with XLSX exported from Google Docs.
+// This is also true for XLSX files created through the streaming APIs.
 func calculateMaxMinFromWorksheet(worksheet *xlsxWorksheet) (minx, miny, maxx, maxy int, err error) {
 func calculateMaxMinFromWorksheet(worksheet *xlsxWorksheet) (minx, miny, maxx, maxy int, err error) {
 	// Note, this method could be very slow for large spreadsheets.
 	// Note, this method could be very slow for large spreadsheets.
 	var x, y int
 	var x, y int
@@ -437,53 +443,56 @@ func shiftCell(cellID string, dx, dy int) string {
 // fillCellData attempts to extract a valid value, usable in
 // fillCellData attempts to extract a valid value, usable in
 // CSV form from the raw cell value.  Note - this is not actually
 // CSV form from the raw cell value.  Note - this is not actually
 // general enough - we should support retaining tabs and newlines.
 // general enough - we should support retaining tabs and newlines.
-func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]sharedFormula, cell *Cell) {
-	var data = rawcell.V
-	if len(data) > 0 {
-		vval := strings.Trim(data, " \t\n\r")
-		switch rawcell.T {
-		case "s": // Shared String
-			ref, error := strconv.Atoi(vval)
-			if error != nil {
-				panic(error)
-			}
-			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 = formulaForCell(rawcell, sharedFormulas)
-			cell.cellType = CellTypeError
-		default:
-			if rawcell.F == nil {
-				// Numeric
-				cell.Value = vval
-				cell.cellType = CellTypeNumeric
-			} else {
-				// Formula
-				cell.Value = vval
-				cell.formula = formulaForCell(rawcell, sharedFormulas)
-				cell.cellType = CellTypeFormula
+func fillCellData(rawCell xlsxC, refTable *RefTable, sharedFormulas map[int]sharedFormula, cell *Cell) {
+	val := strings.Trim(rawCell.V, " \t\n\r")
+	cell.formula = formulaForCell(rawCell, sharedFormulas)
+	switch rawCell.T {
+	case "s": // Shared String
+		cell.cellType = CellTypeString
+		if val != "" {
+			ref, err := strconv.Atoi(val)
+			if err != nil {
+				panic(err)
 			}
 			}
+			cell.Value = refTable.ResolveSharedString(ref)
 		}
 		}
-	} else {
-		if rawcell.Is != nil {
-			fillCellDataFromInlineString(rawcell, cell)
-		}
+	case "inlineStr":
+		cell.cellType = CellTypeInline
+		fillCellDataFromInlineString(rawCell, cell)
+	case "b": // Boolean
+		cell.Value = val
+		cell.cellType = CellTypeBool
+	case "e": // Error
+		cell.Value = val
+		cell.cellType = CellTypeError
+	case "str":
+		// String Formula (special type for cells with formulas that return a string value)
+		// Unlike the other string cell types, the string is stored directly in the value.
+		cell.Value = val
+		cell.cellType = CellTypeStringFormula
+	case "d": // Date: Cell contains a date in the ISO 8601 format.
+		cell.Value = val
+		cell.cellType = CellTypeDate
+	case "": // Numeric is the default
+		fallthrough
+	case "n": // Numeric
+		cell.Value = val
+		cell.cellType = CellTypeNumeric
+	default:
+		panic(errors.New("invalid cell type"))
 	}
 	}
 }
 }
 
 
 // fillCellDataFromInlineString attempts to get inline string data and put it into a Cell.
 // fillCellDataFromInlineString attempts to get inline string data and put it into a Cell.
 func fillCellDataFromInlineString(rawcell xlsxC, cell *Cell) {
 func fillCellDataFromInlineString(rawcell xlsxC, cell *Cell) {
-	if rawcell.Is.T != "" {
-		cell.Value = strings.Trim(rawcell.Is.T, " \t\n\r")
-		cell.cellType = CellTypeInline
-	} else {
-		cell.Value = ""
-		for _, r := range rawcell.Is.R {
-			cell.Value += r.T
+	cell.Value = ""
+	if rawcell.Is != nil {
+		if rawcell.Is.T != "" {
+			cell.Value = strings.Trim(rawcell.Is.T, " \t\n\r")
+		} else {
+			for _, r := range rawcell.Is.R {
+				cell.Value += r.T
+			}
 		}
 		}
 	}
 	}
 }
 }
@@ -492,11 +501,11 @@ func fillCellDataFromInlineString(rawcell xlsxC, cell *Cell) {
 // rows from a XSLXWorksheet, populates 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 value references from the reference table and stores them in
 // the rows and columns.
 // the rows and columns.
-func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*Row, []*Col, int, int) {
+func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet, rowLimit int) ([]*Row, []*Col, int, int) {
 	var rows []*Row
 	var rows []*Row
 	var cols []*Col
 	var cols []*Col
 	var row *Row
 	var row *Row
-	var minCol, maxCol, minRow, maxRow, colCount, rowCount int
+	var minCol, maxCol, maxRow, colCount, rowCount int
 	var reftable *RefTable
 	var reftable *RefTable
 	var err error
 	var err error
 	var insertRowIndex, insertColIndex int
 	var insertRowIndex, insertColIndex int
@@ -506,10 +515,10 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 		return nil, nil, 0, 0
 		return nil, nil, 0, 0
 	}
 	}
 	reftable = file.referenceTable
 	reftable = file.referenceTable
-	if len(Worksheet.Dimension.Ref) > 0 && len(strings.Split(Worksheet.Dimension.Ref, ":")) == 2 {
-		minCol, minRow, maxCol, maxRow, err = getMaxMinFromDimensionRef(Worksheet.Dimension.Ref)
+	if len(Worksheet.Dimension.Ref) > 0 && len(strings.Split(Worksheet.Dimension.Ref, ":")) == 2 && rowLimit == NoRowLimit {
+		minCol, _, maxCol, maxRow, err = getMaxMinFromDimensionRef(Worksheet.Dimension.Ref)
 	} else {
 	} else {
-		minCol, minRow, maxCol, maxRow, err = calculateMaxMinFromWorksheet(Worksheet)
+		minCol, _, maxCol, maxRow, err = calculateMaxMinFromWorksheet(Worksheet)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		panic(err.Error())
 		panic(err.Error())
@@ -519,7 +528,6 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 	colCount = maxCol + 1
 	colCount = maxCol + 1
 	rows = make([]*Row, rowCount)
 	rows = make([]*Row, rowCount)
 	cols = make([]*Col, colCount)
 	cols = make([]*Col, colCount)
-	insertRowIndex = minRow
 	for i := range cols {
 	for i := range cols {
 		cols[i] = &Col{
 		cols[i] = &Col{
 			Hidden: false,
 			Hidden: false,
@@ -544,17 +552,12 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 				cols[i-1] = col
 				cols[i-1] = col
 				if file.styles != nil {
 				if file.styles != nil {
 					col.style = file.styles.getStyle(rawcol.Style)
 					col.style = file.styles.getStyle(rawcol.Style)
-					col.numFmt = file.styles.getNumberFormat(rawcol.Style)
+					col.numFmt, col.parsedNumFmt = file.styles.getNumberFormat(rawcol.Style)
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	// insert leading empty rows that is in front of minRow
-	for rowIndex := 0; rowIndex < minRow; rowIndex++ {
-		rows[rowIndex] = makeEmptyRow(sheet)
-	}
-
 	numRows := len(rows)
 	numRows := len(rows)
 	for rowIndex := 0; rowIndex < len(Worksheet.SheetData.Row); rowIndex++ {
 	for rowIndex := 0; rowIndex < len(Worksheet.SheetData.Row); rowIndex++ {
 		rawrow := Worksheet.SheetData.Row[rowIndex]
 		rawrow := Worksheet.SheetData.Row[rowIndex]
@@ -611,7 +614,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 				fillCellData(rawcell, reftable, sharedFormulas, cell)
 				fillCellData(rawcell, reftable, sharedFormulas, cell)
 				if file.styles != nil {
 				if file.styles != nil {
 					cell.style = file.styles.getStyle(rawcell.S)
 					cell.style = file.styles.getStyle(rawcell.S)
-					cell.NumFmt = file.styles.getNumberFormat(rawcell.S)
+					cell.NumFmt, cell.parsedNumFmt = file.styles.getNumberFormat(rawcell.S)
 				}
 				}
 				cell.date1904 = file.Date1904
 				cell.date1904 = file.Date1904
 				// Cell is considered hidden if the row or the column of this cell is hidden
 				// Cell is considered hidden if the row or the column of this cell is hidden
@@ -624,6 +627,11 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 		}
 		}
 		insertRowIndex++
 		insertRowIndex++
 	}
 	}
+
+	// insert trailing empty rows for the rest of the file
+	for ; insertRowIndex < rowCount; insertRowIndex++ {
+		rows[insertRowIndex] = makeEmptyRow(sheet)
+	}
 	return rows, cols, colCount, rowCount
 	return rows, cols, colCount, rowCount
 }
 }
 
 
@@ -659,11 +667,10 @@ func readSheetViews(xSheetViews xlsxSheetViews) []SheetView {
 // into a Sheet struct.  This work can be done in parallel and so
 // into a Sheet struct.  This work can be done in parallel and so
 // readSheetsFromZipFile will spawn an instance of this function per
 // readSheetsFromZipFile will spawn an instance of this function per
 // sheet and get the results back on the provided channel.
 // sheet and get the results back on the provided channel.
-func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *File, sheetXMLMap map[string]string) (errRes error) {
+func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *File, sheetXMLMap map[string]string, rowLimit int) (errRes error) {
 	result := &indexedSheet{Index: index, Sheet: nil, Error: nil}
 	result := &indexedSheet{Index: index, Sheet: nil, Error: nil}
 	defer func() {
 	defer func() {
 		if e := recover(); e != nil {
 		if e := recover(); e != nil {
-
 			switch e.(type) {
 			switch e.(type) {
 			case error:
 			case error:
 				result.Error = e.(error)
 				result.Error = e.(error)
@@ -676,15 +683,15 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 		}
 		}
 	}()
 	}()
 
 
-	worksheet, error := getWorksheetFromSheet(rsheet, fi.worksheets, sheetXMLMap)
-	if error != nil {
-		result.Error = error
+	worksheet, err := getWorksheetFromSheet(rsheet, fi.worksheets, sheetXMLMap, rowLimit)
+	if err != nil {
+		result.Error = err
 		sc <- result
 		sc <- result
-		return error
+		return err
 	}
 	}
 	sheet := new(Sheet)
 	sheet := new(Sheet)
 	sheet.File = fi
 	sheet.File = fi
-	sheet.Rows, sheet.Cols, sheet.MaxCol, sheet.MaxRow = readRowsFromSheet(worksheet, fi, sheet)
+	sheet.Rows, sheet.Cols, sheet.MaxCol, sheet.MaxRow = readRowsFromSheet(worksheet, fi, sheet, rowLimit)
 	sheet.Hidden = rsheet.State == sheetStateHidden || rsheet.State == sheetStateVeryHidden
 	sheet.Hidden = rsheet.State == sheetStateHidden || rsheet.State == sheetStateVeryHidden
 	sheet.SheetViews = readSheetViews(worksheet.SheetViews)
 	sheet.SheetViews = readSheetViews(worksheet.SheetViews)
 
 
@@ -701,7 +708,7 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 // readSheetsFromZipFile is an internal helper function that loops
 // readSheetsFromZipFile is an internal helper function that loops
 // over the Worksheets defined in the XSLXWorkbook and loads them into
 // over the Worksheets defined in the XSLXWorkbook and loads them into
 // Sheet objects stored in the Sheets slice of a xlsx.File struct.
 // 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) {
+func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]string, rowLimit int) (map[string]*Sheet, []*Sheet, error) {
 	var workbook *xlsxWorkbook
 	var workbook *xlsxWorkbook
 	var err error
 	var err error
 	var rc io.ReadCloser
 	var rc io.ReadCloser
@@ -740,7 +747,7 @@ func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]strin
 		defer close(sheetChan)
 		defer close(sheetChan)
 		err = nil
 		err = nil
 		for i, rawsheet := range workbookSheets {
 		for i, rawsheet := range workbookSheets {
-			if err := readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap); err != nil {
+			if err := readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap, rowLimit); err != nil {
 				return
 				return
 			}
 			}
 		}
 		}
@@ -909,13 +916,28 @@ func readWorkbookRelationsFromZipFile(workbookRels *zip.File) (WorkBookRels, err
 // xlsx.File struct populated with its contents.  In most cases
 // xlsx.File struct populated with its contents.  In most cases
 // ReadZip is not used directly, but is called internally by OpenFile.
 // ReadZip is not used directly, but is called internally by OpenFile.
 func ReadZip(f *zip.ReadCloser) (*File, error) {
 func ReadZip(f *zip.ReadCloser) (*File, error) {
+	return ReadZipWithRowLimit(f, NoRowLimit)
+}
+
+// ReadZipWithRowLimit() takes a pointer to a zip.ReadCloser and returns a
+// xlsx.File struct populated with its contents.  In most cases
+// ReadZip is not used directly, but is called internally by OpenFile.
+func ReadZipWithRowLimit(f *zip.ReadCloser, rowLimit int) (*File, error) {
 	defer f.Close()
 	defer f.Close()
-	return ReadZipReader(&f.Reader)
+	return ReadZipReaderWithRowLimit(&f.Reader, rowLimit)
 }
 }
 
 
 // ReadZipReader() can be used to read an XLSX in memory without
 // ReadZipReader() can be used to read an XLSX in memory without
 // touching the filesystem.
 // touching the filesystem.
 func ReadZipReader(r *zip.Reader) (*File, error) {
 func ReadZipReader(r *zip.Reader) (*File, error) {
+	return ReadZipReaderWithRowLimit(r, NoRowLimit)
+}
+
+// ReadZipReaderWithRowLimit() can be used to read an XLSX in memory without
+// touching the filesystem.
+// rowLimit is the number of rows that should be read from the file. If rowLimit is -1, no limit is applied.
+// You can specify this with the constant NoRowLimit.
+func ReadZipReaderWithRowLimit(r *zip.Reader, rowLimit int) (*File, error) {
 	var err error
 	var err error
 	var file *File
 	var file *File
 	var reftable *RefTable
 	var reftable *RefTable
@@ -947,7 +969,7 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 		case "xl/theme/theme1.xml":
 		case "xl/theme/theme1.xml":
 			themeFile = v
 			themeFile = v
 		default:
 		default:
-			if len(v.Name) > 14 {
+			if len(v.Name) > 17 {
 				if v.Name[0:13] == "xl/worksheets" {
 				if v.Name[0:13] == "xl/worksheets" {
 					worksheets[v.Name[14:len(v.Name)-4]] = v
 					worksheets[v.Name[14:len(v.Name)-4]] = v
 				}
 				}
@@ -986,7 +1008,7 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 
 
 		file.styles = style
 		file.styles = style
 	}
 	}
-	sheetsByName, sheets, err = readSheetsFromZipFile(workbook, file, sheetXMLMap)
+	sheetsByName, sheets, err = readSheetsFromZipFile(workbook, file, sheetXMLMap, rowLimit)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -999,3 +1021,45 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 	file.Sheets = sheets
 	file.Sheets = sheets
 	return file, nil
 	return file, nil
 }
 }
+
+// truncateSheetXML will take in a reader to an XML sheet file and will return a reader that will read an equivalent
+// XML sheet file with only the number of rows specified. This greatly speeds up XML unmarshalling when only
+// a few rows need to be read from a large sheet.
+// When sheets are truncated, all formatting present after the sheetData tag will be lost, but all of this formatting
+// is related to printing and visibility, and is out of scope for most purposes of this library.
+func truncateSheetXML(r io.Reader, rowLimit int) (io.Reader, error) {
+	var rowCount int
+	var token xml.Token
+	var readErr error
+
+	output := new(bytes.Buffer)
+	r = io.TeeReader(r, output)
+	decoder := xml.NewDecoder(r)
+
+	for {
+		token, readErr = decoder.Token()
+		if readErr == io.EOF {
+			break
+		} else if readErr != nil {
+			return nil, readErr
+		}
+		end, ok := token.(xml.EndElement)
+		if ok && end.Name.Local == "row" {
+			rowCount++
+			if rowCount >= rowLimit {
+				break
+			}
+		}
+	}
+
+	offset := decoder.InputOffset()
+	output.Truncate(int(offset))
+
+	if readErr != io.EOF {
+		_, err := output.Write([]byte(sheetEnding))
+		if err != nil {
+			return nil, err
+		}
+	}
+	return output, nil
+}

+ 29 - 26
vendor/github.com/tealeg/xlsx/sheet.go

@@ -1,6 +1,7 @@
 package xlsx
 package xlsx
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"strconv"
 	"strconv"
 )
 )
@@ -129,7 +130,7 @@ func (s *Sheet) handleMerged() {
 	for r, row := range s.Rows {
 	for r, row := range s.Rows {
 		for c, cell := range row.Cells {
 		for c, cell := range row.Cells {
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 			if cell.HMerge > 0 || cell.VMerge > 0 {
-				coord := fmt.Sprintf("%s%d", numericToLetters(c), r+1)
+				coord := GetCellIDStringFromCoords(c, r)
 				merged[coord] = cell
 				merged[coord] = cell
 			}
 			}
 		}
 		}
@@ -280,44 +281,47 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			style := cell.style
 			style := cell.style
 			if style != nil {
 			if style != nil {
 				XfId = handleStyleForXLSX(style, xNumFmt.NumFmtId, styles)
 				XfId = handleStyleForXLSX(style, xNumFmt.NumFmtId, styles)
-			} else if len(cell.NumFmt) > 0 && s.Cols[c].numFmt != cell.NumFmt {
+			} else if len(cell.NumFmt) > 0 && !compareFormatString(s.Cols[c].numFmt, cell.NumFmt) {
 				XfId = handleNumFmtIdForXLSX(xNumFmt.NumFmtId, styles)
 				XfId = handleNumFmtIdForXLSX(xNumFmt.NumFmtId, styles)
 			}
 			}
 
 
 			if c > maxCell {
 			if c > maxCell {
 				maxCell = c
 				maxCell = c
 			}
 			}
-			xC := xlsxC{}
-			xC.R = fmt.Sprintf("%s%d", numericToLetters(c), r+1)
+			xC := xlsxC{
+				S: XfId,
+				R: GetCellIDStringFromCoords(c, r),
+			}
+			if cell.formula != "" {
+				xC.F = &xlsxF{Content: cell.formula}
+			}
 			switch cell.cellType {
 			switch cell.cellType {
+			case CellTypeInline:
+				// Inline strings are turned into shared strings since they are more efficient.
+				// This is what Excel does as well.
+				fallthrough
 			case CellTypeString:
 			case CellTypeString:
 				if len(cell.Value) > 0 {
 				if len(cell.Value) > 0 {
 					xC.V = strconv.Itoa(refTable.AddString(cell.Value))
 					xC.V = strconv.Itoa(refTable.AddString(cell.Value))
 				}
 				}
 				xC.T = "s"
 				xC.T = "s"
-				xC.S = XfId
-			case CellTypeBool:
-				xC.V = cell.Value
-				xC.T = "b"
-				xC.S = XfId
 			case CellTypeNumeric:
 			case CellTypeNumeric:
+				// Numeric is the default, so the type can be left blank
 				xC.V = cell.Value
 				xC.V = cell.Value
-				xC.S = XfId
-			case CellTypeDate:
-				xC.V = cell.Value
-				xC.S = XfId
-			case CellTypeFormula:
+			case CellTypeBool:
 				xC.V = cell.Value
 				xC.V = cell.Value
-				xC.F = &xlsxF{Content: cell.formula}
-				xC.S = XfId
+				xC.T = "b"
 			case CellTypeError:
 			case CellTypeError:
 				xC.V = cell.Value
 				xC.V = cell.Value
-				xC.F = &xlsxF{Content: cell.formula}
 				xC.T = "e"
 				xC.T = "e"
-				xC.S = XfId
-			case CellTypeGeneral:
+			case CellTypeDate:
+				xC.V = cell.Value
+				xC.T = "d"
+			case CellTypeStringFormula:
 				xC.V = cell.Value
 				xC.V = cell.Value
-				xC.S = XfId
+				xC.T = "str"
+			default:
+				panic(errors.New("unknown cell type cannot be marshaled"))
 			}
 			}
 
 
 			xRow.C = append(xRow.C, xC)
 			xRow.C = append(xRow.C, xC)
@@ -325,10 +329,10 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 			if cell.HMerge > 0 || cell.VMerge > 0 {
 				// r == rownum, c == colnum
 				// r == rownum, c == colnum
 				mc := xlsxMergeCell{}
 				mc := xlsxMergeCell{}
-				start := fmt.Sprintf("%s%d", numericToLetters(c), r+1)
-				endcol := c + cell.HMerge
-				endrow := r + cell.VMerge + 1
-				end := fmt.Sprintf("%s%d", numericToLetters(endcol), endrow)
+				start := GetCellIDStringFromCoords(c, r)
+				endCol := c + cell.HMerge
+				endRow := r + cell.VMerge
+				end := GetCellIDStringFromCoords(endCol, endRow)
 				mc.Ref = start + ":" + end
 				mc.Ref = start + ":" + end
 				if worksheet.MergeCells == nil {
 				if worksheet.MergeCells == nil {
 					worksheet.MergeCells = &xlsxMergeCells{}
 					worksheet.MergeCells = &xlsxMergeCells{}
@@ -356,8 +360,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 
 
 	worksheet.SheetData = xSheet
 	worksheet.SheetData = xSheet
 	dimension := xlsxDimension{}
 	dimension := xlsxDimension{}
-	dimension.Ref = fmt.Sprintf("A1:%s%d",
-		numericToLetters(maxCell), maxRow+1)
+	dimension.Ref = "A1:" + GetCellIDStringFromCoords(maxCell, maxRow)
 	if dimension.Ref == "A1:A1" {
 	if dimension.Ref == "A1:A1" {
 		dimension.Ref = "A1"
 		dimension.Ref = "A1"
 	}
 	}

+ 217 - 0
vendor/github.com/tealeg/xlsx/stream_file.go

@@ -0,0 +1,217 @@
+package xlsx
+
+import (
+	"archive/zip"
+	"encoding/xml"
+	"errors"
+	"io"
+	"strconv"
+)
+
+type StreamFile struct {
+	xlsxFile       *File
+	sheetXmlPrefix []string
+	sheetXmlSuffix []string
+	zipWriter      *zip.Writer
+	currentSheet   *streamSheet
+	styleIds       [][]int
+	err            error
+}
+
+type streamSheet struct {
+	// sheetIndex is the XLSX sheet index, which starts at 1
+	index int
+	// The number of rows that have been written to the sheet so far
+	rowCount int
+	// The number of columns in the sheet
+	columnCount int
+	// The writer to write to this sheet's file in the XLSX Zip file
+	writer   io.Writer
+	styleIds []int
+}
+
+var (
+	NoCurrentSheetError     = errors.New("no Current Sheet")
+	WrongNumberOfRowsError  = errors.New("invalid number of cells passed to Write. All calls to Write on the same sheet must have the same number of cells")
+	AlreadyOnLastSheetError = errors.New("NextSheet() called, but already on last sheet")
+)
+
+// Write will write a row of cells to the current sheet. Every call to Write on the same sheet must contain the
+// same number of cells as the header provided when the sheet was created or an error will be returned. This function
+// will always trigger a flush on success. Currently the only supported data type is string data.
+func (sf *StreamFile) Write(cells []string) error {
+	if sf.err != nil {
+		return sf.err
+	}
+	err := sf.write(cells)
+	if err != nil {
+		sf.err = err
+		return err
+	}
+	return sf.zipWriter.Flush()
+}
+
+func (sf *StreamFile) WriteAll(records [][]string) error {
+	if sf.err != nil {
+		return sf.err
+	}
+	for _, row := range records {
+		err := sf.write(row)
+		if err != nil {
+			sf.err = err
+			return err
+		}
+	}
+	return sf.zipWriter.Flush()
+}
+
+func (sf *StreamFile) write(cells []string) error {
+	if sf.currentSheet == nil {
+		return NoCurrentSheetError
+	}
+	if len(cells) != sf.currentSheet.columnCount {
+		return WrongNumberOfRowsError
+	}
+	sf.currentSheet.rowCount++
+	if err := sf.currentSheet.write(`<row r="` + strconv.Itoa(sf.currentSheet.rowCount) + `">`); err != nil {
+		return err
+	}
+	for colIndex, cellData := range cells {
+		// documentation for the c.t (cell.Type) attribute:
+		// b (Boolean): Cell containing a boolean.
+		// d (Date): Cell contains a date in the ISO 8601 format.
+		// e (Error): Cell containing an error.
+		// inlineStr (Inline String): Cell containing an (inline) rich string, i.e., one not in the shared string table.
+		// If this cell type is used, then the cell value is in the is element rather than the v element in the cell (c element).
+		// n (Number): Cell containing a number.
+		// s (Shared String): Cell containing a shared string.
+		// str (String): Cell containing a formula string.
+		cellCoordinate := GetCellIDStringFromCoords(colIndex, sf.currentSheet.rowCount-1)
+		cellType := "inlineStr"
+		cellOpen := `<c r="` + cellCoordinate + `" t="` + cellType + `"`
+		// Add in the style id if the cell isn't using the default style
+		if colIndex < len(sf.currentSheet.styleIds) && sf.currentSheet.styleIds[colIndex] != 0 {
+			cellOpen += ` s="` + strconv.Itoa(sf.currentSheet.styleIds[colIndex]) + `"`
+		}
+		cellOpen += `><is><t>`
+		cellClose := `</t></is></c>`
+
+		if err := sf.currentSheet.write(cellOpen); err != nil {
+			return err
+		}
+		if err := xml.EscapeText(sf.currentSheet.writer, []byte(cellData)); err != nil {
+			return err
+		}
+		if err := sf.currentSheet.write(cellClose); err != nil {
+			return err
+		}
+	}
+	if err := sf.currentSheet.write(`</row>`); err != nil {
+		return err
+	}
+	return sf.zipWriter.Flush()
+}
+
+// Error reports any error that has occurred during a previous Write or Flush.
+func (sf *StreamFile) Error() error {
+	return sf.err
+}
+
+func (sf *StreamFile) Flush() {
+	if sf.err != nil {
+		sf.err = sf.zipWriter.Flush()
+	}
+}
+
+// NextSheet will switch to the next sheet. Sheets are selected in the same order they were added.
+// Once you leave a sheet, you cannot return to it.
+func (sf *StreamFile) NextSheet() error {
+	if sf.err != nil {
+		return sf.err
+	}
+	var sheetIndex int
+	if sf.currentSheet != nil {
+		if sf.currentSheet.index >= len(sf.xlsxFile.Sheets) {
+			sf.err = AlreadyOnLastSheetError
+			return AlreadyOnLastSheetError
+		}
+		if err := sf.writeSheetEnd(); err != nil {
+			sf.currentSheet = nil
+			sf.err = err
+			return err
+		}
+		sheetIndex = sf.currentSheet.index
+	}
+	sheetIndex++
+	sf.currentSheet = &streamSheet{
+		index:       sheetIndex,
+		columnCount: len(sf.xlsxFile.Sheets[sheetIndex-1].Cols),
+		styleIds:    sf.styleIds[sheetIndex-1],
+		rowCount:    1,
+	}
+	sheetPath := sheetFilePathPrefix + strconv.Itoa(sf.currentSheet.index) + sheetFilePathSuffix
+	fileWriter, err := sf.zipWriter.Create(sheetPath)
+	if err != nil {
+		sf.err = err
+		return err
+	}
+	sf.currentSheet.writer = fileWriter
+
+	if err := sf.writeSheetStart(); err != nil {
+		sf.err = err
+		return err
+	}
+	return nil
+}
+
+// Close closes the Stream File.
+// Any sheets that have not yet been written to will have an empty sheet created for them.
+func (sf *StreamFile) Close() error {
+	if sf.err != nil {
+		return sf.err
+	}
+	// If there are sheets that have not been written yet, call NextSheet() which will add files to the zip for them.
+	// XLSX readers may error if the sheets registered in the metadata are not present in the file.
+	if sf.currentSheet != nil {
+		for sf.currentSheet.index < len(sf.xlsxFile.Sheets) {
+			if err := sf.NextSheet(); err != nil {
+				sf.err = err
+				return err
+			}
+		}
+		// Write the end of the last sheet.
+		if err := sf.writeSheetEnd(); err != nil {
+			sf.err = err
+			return err
+		}
+	}
+	err := sf.zipWriter.Close()
+	if err != nil {
+		sf.err = err
+	}
+	return err
+}
+
+// writeSheetStart will write the start of the Sheet's XML
+func (sf *StreamFile) writeSheetStart() error {
+	if sf.currentSheet == nil {
+		return NoCurrentSheetError
+	}
+	return sf.currentSheet.write(sf.sheetXmlPrefix[sf.currentSheet.index-1])
+}
+
+// writeSheetEnd will write the end of the Sheet's XML
+func (sf *StreamFile) writeSheetEnd() error {
+	if sf.currentSheet == nil {
+		return NoCurrentSheetError
+	}
+	if err := sf.currentSheet.write(endSheetDataTag); err != nil {
+		return err
+	}
+	return sf.currentSheet.write(sf.sheetXmlSuffix[sf.currentSheet.index-1])
+}
+
+func (ss *streamSheet) write(data string) error {
+	_, err := ss.writer.Write([]byte(data))
+	return err
+}

+ 245 - 0
vendor/github.com/tealeg/xlsx/stream_file_builder.go

@@ -0,0 +1,245 @@
+// Authors: Ryan Hollis (ryanh@)
+
+// The purpose of StreamFileBuilder and StreamFile is to allow streamed writing of XLSX files.
+// Directions:
+// 1. Create a StreamFileBuilder with NewStreamFileBuilder() or NewStreamFileBuilderForPath().
+// 2. Add the sheets and their first row of data by calling AddSheet().
+// 3. Call Build() to get a StreamFile. Once built, all functions on the builder will return an error.
+// 4. Write to the StreamFile with Write(). Writes begin on the first sheet. New rows are always written and flushed
+// to the io. All rows written to the same sheet must have the same number of cells as the header provided when the sheet
+// was created or an error will be returned.
+// 5. Call NextSheet() to proceed to the next sheet. Once NextSheet() is called, the previous sheet can not be edited.
+// 6. Call Close() to finish.
+
+// Future work suggestions:
+// Currently the only supported cell type is string, since the main reason this library was written was to prevent
+// strings from being interpreted as numbers. It would be nice to have support for numbers and money so that the exported
+// files could better take advantage of XLSX's features.
+// All text is written with the same text style. Support for additional text styles could be added to highlight certain
+// data in the file.
+// The current default style uses fonts that are not on Macs by default so opening the XLSX files in Numbers causes a
+// pop up that says there are missing fonts. The font could be changed to something that is usually found on Mac and PC.
+
+package xlsx
+
+import (
+	"archive/zip"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+)
+
+type StreamFileBuilder struct {
+	built              bool
+	xlsxFile           *File
+	zipWriter          *zip.Writer
+	cellTypeToStyleIds map[CellType]int
+	maxStyleId         int
+	styleIds           [][]int
+}
+
+const (
+	sheetFilePathPrefix = "xl/worksheets/sheet"
+	sheetFilePathSuffix = ".xml"
+	endSheetDataTag     = "</sheetData>"
+	dimensionTag        = `<dimension ref="%s"></dimension>`
+	// This is the index of the max style that this library will insert into XLSX sheets by default.
+	// This allows us to predict what the style id of styles that we add will be.
+	// TestXlsxStyleBehavior tests that this behavior continues to be what we expect.
+	initMaxStyleId = 1
+)
+
+var BuiltStreamFileBuilderError = errors.New("StreamFileBuilder has already been built, functions may no longer be used")
+
+// NewStreamFileBuilder creates an StreamFileBuilder that will write to the the provided io.writer
+func NewStreamFileBuilder(writer io.Writer) *StreamFileBuilder {
+	return &StreamFileBuilder{
+		zipWriter:          zip.NewWriter(writer),
+		xlsxFile:           NewFile(),
+		cellTypeToStyleIds: make(map[CellType]int),
+		maxStyleId:         initMaxStyleId,
+	}
+}
+
+// NewStreamFileBuilderForPath takes the name of an XLSX file and returns a builder for it.
+// The file will be created if it does not exist, or truncated if it does.
+func NewStreamFileBuilderForPath(path string) (*StreamFileBuilder, error) {
+	file, err := os.Create(path)
+	if err != nil {
+		return nil, err
+	}
+	return NewStreamFileBuilder(file), nil
+}
+
+// AddSheet will add sheets with the given name with the provided headers. The headers cannot be edited later, and all
+// rows written to the sheet must contain the same number of cells as the header. Sheet names must be unique, or an
+// error will be thrown.
+func (sb *StreamFileBuilder) AddSheet(name string, headers []string, cellTypes []*CellType) error {
+	if sb.built {
+		return BuiltStreamFileBuilderError
+	}
+	if len(cellTypes) > len(headers) {
+		return errors.New("cellTypes is longer than headers")
+	}
+	sheet, err := sb.xlsxFile.AddSheet(name)
+	if err != nil {
+		// Set built on error so that all subsequent calls to the builder will also fail.
+		sb.built = true
+		return err
+	}
+	sb.styleIds = append(sb.styleIds, []int{})
+	row := sheet.AddRow()
+	if count := row.WriteSlice(&headers, -1); count != len(headers) {
+		// Set built on error so that all subsequent calls to the builder will also fail.
+		sb.built = true
+		return errors.New("failed to write headers")
+	}
+	for i, cellType := range cellTypes {
+		var cellStyleIndex int
+		var ok bool
+		if cellType != nil {
+			// The cell type is one of the attributes of a Style.
+			// Since it is the only attribute of Style that we use, we can assume that cell types
+			// map one to one with Styles and their Style ID.
+			// If a new cell type is used, a new style gets created with an increased id, if an existing cell type is
+			// used, the pre-existing style will also be used.
+			cellStyleIndex, ok = sb.cellTypeToStyleIds[*cellType]
+			if !ok {
+				sb.maxStyleId++
+				cellStyleIndex = sb.maxStyleId
+				sb.cellTypeToStyleIds[*cellType] = sb.maxStyleId
+			}
+			sheet.Cols[i].SetType(*cellType)
+		}
+		sb.styleIds[len(sb.styleIds)-1] = append(sb.styleIds[len(sb.styleIds)-1], cellStyleIndex)
+	}
+	return nil
+}
+
+// Build begins streaming the XLSX file to the io, by writing all the XLSX metadata. It creates a StreamFile struct
+// that can be used to write the rows to the sheets.
+func (sb *StreamFileBuilder) Build() (*StreamFile, error) {
+	if sb.built {
+		return nil, BuiltStreamFileBuilderError
+	}
+	sb.built = true
+	parts, err := sb.xlsxFile.MarshallParts()
+	if err != nil {
+		return nil, err
+	}
+	es := &StreamFile{
+		zipWriter:      sb.zipWriter,
+		xlsxFile:       sb.xlsxFile,
+		sheetXmlPrefix: make([]string, len(sb.xlsxFile.Sheets)),
+		sheetXmlSuffix: make([]string, len(sb.xlsxFile.Sheets)),
+		styleIds:       sb.styleIds,
+	}
+	for path, data := range parts {
+		// If the part is a sheet, don't write it yet. We only want to write the XLSX metadata files, since at this
+		// point the sheets are still empty. The sheet files will be written later as their rows come in.
+		if strings.HasPrefix(path, sheetFilePathPrefix) {
+			if err := sb.processEmptySheetXML(es, path, data); err != nil {
+				return nil, err
+			}
+			continue
+		}
+		metadataFile, err := sb.zipWriter.Create(path)
+		if err != nil {
+			return nil, err
+		}
+		_, err = metadataFile.Write([]byte(data))
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if err := es.NextSheet(); err != nil {
+		return nil, err
+	}
+	return es, nil
+}
+
+// processEmptySheetXML will take in the path and XML data of an empty sheet, and will save the beginning and end of the
+// XML file so that these can be written at the right time.
+func (sb *StreamFileBuilder) processEmptySheetXML(sf *StreamFile, path, data string) error {
+	// Get the sheet index from the path
+	sheetIndex, err := getSheetIndex(sf, path)
+	if err != nil {
+		return err
+	}
+
+	// Remove the Dimension tag. Since more rows are going to be written to the sheet, it will be wrong.
+	// It is valid to for a sheet to be missing a Dimension tag, but it is not valid for it to be wrong.
+	data, err = removeDimensionTag(data, sf.xlsxFile.Sheets[sheetIndex])
+	if err != nil {
+		return err
+	}
+
+	// Split the sheet at the end of its SheetData tag so that more rows can be added inside.
+	prefix, suffix, err := splitSheetIntoPrefixAndSuffix(data)
+	if err != nil {
+		return err
+	}
+	sf.sheetXmlPrefix[sheetIndex] = prefix
+	sf.sheetXmlSuffix[sheetIndex] = suffix
+	return nil
+}
+
+// getSheetIndex parses the path to the XLSX sheet data and returns the index
+// The files that store the data for each sheet must have the format:
+// xl/worksheets/sheet123.xml
+// where 123 is the index of the sheet. This file path format is part of the XLSX file standard.
+func getSheetIndex(sf *StreamFile, path string) (int, error) {
+	indexString := path[len(sheetFilePathPrefix) : len(path)-len(sheetFilePathSuffix)]
+	sheetXLSXIndex, err := strconv.Atoi(indexString)
+	if err != nil {
+		return -1, errors.New("Unexpected sheet file name from xlsx package")
+	}
+	if sheetXLSXIndex < 1 || len(sf.sheetXmlPrefix) < sheetXLSXIndex ||
+		len(sf.sheetXmlSuffix) < sheetXLSXIndex || len(sf.xlsxFile.Sheets) < sheetXLSXIndex {
+		return -1, errors.New("Unexpected sheet index")
+	}
+	sheetArrayIndex := sheetXLSXIndex - 1
+	return sheetArrayIndex, nil
+}
+
+// removeDimensionTag will return the passed in XLSX Spreadsheet XML with the dimension tag removed.
+// data is the XML data for the sheet
+// sheet is the Sheet struct that the XML was created from.
+// Can return an error if the XML's dimension tag does not match was is expected based on the provided Sheet
+func removeDimensionTag(data string, sheet *Sheet) (string, error) {
+	x := len(sheet.Cols) - 1
+	y := len(sheet.Rows) - 1
+	if x < 0 {
+		x = 0
+	}
+	if y < 0 {
+		y = 0
+	}
+	var dimensionRef string
+	if x == 0 && y == 0 {
+		dimensionRef = "A1"
+	} else {
+		endCoordinate := GetCellIDStringFromCoords(x, y)
+		dimensionRef = "A1:" + endCoordinate
+	}
+	dataParts := strings.Split(data, fmt.Sprintf(dimensionTag, dimensionRef))
+	if len(dataParts) != 2 {
+		return "", errors.New("unexpected Sheet XML: dimension tag not found")
+	}
+	return dataParts[0] + dataParts[1], nil
+}
+
+// splitSheetIntoPrefixAndSuffix will split the provided XML sheet into a prefix and a suffix so that
+// more spreadsheet rows can be inserted in between.
+func splitSheetIntoPrefixAndSuffix(data string) (string, string, error) {
+	// Split the sheet at the end of its SheetData tag so that more rows can be added inside.
+	sheetParts := strings.Split(data, endSheetDataTag)
+	if len(sheetParts) != 2 {
+		return "", "", errors.New("unexpected Sheet XML: SheetData close tag not found")
+	}
+	return sheetParts[0], sheetParts[1], nil
+}

+ 42 - 20
vendor/github.com/tealeg/xlsx/xmlStyle.go

@@ -12,7 +12,6 @@ import (
 	"encoding/xml"
 	"encoding/xml"
 	"fmt"
 	"fmt"
 	"strconv"
 	"strconv"
-	"strings"
 	"sync"
 	"sync"
 )
 )
 
 
@@ -58,6 +57,19 @@ var builtInNumFmt = map[int]string{
 	49: "@",
 	49: "@",
 }
 }
 
 
+// These are the color annotations from number format codes that contain color names.
+// Also possible are [color1] through [color56]
+var numFmtColorCodes = []string{
+	"[red]",
+	"[black]",
+	"[green]",
+	"[white]",
+	"[blue]",
+	"[magenta]",
+	"[yellow]",
+	"[cyan]",
+}
+
 var builtInNumFmtInv = make(map[string]int, 40)
 var builtInNumFmtInv = make(map[string]int, 40)
 
 
 func init() {
 func init() {
@@ -91,9 +103,10 @@ type xlsxStyleSheet struct {
 
 
 	theme *theme
 	theme *theme
 
 
-	sync.RWMutex   // protects the following
-	styleCache     map[int]*Style
-	numFmtRefTable map[int]xlsxNumFmt
+	sync.RWMutex      // protects the following
+	styleCache        map[int]*Style
+	numFmtRefTable    map[int]xlsxNumFmt
+	parsedNumFmtTable map[string]*parsedNumberFormat
 }
 }
 
 
 func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
 func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
@@ -198,8 +211,9 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) *Style {
 			style.Alignment.Vertical = xf.Alignment.Vertical
 			style.Alignment.Vertical = xf.Alignment.Vertical
 		}
 		}
 		style.Alignment.WrapText = xf.Alignment.WrapText
 		style.Alignment.WrapText = xf.Alignment.WrapText
-
-		styles.Lock()
+        	style.Alignment.TextRotation = xf.Alignment.TextRotation
+		
+        	styles.Lock()
 		styles.styleCache[styleIndex] = style
 		styles.styleCache[styleIndex] = style
 		styles.Unlock()
 		styles.Unlock()
 	}
 	}
@@ -220,22 +234,30 @@ func getBuiltinNumberFormat(numFmtId int) string {
 	return builtInNumFmt[numFmtId]
 	return builtInNumFmt[numFmtId]
 }
 }
 
 
-func (styles *xlsxStyleSheet) getNumberFormat(styleIndex int) string {
-	if styles.CellXfs.Xf == nil {
-		return ""
-	}
-	var numberFormat string = ""
-	if styleIndex > -1 && styleIndex <= styles.CellXfs.Count {
-		xf := styles.CellXfs.Xf[styleIndex]
-		if builtin := getBuiltinNumberFormat(xf.NumFmtId); builtin != "" {
-			return builtin
+func (styles *xlsxStyleSheet) getNumberFormat(styleIndex int) (string, *parsedNumberFormat) {
+	var numberFormat string = "general"
+	if styles.CellXfs.Xf != nil {
+		if styleIndex > -1 && styleIndex <= styles.CellXfs.Count {
+			xf := styles.CellXfs.Xf[styleIndex]
+			if builtin := getBuiltinNumberFormat(xf.NumFmtId); builtin != "" {
+				numberFormat = builtin
+			} else {
+				if styles.numFmtRefTable != nil {
+					numFmt := styles.numFmtRefTable[xf.NumFmtId]
+					numberFormat = numFmt.FormatCode
+				}
+			}
 		}
 		}
-		if styles.numFmtRefTable != nil {
-			numFmt := styles.numFmtRefTable[xf.NumFmtId]
-			numberFormat = numFmt.FormatCode
+	}
+	parsedFmt, ok := styles.parsedNumFmtTable[numberFormat]
+	if !ok {
+		if styles.parsedNumFmtTable == nil {
+			styles.parsedNumFmtTable = map[string]*parsedNumberFormat{}
 		}
 		}
+		parsedFmt = parseFullNumberFormatString(numberFormat)
+		styles.parsedNumFmtTable[numberFormat] = parsedFmt
 	}
 	}
-	return strings.ToLower(numberFormat)
+	return numberFormat, parsedFmt
 }
 }
 
 
 func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
 func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
@@ -313,7 +335,7 @@ func (styles *xlsxStyleSheet) addCellXf(xCellXf xlsxXf) (index int) {
 
 
 // newNumFmt generate a xlsxNumFmt according the format code. When the FormatCode is built in, it will return a xlsxNumFmt with the NumFmtId defined in ECMA document, otherwise it will generate a new NumFmtId greater than 164.
 // newNumFmt generate a xlsxNumFmt according the format code. When the FormatCode is built in, it will return a xlsxNumFmt with the NumFmtId defined in ECMA document, otherwise it will generate a new NumFmtId greater than 164.
 func (styles *xlsxStyleSheet) newNumFmt(formatCode string) xlsxNumFmt {
 func (styles *xlsxStyleSheet) newNumFmt(formatCode string) xlsxNumFmt {
-	if formatCode == "" {
+	if compareFormatString(formatCode, "general") {
 		return xlsxNumFmt{NumFmtId: 0, FormatCode: "general"}
 		return xlsxNumFmt{NumFmtId: 0, FormatCode: "general"}
 	}
 	}
 	// built in NumFmts in xmlStyle.go, traverse from the const.
 	// built in NumFmts in xmlStyle.go, traverse from the const.

+ 21 - 11
vendor/github.com/tealeg/xlsx/xmlWorkbook.go

@@ -177,27 +177,37 @@ func worksheetFileForSheet(sheet xlsxSheet, worksheets map[string]*zip.File, she
 }
 }
 
 
 // getWorksheetFromSheet() is an internal helper function to open a
 // getWorksheetFromSheet() is an internal helper function to open a
-// sheetN.xml file, refered to by an xlsx.xlsxSheet struct, from the XLSX
+// sheetN.xml file, referred to by an xlsx.xlsxSheet struct, from the XLSX
 // file and unmarshal it an xlsx.xlsxWorksheet struct
 // file and unmarshal it an xlsx.xlsxWorksheet struct
-func getWorksheetFromSheet(sheet xlsxSheet, worksheets map[string]*zip.File, sheetXMLMap map[string]string) (*xlsxWorksheet, error) {
-	var rc io.ReadCloser
+func getWorksheetFromSheet(sheet xlsxSheet, worksheets map[string]*zip.File, sheetXMLMap map[string]string, rowLimit int) (*xlsxWorksheet, error) {
+	var r io.Reader
 	var decoder *xml.Decoder
 	var decoder *xml.Decoder
 	var worksheet *xlsxWorksheet
 	var worksheet *xlsxWorksheet
-	var error error
+	var err error
 	worksheet = new(xlsxWorksheet)
 	worksheet = new(xlsxWorksheet)
 
 
 	f := worksheetFileForSheet(sheet, worksheets, sheetXMLMap)
 	f := worksheetFileForSheet(sheet, worksheets, sheetXMLMap)
 	if f == nil {
 	if f == nil {
 		return nil, fmt.Errorf("Unable to find sheet '%s'", sheet)
 		return nil, fmt.Errorf("Unable to find sheet '%s'", sheet)
 	}
 	}
-	rc, error = f.Open()
-	if error != nil {
-		return nil, error
+	if rc, err := f.Open(); err != nil {
+		return nil, err
+	} else {
+		defer rc.Close()
+		r = rc
 	}
 	}
-	decoder = xml.NewDecoder(rc)
-	error = decoder.Decode(worksheet)
-	if error != nil {
-		return nil, error
+
+	if rowLimit != NoRowLimit {
+		r, err = truncateSheetXML(r, rowLimit)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	decoder = xml.NewDecoder(r)
+	err = decoder.Decode(worksheet)
+	if err != nil {
+		return nil, err
 	}
 	}
 	return worksheet, nil
 	return worksheet, nil
 }
 }