张金坤 8 lat temu
rodzic
commit
b3802db26a
36 zmienionych plików z 2144 dodań i 672 usunięć
  1. 12 2
      common/src/github.com/tealeg/xlsx/.travis.yml
  2. 54 0
      common/src/github.com/tealeg/xlsx/AUTHORS.txt
  3. 23 3
      common/src/github.com/tealeg/xlsx/README.org
  4. 92 59
      common/src/github.com/tealeg/xlsx/cell.go
  5. 250 89
      common/src/github.com/tealeg/xlsx/cell_test.go
  6. 8 7
      common/src/github.com/tealeg/xlsx/col.go
  7. 30 13
      common/src/github.com/tealeg/xlsx/compatibility_test.go
  8. 16 9
      common/src/github.com/tealeg/xlsx/date.go
  9. 7 2
      common/src/github.com/tealeg/xlsx/date_test.go
  10. 36 0
      common/src/github.com/tealeg/xlsx/example_read_test.go
  11. 88 69
      common/src/github.com/tealeg/xlsx/file.go
  12. 109 20
      common/src/github.com/tealeg/xlsx/file_test.go
  13. 1 1
      common/src/github.com/tealeg/xlsx/fuzzy_test.go
  14. 141 43
      common/src/github.com/tealeg/xlsx/lib.go
  15. 168 18
      common/src/github.com/tealeg/xlsx/lib_test.go
  16. 132 0
      common/src/github.com/tealeg/xlsx/read.go
  17. 243 0
      common/src/github.com/tealeg/xlsx/read_test.go
  18. 11 5
      common/src/github.com/tealeg/xlsx/row.go
  19. 1 1
      common/src/github.com/tealeg/xlsx/row_test.go
  20. 136 19
      common/src/github.com/tealeg/xlsx/sheet.go
  21. 42 37
      common/src/github.com/tealeg/xlsx/sheet_test.go
  22. 41 38
      common/src/github.com/tealeg/xlsx/style.go
  23. 1 6
      common/src/github.com/tealeg/xlsx/style_test.go
  24. 55 45
      common/src/github.com/tealeg/xlsx/write.go
  25. 108 10
      common/src/github.com/tealeg/xlsx/write_test.go
  26. 199 139
      common/src/github.com/tealeg/xlsx/xmlStyle.go
  27. 40 4
      common/src/github.com/tealeg/xlsx/xmlStyle_test.go
  28. 18 3
      common/src/github.com/tealeg/xlsx/xmlWorkbook.go
  29. 5 2
      common/src/github.com/tealeg/xlsx/xmlWorkbook_test.go
  30. 29 19
      common/src/github.com/tealeg/xlsx/xmlWorksheet.go
  31. 12 4
      common/src/github.com/tealeg/xlsx/xmlWorksheet_test.go
  32. 1 1
      common/src/github.com/xuri/excelize/README.md
  33. 7 1
      common/src/github.com/xuri/excelize/excelize_test.go
  34. 12 0
      common/src/github.com/xuri/excelize/lib.go
  35. 2 1
      common/src/github.com/xuri/excelize/sheet.go
  36. 14 2
      common/src/github.com/xuri/excelize/styles.go

+ 12 - 2
common/src/github.com/tealeg/xlsx/.travis.yml

@@ -4,5 +4,15 @@ install:
   - go get -d -t -v ./... && go build -v ./...
 
 go:
-  - 1.2
-  - 1.3
+  - 1.5
+  - 1.6
+  - 1.7
+  - 1.8
+  - tip
+
+script:
+  - go vet ./...
+  - go test -v -coverprofile=coverage.txt -covermode=atomic
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)

+ 54 - 0
common/src/github.com/tealeg/xlsx/AUTHORS.txt

@@ -0,0 +1,54 @@
+ACHER <artem.chernyak@monsanto.com>
+Andrew Schwartz <andrew.schwartz@gengo.com>
+Artem Chernyak <artemchernyak@Artems-MacBook-Air.local>
+Artem Chernyak <artemchernyak@gmail.com>
+blackss2 <blackss2@nate.com>
+Brandon Mulcahy <brandon@jangler.info>
+Brian Smith <ohohvi@gmail.com>
+bronze1man <bronze1man@gmail.com>
+Bruno Bigras <bigras.bruno@gmail.com>
+Chris Glass <tribaal@gmail.com>
+Colin Fox <colin.fox@cumul8.com>
+Colin Fox <greenenergy@gmail.com>
+crahles <christoph@rahles.de>
+Daniel Upton <daniel@floppy.co>
+Daniel YC Lin <dlin@u40>
+DerLinkshaender <mail@arminhanisch.de>
+Eric <ericscottlagergren@gmail.com>
+frogs <frogs@frogss-MacBook-Air.local>
+fzerorubigd <fzerorubigd@gmail.com>
+Geoffrey J. Teale <geoffrey.teale@canonical.com>
+Gyu-Ho Lee <gyuho.cs@gmail.com>
+Herman Schaaf <hermanschaaf@gmail.com>
+Hugh Gao <email@klniu.com>
+Iain Lowe <i.lowe@mademediacorp.com>
+ivnivnch <ivnivnch@gmail.com>
+Jason Hall <imjasonh@gmail.com>
+Joshua Baker <joshua.baker@cumul8.com>
+Kaur Kuut <strom@nevermore.ee>
+Lunny Xiao <xiaolunwen@gmail.com>
+magician1 <kouta-k@mbm.nifty.com>
+Mathias Fredriksson <mafredri@gmail.com>
+Matt Aimonetti <mattaimonetti@gmail.com>
+Moch. Lutfi <kapten_lufi@yahoo.co.id>
+Moch.Lutfi <kapten_lufi@yahoo.co.id>
+Neoin <Heinoldewage@gmail.com>
+Nguyen Nguyen <ntn@NTN.local>
+Nikita Danilov <mirt@mirt.su>
+OneOfOne <OneOfOne@gmail.com>
+Peter Waller <p@pwaller.net>
+Philipp Klose <TheHippo@users.noreply.github.com>
+richard bucker <richard@bucker.net>
+Shawn Milochik <shawn@milochik.com>
+Shawn Smith <shawnpsmith@gmail.com>
+Shawn Smith <shawn.smith@gengo.com>
+SHIMADA Koji <koji.shimada@enishi-tech.com>
+Steven Degutis <steven.degutis@gmail.com>
+takuya sato <sato-taku@klab.com>
+Thieu Pham <thieu.pham@workiva.com>
+Tormod Erevik Lea <tormodlea@gmail.com>
+trinchan <andrew.schwartz@gengo.com>
+U-NORTH_AMERICA\ACHER <ACHER@PBECRLK.na.ds.monsanto.com>
+YAMADA Tsuyoshi <tyamada@minimum2scp.org>
+Yoshiki Shibukawa <shibukawa.yoshiki@dena.jp>
+zhcy <zhangchenyu2009@gmail.com>

+ 23 - 3
common/src/github.com/tealeg/xlsx/README.org

@@ -1,4 +1,10 @@
-* XSLX
+* XLSX
+
+[[https://travis-ci.org/tealeg/xlsx][https://img.shields.io/travis/tealeg/xlsx/master.svg?style=flat-square]]
+[[https://codecov.io/gh/tealeg/xlsx][https://codecov.io/gh/tealeg/xlsx/branch/master/graph/badge.svg]]
+[[https://godoc.org/github.com/tealeg/xlsx][https://godoc.org/github.com/tealeg/xlsx?status.svg]]
+[[https://github.com/tealeg/xlsx#license][https://img.shields.io/badge/license-bsd-orange.svg]]
+
 ** Introduction
 xlsx is a library to simplify reading and writing the XML format used
 by recent version of Microsoft Excel in Go programs.
@@ -34,7 +40,8 @@ func main() {
     for _, sheet := range xlFile.Sheets {
         for _, row := range sheet.Rows {
             for _, cell := range row.Cells {
-                fmt.Printf("%s\n", cell.String())
+                text := cell.String()
+                fmt.Printf("%s\n", text)
             }
         }
     }
@@ -66,7 +73,10 @@ func main() {
     var err error
 
     file = xlsx.NewFile()
-    sheet = file.AddSheet("Sheet1")
+    sheet, err = file.AddSheet("Sheet1")
+    if err != nil {
+        fmt.Printf(err.Error())
+    }
     row = sheet.AddRow()
     cell = row.AddCell()
     cell.Value = "I am a cell!"
@@ -78,6 +88,16 @@ func main() {
 
 #+END_SRC
 
+** Contributing
+
+We're extremely happy to review pull requests.  Please be patient, maintaining XLSX doesn't pay anyone's salary (to my knowledge).
+
+If you'd like to propose a change please ensure the following:
+
+- All existing tests are passing.
+- There are tests in the test suite that cover the changes you're making.
+- You have added documentation strings (in English) to (at least) the public functions you've added or modified.
+- Your use of, or creation of, XML is compliant with [[http://www.ecma-international.org/publications/standards/Ecma-376.htm][part 1 of the 4th edition of the ECMA-376 Standard for Office Open XML]].
 
 ** License
 This code is under a BSD style license:

+ 92 - 59
common/src/github.com/tealeg/xlsx/cell.go

@@ -67,14 +67,29 @@ func (c *Cell) SetString(s string) {
 	c.cellType = CellTypeString
 }
 
-// String returns the value of a Cell as a string.
+// String returns the value of a Cell as a string.  If you'd like to
+// see errors returned from formatting then please use
+// Cell.FormattedValue() instead.
 func (c *Cell) String() string {
-	return c.FormattedValue()
+	// To preserve the String() interface we'll throw away errors.
+	// Not that using FormattedValue is therefore strongly
+	// preferred.
+	value, _ := c.FormattedValue()
+	return value
 }
 
 // SetFloat sets the value of a cell to a float.
 func (c *Cell) SetFloat(n float64) {
-	c.SetFloatWithFormat(n, builtInNumFmt[builtInNumFmtIndex_GENERAL])
+	c.SetValue(n)
+}
+
+//GetTime returns the value of a Cell as a time.Time
+func (c *Cell) GetTime(date1904 bool) (t time.Time, err error) {
+	f, err := c.Float()
+	if err != nil {
+		return t, err
+	}
+	return TimeFromExcelTime(f, date1904), nil
 }
 
 /*
@@ -103,27 +118,53 @@ func (c *Cell) SetFloatWithFormat(n float64, format string) {
 	c.cellType = CellTypeNumeric
 }
 
-var timeLocationUTC *time.Location
+var timeLocationUTC, _ = time.LoadLocation("UTC")
 
-func init() {
-	timeLocationUTC, _ = time.LoadLocation("UTC")
+func TimeToUTCTime(t time.Time) time.Time {
+	return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
 }
 
-func timeToUTCTime(t time.Time) time.Time {
-	return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
+func TimeToExcelTime(t time.Time) float64 {
+	return float64(t.UnixNano())/8.64e13 + 25569.0
 }
 
-func timeToExcelTime(t time.Time) float64 {
-	return float64(t.Unix())/86400.0 + 25569.0
+// DateTimeOptions are additional options for exporting times
+type DateTimeOptions struct {
+	// Location allows calculating times in other timezones/locations
+	Location *time.Location
+	// ExcelTimeFormat is the string you want excel to use to format the datetime
+	ExcelTimeFormat string
 }
 
+var (
+	DefaultDateFormat     = builtInNumFmt[14]
+	DefaultDateTimeFormat = builtInNumFmt[22]
+
+	DefaultDateOptions = DateTimeOptions{
+		Location:        timeLocationUTC,
+		ExcelTimeFormat: DefaultDateFormat,
+	}
+
+	DefaultDateTimeOptions = DateTimeOptions{
+		Location:        timeLocationUTC,
+		ExcelTimeFormat: DefaultDateTimeFormat,
+	}
+)
+
 // SetDate sets the value of a cell to a float.
 func (c *Cell) SetDate(t time.Time) {
-	c.SetDateTimeWithFormat(float64(int64(timeToExcelTime(timeToUTCTime(t)))), builtInNumFmt[14])
+	c.SetDateWithOptions(t, DefaultDateOptions)
 }
 
 func (c *Cell) SetDateTime(t time.Time) {
-	c.SetDateTimeWithFormat(timeToExcelTime(timeToUTCTime(t)), builtInNumFmt[14])
+	c.SetDateWithOptions(t, DefaultDateTimeOptions)
+}
+
+// SetDateWithOptions allows for more granular control when exporting dates and times
+func (c *Cell) SetDateWithOptions(t time.Time, options DateTimeOptions) {
+	_, offset := t.In(options.Location).Zone()
+	t = time.Unix(t.Unix()+int64(offset), 0)
+	c.SetDateTimeWithFormat(TimeToExcelTime(t.In(timeLocationUTC)), options.ExcelTimeFormat)
 }
 
 func (c *Cell) SetDateTimeWithFormat(n float64, format string) {
@@ -144,10 +185,7 @@ func (c *Cell) Float() (float64, error) {
 
 // SetInt64 sets a cell's value to a 64-bit integer.
 func (c *Cell) SetInt64(n int64) {
-	c.Value = fmt.Sprintf("%d", n)
-	c.NumFmt = builtInNumFmt[builtInNumFmtIndex_INT]
-	c.formula = ""
-	c.cellType = CellTypeNumeric
+	c.SetValue(n)
 }
 
 // Int64 returns the value of cell as 64-bit integer.
@@ -161,44 +199,26 @@ func (c *Cell) Int64() (int64, error) {
 
 // SetInt sets a cell's value to an integer.
 func (c *Cell) SetInt(n int) {
-	c.Value = fmt.Sprintf("%d", n)
-	c.NumFmt = builtInNumFmt[builtInNumFmtIndex_INT]
-	c.formula = ""
-	c.cellType = CellTypeNumeric
+	c.SetValue(n)
 }
 
 // SetInt sets a cell's value to an integer.
 func (c *Cell) SetValue(n interface{}) {
-	var s string
-	switch n.(type) {
+	switch t := n.(type) {
 	case time.Time:
 		c.SetDateTime(n.(time.Time))
 		return
-	case int:
+	case int, int8, int16, int32, int64, float32, float64:
 		c.setGeneral(fmt.Sprintf("%v", n))
-		return
-	case int32:
-		c.setGeneral(fmt.Sprintf("%v", n))
-		return
-	case int64:
-		c.setGeneral(fmt.Sprintf("%v", n))
-		return
-	case float32:
-		c.setGeneral(fmt.Sprintf("%v", n))
-		return
-	case float64:
-		c.setGeneral(fmt.Sprintf("%v", n))
-		return
 	case string:
-		s = n.(string)
+		c.SetString(t)
 	case []byte:
-		s = string(n.([]byte))
+		c.SetString(string(t))
 	case nil:
-		s = ""
+		c.SetString("")
 	default:
-		s = fmt.Sprintf("%v", n)
+		c.SetString(fmt.Sprintf("%v", n))
 	}
-	c.SetString(s)
 }
 
 // SetInt sets a cell's value to an integer.
@@ -239,7 +259,7 @@ func (c *Cell) Bool() bool {
 		return c.Value == "1"
 	}
 	// If numeric, base it on a non-zero.
-	if c.cellType == CellTypeNumeric {
+	if c.cellType == CellTypeNumeric || c.cellType == CellTypeGeneral {
 		return c.Value != "0"
 	}
 	// Return whether there's an empty string.
@@ -291,11 +311,11 @@ func (c *Cell) formatToInt(format string) (string, error) {
 	return fmt.Sprintf(format, int(f)), nil
 }
 
-// SafeFormattedValue 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
 // value, it will do so, if not then an error will be returned, along
 // with the raw value of the Cell.
-func (c *Cell) SafeFormattedValue() (string, error) {
+func (c *Cell) FormattedValue() (string, error) {
 	var numberFormat = c.GetNumberFormat()
 	if isTimeFormat(numberFormat) {
 		return parseTime(c)
@@ -348,17 +368,6 @@ func (c *Cell) SafeFormattedValue() (string, error) {
 
 }
 
-// FormattedValue returns the formatted version of the value.
-// If it's a string type, c.Value will just be returned. Otherwise,
-// it will attempt to apply Excel formatting to the value.
-func (c *Cell) FormattedValue() string {
-	value, err := c.SafeFormattedValue()
-	if err != nil {
-		return err.Error()
-	}
-	return value
-}
-
 // parseTime returns a string parsed using time.Time
 func parseTime(c *Cell) (string, error) {
 	f, err := strconv.ParseFloat(c.Value, 64)
@@ -367,27 +376,44 @@ func parseTime(c *Cell) (string, error) {
 	}
 	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"},
-		{"hh", "15"},
-		{"h", "3"},
 		{"mm:", "04:"},
 		{":mm", ":04"},
 		{"mm", "01"},
 		{"am/pm", "pm"},
 		{"m/", "1/"},
-		{".0", ".9999"},
+		{"%%%%", "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)
@@ -396,6 +422,7 @@ func parseTime(c *Cell) (string, error) {
 	// 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 {
@@ -409,7 +436,7 @@ func parseTime(c *Cell) (string, error) {
 // a time.Time.
 func isTimeFormat(format string) bool {
 	dateParts := []string{
-		"yy", "hh", "am", "pm", "ss", "mm", ":",
+		"yy", "hh", "h", "am/pm", "AM/PM", "A/P", "a/p", "ss", "mm", ":",
 	}
 	for _, part := range dateParts {
 		if strings.Contains(format, part) {
@@ -418,3 +445,9 @@ func isTimeFormat(format string) bool {
 	}
 	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")
+}

+ 250 - 89
common/src/github.com/tealeg/xlsx/cell_test.go

@@ -1,6 +1,9 @@
 package xlsx
 
 import (
+	"math"
+	"time"
+
 	. "gopkg.in/check.v1"
 )
 
@@ -35,7 +38,7 @@ func (s *CellSuite) TestGetStyleWithFonts(c *C) {
 // Test that SetStyle correctly translates into a xlsxFont element
 func (s *CellSuite) TestSetStyleWithFonts(c *C) {
 	file := NewFile()
-	sheet := file.AddSheet("Test")
+	sheet, _ := file.AddSheet("Test")
 	row := sheet.AddRow()
 	cell := row.AddCell()
 	font := NewFont(12, "Calibra")
@@ -43,7 +46,7 @@ func (s *CellSuite) TestSetStyleWithFonts(c *C) {
 	style.Font = *font
 	cell.SetStyle(style)
 	style = cell.GetStyle()
-	xFont, _, _, _, _ := style.makeXLSXStyleElements()
+	xFont, _, _, _ := style.makeXLSXStyleElements()
 	c.Assert(xFont.Sz.Val, Equals, "12")
 	c.Assert(xFont.Name.Val, Equals, "Calibra")
 }
@@ -55,7 +58,7 @@ func (s *CellSuite) TestGetStyleWithFills(c *C) {
 	style.Fill = fill
 	cell := &Cell{Value: "123", style: style}
 	style = cell.GetStyle()
-	_, xFill, _, _, _ := style.makeXLSXStyleElements()
+	_, xFill, _, _ := style.makeXLSXStyleElements()
 	c.Assert(xFill.PatternFill.PatternType, Equals, "solid")
 	c.Assert(xFill.PatternFill.BgColor.RGB, Equals, "00FF0000")
 	c.Assert(xFill.PatternFill.FgColor.RGB, Equals, "FF000000")
@@ -64,7 +67,7 @@ func (s *CellSuite) TestGetStyleWithFills(c *C) {
 // Test that SetStyle correctly updates xlsxStyle.Fills.
 func (s *CellSuite) TestSetStyleWithFills(c *C) {
 	file := NewFile()
-	sheet := file.AddSheet("Test")
+	sheet, _ := file.AddSheet("Test")
 	row := sheet.AddRow()
 	cell := row.AddCell()
 	fill := NewFill("solid", "00FF0000", "FF000000")
@@ -72,7 +75,7 @@ func (s *CellSuite) TestSetStyleWithFills(c *C) {
 	style.Fill = *fill
 	cell.SetStyle(style)
 	style = cell.GetStyle()
-	_, xFill, _, _, _ := style.makeXLSXStyleElements()
+	_, xFill, _, _ := style.makeXLSXStyleElements()
 	xPatternFill := xFill.PatternFill
 	c.Assert(xPatternFill.PatternType, Equals, "solid")
 	c.Assert(xPatternFill.FgColor.RGB, Equals, "00FF0000")
@@ -86,7 +89,7 @@ func (s *CellSuite) TestGetStyleWithBorders(c *C) {
 	style.Border = border
 	cell := Cell{Value: "123", style: style}
 	style = cell.GetStyle()
-	_, _, xBorder, _, _ := style.makeXLSXStyleElements()
+	_, _, xBorder, _ := style.makeXLSXStyleElements()
 	c.Assert(xBorder.Left.Style, Equals, "thin")
 	c.Assert(xBorder.Right.Style, Equals, "thin")
 	c.Assert(xBorder.Top.Style, Equals, "thin")
@@ -114,11 +117,26 @@ func (l *CellSuite) TestSetFloat(c *C) {
 	c.Assert(cell.Value, Equals, "37947.75334343")
 }
 
-// SafeFormattedValue returns an error for formatting errors
-func (l *CellSuite) TestSafeFormattedValueErrorsOnBadFormat(c *C) {
+func (s *CellSuite) TestGetTime(c *C) {
+	cell := Cell{}
+	cell.SetFloat(0)
+	date, err := cell.GetTime(false)
+	c.Assert(err, Equals, nil)
+	c.Assert(date, Equals, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC))
+	cell.SetFloat(39813.0)
+	date, err = cell.GetTime(true)
+	c.Assert(err, Equals, nil)
+	c.Assert(date, Equals, time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC))
+	cell.Value = "d"
+	_, err = cell.GetTime(false)
+	c.Assert(err, NotNil)
+}
+
+// FormattedValue returns an error for formatting errors
+func (l *CellSuite) TestFormattedValueErrorsOnBadFormat(c *C) {
 	cell := Cell{Value: "Fudge Cake"}
 	cell.NumFmt = "#,##0 ;(#,##0)"
-	value, err := cell.SafeFormattedValue()
+	value, err := cell.FormattedValue()
 	c.Assert(value, Equals, "Fudge Cake")
 	c.Assert(err, NotNil)
 	c.Assert(err.Error(), Equals, "strconv.ParseFloat: parsing \"Fudge Cake\": invalid syntax")
@@ -128,8 +146,24 @@ func (l *CellSuite) TestSafeFormattedValueErrorsOnBadFormat(c *C) {
 func (l *CellSuite) TestFormattedValueReturnsErrorAsValueForBadFormat(c *C) {
 	cell := Cell{Value: "Fudge Cake"}
 	cell.NumFmt = "#,##0 ;(#,##0)"
-	value := cell.FormattedValue()
-	c.Assert(value, Equals, "strconv.ParseFloat: parsing \"Fudge Cake\": invalid syntax")
+	_, err := cell.FormattedValue()
+	c.Assert(err.Error(), Equals, "strconv.ParseFloat: parsing \"Fudge Cake\": invalid syntax")
+}
+
+// formattedValueChecker removes all the boilerplate for testing Cell.FormattedValue
+// after its change from returning one value (a string) to two values (string, error)
+// This allows all the old one-line asserts in the test to continue to be one
+// line, instead of multi-line with error checking.
+type formattedValueChecker struct {
+	c *C
+}
+
+func (fvc *formattedValueChecker) Equals(cell Cell, expected string) {
+	val, err := cell.FormattedValue()
+	if err != nil {
+		fvc.c.Error(err)
+	}
+	fvc.c.Assert(val, Equals, expected)
 }
 
 // We can return a string representation of the formatted data
@@ -142,193 +176,215 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	smallCell := Cell{Value: "0.007"}
 	earlyCell := Cell{Value: "2.1"}
 
+	fvc := formattedValueChecker{c: c}
+
 	cell.NumFmt = "general"
-	c.Assert(cell.FormattedValue(), Equals, "37947.7500001")
+	fvc.Equals(cell, "37947.7500001")
 	negativeCell.NumFmt = "general"
-	c.Assert(negativeCell.FormattedValue(), Equals, "-37947.7500001")
+	fvc.Equals(negativeCell, "-37947.7500001")
 
 	// TODO: This test is currently broken.  For a string type cell, I
 	// don't think FormattedValue() should be doing a numeric conversion on the value
 	// before returning the string.
 	cell.NumFmt = "0"
-	c.Assert(cell.FormattedValue(), Equals, "37947")
+	fvc.Equals(cell, "37947")
 
 	cell.NumFmt = "#,##0" // For the time being we're not doing
 	// this comma formatting, so it'll fall back to the related
 	// non-comma form.
-	c.Assert(cell.FormattedValue(), Equals, "37947")
+	fvc.Equals(cell, "37947")
 
 	cell.NumFmt = "#,##0.00;(#,##0.00)"
-	c.Assert(cell.FormattedValue(), Equals, "37947.75")
+	fvc.Equals(cell, "37947.75")
 
 	cell.NumFmt = "0.00"
-	c.Assert(cell.FormattedValue(), Equals, "37947.75")
+	fvc.Equals(cell, "37947.75")
 
 	cell.NumFmt = "#,##0.00" // For the time being we're not doing
 	// this comma formatting, so it'll fall back to the related
 	// non-comma form.
-	c.Assert(cell.FormattedValue(), Equals, "37947.75")
+	fvc.Equals(cell, "37947.75")
 
 	cell.NumFmt = "#,##0 ;(#,##0)"
-	c.Assert(cell.FormattedValue(), Equals, "37947")
+	fvc.Equals(cell, "37947")
 	negativeCell.NumFmt = "#,##0 ;(#,##0)"
-	c.Assert(negativeCell.FormattedValue(), Equals, "(37947)")
+	fvc.Equals(negativeCell, "(37947)")
 
 	cell.NumFmt = "#,##0 ;[red](#,##0)"
-	c.Assert(cell.FormattedValue(), Equals, "37947")
+	fvc.Equals(cell, "37947")
 	negativeCell.NumFmt = "#,##0 ;[red](#,##0)"
-	c.Assert(negativeCell.FormattedValue(), Equals, "(37947)")
+	fvc.Equals(negativeCell, "(37947)")
 
 	negativeCell.NumFmt = "#,##0.00;(#,##0.00)"
-	c.Assert(negativeCell.FormattedValue(), Equals, "(-37947.75)")
+	fvc.Equals(negativeCell, "(-37947.75)")
 
 	cell.NumFmt = "0%"
-	c.Assert(cell.FormattedValue(), Equals, "3794775%")
+	fvc.Equals(cell, "3794775%")
 
 	cell.NumFmt = "0.00%"
-	c.Assert(cell.FormattedValue(), Equals, "3794775.00%")
+	fvc.Equals(cell, "3794775.00%")
 
 	cell.NumFmt = "0.00e+00"
-	c.Assert(cell.FormattedValue(), Equals, "3.794775e+04")
+	fvc.Equals(cell, "3.794775e+04")
 
 	cell.NumFmt = "##0.0e+0" // This is wrong, but we'll use it for now.
-	c.Assert(cell.FormattedValue(), Equals, "3.794775e+04")
+	fvc.Equals(cell, "3.794775e+04")
 
 	cell.NumFmt = "mm-dd-yy"
-	c.Assert(cell.FormattedValue(), Equals, "11-22-03")
+	fvc.Equals(cell, "11-22-03")
 
 	cell.NumFmt = "d-mmm-yy"
-	c.Assert(cell.FormattedValue(), Equals, "22-Nov-03")
+	fvc.Equals(cell, "22-Nov-03")
 	earlyCell.NumFmt = "d-mmm-yy"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan-00")
+	fvc.Equals(earlyCell, "1-Jan-00")
 
 	cell.NumFmt = "d-mmm"
-	c.Assert(cell.FormattedValue(), Equals, "22-Nov")
+	fvc.Equals(cell, "22-Nov")
 	earlyCell.NumFmt = "d-mmm"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan")
+	fvc.Equals(earlyCell, "1-Jan")
 
 	cell.NumFmt = "mmm-yy"
-	c.Assert(cell.FormattedValue(), Equals, "Nov-03")
+	fvc.Equals(cell, "Nov-03")
 
 	cell.NumFmt = "h:mm am/pm"
-	c.Assert(cell.FormattedValue(), Equals, "6:00 pm")
+	fvc.Equals(cell, "6:00 pm")
 	smallCell.NumFmt = "h:mm am/pm"
-	c.Assert(smallCell.FormattedValue(), Equals, "12:14 am")
+	fvc.Equals(smallCell, "12:10 am")
 
 	cell.NumFmt = "h:mm:ss am/pm"
-	c.Assert(cell.FormattedValue(), Equals, "6:00:00 pm")
+	fvc.Equals(cell, "6:00:00 pm")
 	cell.NumFmt = "hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	fvc.Equals(cell, "18:00:00")
 	smallCell.NumFmt = "h:mm:ss am/pm"
-	c.Assert(smallCell.FormattedValue(), Equals, "12:14:47 am")
+	fvc.Equals(smallCell, "12:10:04 am")
 
 	cell.NumFmt = "h:mm"
-	c.Assert(cell.FormattedValue(), Equals, "6:00")
+	fvc.Equals(cell, "18:00")
 	smallCell.NumFmt = "h:mm"
-	c.Assert(smallCell.FormattedValue(), Equals, "12:14")
+	fvc.Equals(smallCell, "00:10")
 	smallCell.NumFmt = "hh:mm"
-	c.Assert(smallCell.FormattedValue(), Equals, "00:14")
+	fvc.Equals(smallCell, "00:10")
 
 	cell.NumFmt = "h:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "6:00:00")
+	fvc.Equals(cell, "18:00:00")
 	cell.NumFmt = "hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	fvc.Equals(cell, "18:00:00")
 
 	smallCell.NumFmt = "hh:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "00:14:47")
+	fvc.Equals(smallCell, "00:10:04")
 	smallCell.NumFmt = "h:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "12:14:47")
+	fvc.Equals(smallCell, "00:10:04")
 
 	cell.NumFmt = "m/d/yy h:mm"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/03 6:00")
+	fvc.Equals(cell, "11/22/03 18:00")
 	cell.NumFmt = "m/d/yy hh:mm"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/03 18:00")
+	fvc.Equals(cell, "11/22/03 18:00")
 	smallCell.NumFmt = "m/d/yy h:mm"
-	c.Assert(smallCell.FormattedValue(), Equals, "12/30/99 12:14") // Note, that's 1899
+	fvc.Equals(smallCell, "12/30/99 00:10")
 	smallCell.NumFmt = "m/d/yy hh:mm"
-	c.Assert(smallCell.FormattedValue(), Equals, "12/30/99 00:14") // Note, that's 1899
+	fvc.Equals(smallCell, "12/30/99 00:10")
 	earlyCell.NumFmt = "m/d/yy hh:mm"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/00 02:24") // and 1900
+	fvc.Equals(earlyCell, "1/1/00 02:24")
 	earlyCell.NumFmt = "m/d/yy h:mm"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/00 2:24") // and 1900
+	fvc.Equals(earlyCell, "1/1/00 02:24")
 
 	cell.NumFmt = "mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "00:00")
+	fvc.Equals(cell, "00:00")
 	smallCell.NumFmt = "mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "14:47")
+	fvc.Equals(smallCell, "10:04")
 
 	cell.NumFmt = "[hh]:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	fvc.Equals(cell, "18:00:00")
 	cell.NumFmt = "[h]:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "6:00:00")
+	fvc.Equals(cell, "18:00:00")
 	smallCell.NumFmt = "[h]:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "14:47")
-
-	cell.NumFmt = "mmss.0" // I'm not sure about these.
-	c.Assert(cell.FormattedValue(), Equals, "0000.0086")
-	smallCell.NumFmt = "mmss.0"
-	c.Assert(smallCell.FormattedValue(), Equals, "1447.9999")
+	fvc.Equals(smallCell, "10:04")
+
+	const (
+		expect1 = "0000.0086"
+		expect2 = "1004.8000"
+		format  = "mmss.0000"
+		tlen    = len(format)
+	)
+
+	for i := 0; i < 3; i++ {
+		tfmt := format[0 : tlen-i]
+		cell.NumFmt = tfmt
+		fvc.Equals(cell, expect1[0:tlen-i])
+		smallCell.NumFmt = tfmt
+		fvc.Equals(smallCell, expect2[0:tlen-i])
+	}
 
 	cell.NumFmt = "yyyy\\-mm\\-dd"
-	c.Assert(cell.FormattedValue(), Equals, "2003\\-11\\-22")
+	fvc.Equals(cell, "2003\\-11\\-22")
 
 	cell.NumFmt = "dd/mm/yyyy hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "22/11/2003 18:00:00")
+	fvc.Equals(cell, "22/11/2003 18:00:00")
 
 	cell.NumFmt = "dd/mm/yy"
-	c.Assert(cell.FormattedValue(), Equals, "22/11/03")
+	fvc.Equals(cell, "22/11/03")
 	earlyCell.NumFmt = "dd/mm/yy"
-	c.Assert(earlyCell.FormattedValue(), Equals, "01/01/00")
+	fvc.Equals(earlyCell, "01/01/00")
 
 	cell.NumFmt = "hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "18:00:00")
+	fvc.Equals(cell, "18:00:00")
 	smallCell.NumFmt = "hh:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "00:14:47")
+	fvc.Equals(smallCell, "00:10:04")
 
 	cell.NumFmt = "dd/mm/yy\\ hh:mm"
-	c.Assert(cell.FormattedValue(), Equals, "22/11/03\\ 18:00")
+	fvc.Equals(cell, "22/11/03\\ 18:00")
 
 	cell.NumFmt = "yyyy/mm/dd"
-	c.Assert(cell.FormattedValue(), Equals, "2003/11/22")
+	fvc.Equals(cell, "2003/11/22")
 
 	cell.NumFmt = "yy-mm-dd"
-	c.Assert(cell.FormattedValue(), Equals, "03-11-22")
+	fvc.Equals(cell, "03-11-22")
 
 	cell.NumFmt = "d-mmm-yyyy"
-	c.Assert(cell.FormattedValue(), Equals, "22-Nov-2003")
+	fvc.Equals(cell, "22-Nov-2003")
 	earlyCell.NumFmt = "d-mmm-yyyy"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1-Jan-1900")
+	fvc.Equals(earlyCell, "1-Jan-1900")
 
 	cell.NumFmt = "m/d/yy"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/03")
+	fvc.Equals(cell, "11/22/03")
 	earlyCell.NumFmt = "m/d/yy"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/00")
+	fvc.Equals(earlyCell, "1/1/00")
 
 	cell.NumFmt = "m/d/yyyy"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/2003")
+	fvc.Equals(cell, "11/22/2003")
 	earlyCell.NumFmt = "m/d/yyyy"
-	c.Assert(earlyCell.FormattedValue(), Equals, "1/1/1900")
+	fvc.Equals(earlyCell, "1/1/1900")
 
 	cell.NumFmt = "dd-mmm-yyyy"
-	c.Assert(cell.FormattedValue(), Equals, "22-Nov-2003")
+	fvc.Equals(cell, "22-Nov-2003")
 
 	cell.NumFmt = "dd/mm/yyyy"
-	c.Assert(cell.FormattedValue(), Equals, "22/11/2003")
+	fvc.Equals(cell, "22/11/2003")
 
 	cell.NumFmt = "mm/dd/yy hh:mm am/pm"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/03 18:00 pm")
+	fvc.Equals(cell, "11/22/03 06:00 pm")
 	cell.NumFmt = "mm/dd/yy h:mm am/pm"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/03 6:00 pm")
+	fvc.Equals(cell, "11/22/03 6:00 pm")
 
 	cell.NumFmt = "mm/dd/yyyy hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "11/22/2003 18:00:00")
+	fvc.Equals(cell, "11/22/2003 18:00:00")
 	smallCell.NumFmt = "mm/dd/yyyy hh:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "12/30/1899 00:14:47")
+	fvc.Equals(smallCell, "12/30/1899 00:10:04")
 
 	cell.NumFmt = "yyyy-mm-dd hh:mm:ss"
-	c.Assert(cell.FormattedValue(), Equals, "2003-11-22 18:00:00")
+	fvc.Equals(cell, "2003-11-22 18:00:00")
 	smallCell.NumFmt = "yyyy-mm-dd hh:mm:ss"
-	c.Assert(smallCell.FormattedValue(), Equals, "1899-12-30 00:14:47")
+	fvc.Equals(smallCell, "1899-12-30 00:10:04")
+
+	cell.NumFmt = "mmmm d, yyyy"
+	fvc.Equals(cell, "November 22, 2003")
+	smallCell.NumFmt = "mmmm d, yyyy"
+	fvc.Equals(smallCell, "December 30, 1899")
+
+	cell.NumFmt = "dddd, mmmm dd, yyyy"
+	fvc.Equals(cell, "Saturday, November 22, 2003")
+	smallCell.NumFmt = "dddd, mmmm dd, yyyy"
+	fvc.Equals(smallCell, "Saturday, December 30, 1899")
 }
 
 // test setters and getters
@@ -336,25 +392,32 @@ func (s *CellSuite) TestSetterGetters(c *C) {
 	cell := Cell{}
 
 	cell.SetString("hello world")
-	c.Assert(cell.String(), Equals, "hello world")
+	if val, err := cell.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "hello world")
+	}
 	c.Assert(cell.Type(), Equals, CellTypeString)
 
 	cell.SetInt(1024)
 	intValue, _ := cell.Int()
 	c.Assert(intValue, Equals, 1024)
-	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+	c.Assert(cell.NumFmt, Equals, builtInNumFmt[builtInNumFmtIndex_GENERAL])
+	c.Assert(cell.Type(), Equals, CellTypeGeneral)
 
 	cell.SetInt64(1024)
 	int64Value, _ := cell.Int64()
 	c.Assert(int64Value, Equals, int64(1024))
-	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+	c.Assert(cell.NumFmt, Equals, builtInNumFmt[builtInNumFmtIndex_GENERAL])
+	c.Assert(cell.Type(), Equals, CellTypeGeneral)
 
 	cell.SetFloat(1.024)
 	float, _ := cell.Float()
 	intValue, _ = cell.Int() // convert
 	c.Assert(float, Equals, 1.024)
 	c.Assert(intValue, Equals, 1)
-	c.Assert(cell.Type(), Equals, CellTypeNumeric)
+	c.Assert(cell.NumFmt, Equals, builtInNumFmt[builtInNumFmtIndex_GENERAL])
+	c.Assert(cell.Type(), Equals, CellTypeGeneral)
 
 	cell.SetFormula("10+20")
 	c.Assert(cell.Formula(), Equals, "10+20")
@@ -370,7 +433,11 @@ func (s *CellSuite) TestOddInput(c *C) {
 	odd := `[1],[12,"DATE NOT NULL DEFAULT '0000-00-00'"]`
 	cell.Value = odd
 	cell.NumFmt = "@"
-	c.Assert(cell.String(), Equals, odd)
+	if val, err := cell.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, odd)
+	}
 }
 
 // TestBool tests basic Bool getting and setting booleans.
@@ -396,3 +463,97 @@ func (s *CellSuite) TestStringBool(c *C) {
 	cell.SetString("0")
 	c.Assert(cell.Bool(), Equals, true)
 }
+
+// TestSetValue tests whether SetValue handle properly for different type values.
+func (s *CellSuite) TestSetValue(c *C) {
+	cell := Cell{}
+
+	// int
+	for _, i := range []interface{}{1, int8(1), int16(1), int32(1), int64(1)} {
+		cell.SetValue(i)
+		val, err := cell.Int64()
+		c.Assert(err, IsNil)
+		c.Assert(val, Equals, int64(1))
+	}
+
+	// float
+	for _, i := range []interface{}{1.11, float32(1.11), float64(1.11)} {
+		cell.SetValue(i)
+		val, err := cell.Float()
+		c.Assert(err, IsNil)
+		c.Assert(val, Equals, 1.11)
+	}
+
+	// time
+	cell.SetValue(time.Unix(0, 0))
+	val, err := cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(math.Floor(val), Equals, 25569.0)
+
+	// string and nil
+	for _, i := range []interface{}{nil, "", []byte("")} {
+		cell.SetValue(i)
+		c.Assert(cell.Value, Equals, "")
+	}
+
+	// others
+	cell.SetValue([]string{"test"})
+	c.Assert(cell.Value, Equals, "[test]")
+}
+
+func (s *CellSuite) TestSetDateWithOptions(c *C) {
+	cell := Cell{}
+
+	// time
+	cell.SetDate(time.Unix(0, 0))
+	val, err := cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(math.Floor(val), Equals, 25569.0)
+
+	// our test subject
+	date2016UTC := time.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC)
+
+	// test ny timezone
+	nyTZ, err := time.LoadLocation("America/New_York")
+	c.Assert(err, IsNil)
+	cell.SetDateWithOptions(date2016UTC, DateTimeOptions{
+		ExcelTimeFormat: "test_format1",
+		Location:        nyTZ,
+	})
+	val, err = cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 7, 0, 0, 0, time.UTC)))
+
+	// test jp timezone
+	jpTZ, err := time.LoadLocation("Asia/Tokyo")
+	c.Assert(err, IsNil)
+	cell.SetDateWithOptions(date2016UTC, DateTimeOptions{
+		ExcelTimeFormat: "test_format2",
+		Location:        jpTZ,
+	})
+	val, err = cell.Float()
+	c.Assert(err, IsNil)
+	c.Assert(val, Equals, TimeToExcelTime(time.Date(2016, 1, 1, 21, 0, 0, 0, time.UTC)))
+}
+
+func (s *CellSuite) TestIsTimeFormat(c *C) {
+	c.Assert(isTimeFormat("yy"), Equals, true)
+	c.Assert(isTimeFormat("hh"), Equals, true)
+	c.Assert(isTimeFormat("h"), Equals, true)
+	c.Assert(isTimeFormat("am/pm"), Equals, true)
+	c.Assert(isTimeFormat("AM/PM"), Equals, true)
+	c.Assert(isTimeFormat("A/P"), Equals, true)
+	c.Assert(isTimeFormat("a/p"), Equals, true)
+	c.Assert(isTimeFormat("ss"), Equals, true)
+	c.Assert(isTimeFormat("mm"), Equals, true)
+	c.Assert(isTimeFormat(":"), Equals, true)
+	c.Assert(isTimeFormat("z"), Equals, false)
+}
+
+func (s *CellSuite) TestIs12HourtTime(c *C) {
+	c.Assert(is12HourTime("am/pm"), Equals, true)
+	c.Assert(is12HourTime("AM/PM"), Equals, true)
+	c.Assert(is12HourTime("a/p"), Equals, true)
+	c.Assert(is12HourTime("A/P"), Equals, true)
+	c.Assert(is12HourTime("x"), Equals, false)
+}

+ 8 - 7
common/src/github.com/tealeg/xlsx/col.go

@@ -4,13 +4,14 @@ package xlsx
 const ColWidth = 9.5
 
 type Col struct {
-	Min       int
-	Max       int
-	Hidden    bool
-	Width     float64
-	Collapsed bool
-	numFmt    string
-	style     *Style
+	Min          int
+	Max          int
+	Hidden       bool
+	Width        float64
+	Collapsed    bool
+	OutlineLevel uint8
+	numFmt       string
+	style        *Style
 }
 
 func (c *Col) SetType(cellType CellType) {

+ 30 - 13
common/src/github.com/tealeg/xlsx/compatibility_test.go

@@ -26,8 +26,11 @@ func (m *MacExcelSuite) TestMacExcel(c *C) {
 	xlsxFile, err := OpenFile("./testdocs/macExcelTest.xlsx")
 	c.Assert(err, IsNil)
 	c.Assert(xlsxFile, NotNil)
-	s := xlsxFile.Sheet["普通技能"].Cell(0, 0).String()
-	c.Assert(s, Equals, "编号")
+	if val, err := xlsxFile.Sheet["普通技能"].Cell(0, 0).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "编号")
+	}
 }
 
 type MacNumbersSuite struct{}
@@ -42,8 +45,11 @@ func (m *MacNumbersSuite) TestMacNumbers(c *C) {
 	c.Assert(xlsxFile, NotNil)
 	sheet, ok := xlsxFile.Sheet["主动技能"]
 	c.Assert(ok, Equals, true)
-	s := sheet.Cell(0, 0).String()
-	c.Assert(s, Equals, "编号")
+	if val, err := sheet.Cell(0, 0).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "编号")
+	}
 }
 
 type WpsBlankLineSuite struct{}
@@ -59,20 +65,31 @@ func (w *WpsBlankLineSuite) TestWpsBlankLine(c *C) {
 	sheet := xlsxFile.Sheet["Sheet1"]
 	row := sheet.Rows[0]
 	cell := row.Cells[0]
-	s := cell.String()
+
 	expected := "编号"
-	c.Assert(s, Equals, expected)
+	var val string
+
+	if val, err = cell.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, expected)
 
 	row = sheet.Rows[2]
 	cell = row.Cells[0]
-	s = cell.String()
-	c.Assert(s, Equals, expected)
+	if val, err = cell.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, expected)
 
 	row = sheet.Rows[4]
 	cell = row.Cells[1]
-	s = cell.String()
-	c.Assert(s, Equals, "")
-
-	s = sheet.Rows[4].Cells[2].String()
-	c.Assert(s, Equals, expected)
+	if val, err = cell.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, "")
+
+	if val, err = sheet.Rows[4].Cells[2].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, expected)
 }

+ 16 - 9
common/src/github.com/tealeg/xlsx/date.go

@@ -24,16 +24,23 @@ func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
 
 // Return the integer values for hour, minutes, seconds and
 // nanoseconds that comprised a given fraction of a day.
+// values would round to 1 us.
 func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
-	f := 5184000000000000 * fraction
-	nanoseconds = int(math.Mod(f, 1000000000))
-	f = f / 1000000000
-	seconds = int(math.Mod(f, 3600))
-	f = f / 3600
-	minutes = int(math.Mod(f, 60))
-	f = f / 60
-	hours = int(f)
-	return hours, minutes, seconds, nanoseconds
+
+	const (
+		c1us  = 1e3
+		c1s   = 1e9
+		c1day = 24 * 60 * 60 * c1s
+	)
+
+	frac := int64(c1day*fraction + c1us/2)
+	nanoseconds = int((frac%c1s)/c1us) * c1us
+	frac /= c1s
+	seconds = int(frac % 60)
+	frac /= 60
+	minutes = int(frac % 60)
+	hours = int(frac / 60)
+	return
 }
 
 func julianDateToGregorianTime(part1, part2 float64) time.Time {

+ 7 - 2
common/src/github.com/tealeg/xlsx/date_test.go

@@ -29,9 +29,14 @@ func (d *DateSuite) TestJulianDateToGregorianTime(c *C) {
 	c.Assert(julianDateToGregorianTime(2400000.5, 51544.5),
 		Equals, time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC))
 	c.Assert(julianDateToGregorianTime(2400000.5, 51544.245),
-		Equals, time.Date(2000, 1, 1, 6, 40, 0, 13578, time.UTC))
+		Equals, time.Date(2000, 1, 1, 5, 52, 48, 0, time.UTC))
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.2456),
+		Equals, time.Date(2000, 1, 1, 5, 53, 39, 840000000, time.UTC))
+	/* test rounding: 0.24560789123*24*3600 = 21220.521802272 */
+	c.Assert(julianDateToGregorianTime(2400000.5, 51544.24560789123),
+		Equals, time.Date(2000, 1, 1, 5, 53, 40, 521802000, time.UTC))
 	c.Assert(julianDateToGregorianTime(2400000.5, 51544.1),
-		Equals, time.Date(2000, 1, 1, 3, 22, 59, 999992456, time.UTC))
+		Equals, time.Date(2000, 1, 1, 2, 24, 00, 0, time.UTC))
 	c.Assert(julianDateToGregorianTime(2400000.5, 51544.75),
 		Equals, time.Date(2000, 1, 1, 18, 0, 0, 0, time.UTC))
 }

+ 36 - 0
common/src/github.com/tealeg/xlsx/example_read_test.go

@@ -0,0 +1,36 @@
+package xlsx
+
+import "fmt"
+
+func ExampleRow_ReadStruct() {
+	//example type
+	type structTest struct {
+		IntVal     int     `xlsx:"0"`
+		StringVal  string  `xlsx:"1"`
+		FloatVal   float64 `xlsx:"2"`
+		IgnoredVal int     `xlsx:"-"`
+		BoolVal    bool    `xlsx:"4"`
+	}
+	structVal := structTest{
+		IntVal:     16,
+		StringVal:  "heyheyhey :)!",
+		FloatVal:   3.14159216,
+		IgnoredVal: 7,
+		BoolVal:    true,
+	}
+	//create a new xlsx file and write a struct
+	//in a new row
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestRead")
+	row := sheet.AddRow()
+	row.WriteStruct(&structVal, -1)
+
+	//read the struct from the same row
+	readStruct := &structTest{}
+	err := row.ReadStruct(readStruct)
+	if err != nil {
+		fmt.Println(readStruct)
+	} else {
+		panic(err)
+	}
+}

+ 88 - 69
common/src/github.com/tealeg/xlsx/file.go

@@ -4,6 +4,7 @@ import (
 	"archive/zip"
 	"bytes"
 	"encoding/xml"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -21,14 +22,16 @@ type File struct {
 	Sheets         []*Sheet
 	Sheet          map[string]*Sheet
 	theme          *theme
+	DefinedNames   []*xlsxDefinedName
 }
 
 // Create a new File
-func NewFile() (file *File) {
-	file = &File{}
-	file.Sheet = make(map[string]*Sheet)
-	file.Sheets = make([]*Sheet, 0)
-	return
+func NewFile() *File {
+	return &File{
+		Sheet:        make(map[string]*Sheet),
+		Sheets:       make([]*Sheet, 0),
+		DefinedNames: make([]*xlsxDefinedName, 0),
+	}
 }
 
 // OpenFile() take the name of an XLSX file and returns a populated
@@ -45,22 +48,19 @@ func OpenFile(filename string) (file *File, err error) {
 
 // OpenBinary() take bytes of an XLSX file and returns a populated
 // xlsx.File struct for it.
-func OpenBinary(bs []byte) (file *File, err error) {
+func OpenBinary(bs []byte) (*File, error) {
 	r := bytes.NewReader(bs)
-	file, err = OpenReaderAt(r, int64(r.Len()))
-	return
+	return OpenReaderAt(r, int64(r.Len()))
 }
 
 // OpenReaderAt() take io.ReaderAt of an XLSX file and returns a populated
 // xlsx.File struct for it.
-func OpenReaderAt(r io.ReaderAt, size int64) (file *File, err error) {
-	var f *zip.Reader
-	f, err = zip.NewReader(r, size)
+func OpenReaderAt(r io.ReaderAt, size int64) (*File, error) {
+	file, err := zip.NewReader(r, size)
 	if err != nil {
 		return nil, err
 	}
-	file, err = ReadZipReader(f)
-	return
+	return ReadZipReader(file)
 }
 
 // A convenient wrapper around File.ToSlice, FileToSlice will
@@ -87,89 +87,94 @@ func FileToSlice(path string) ([][][]string, error) {
 
 // Save the File to an xlsx file at the provided path.
 func (f *File) Save(path string) (err error) {
-	var target *os.File
-
-	target, err = os.Create(path)
+	target, err := os.Create(path)
 	if err != nil {
-		return
+		return err
 	}
-
 	err = f.Write(target)
 	if err != nil {
-		return
+		return err
 	}
-
 	return target.Close()
 }
 
 // Write the File to io.Writer as xlsx
 func (f *File) Write(writer io.Writer) (err error) {
-	var parts map[string]string
-	var zipWriter *zip.Writer
-
-	parts, err = f.MarshallParts()
+	parts, err := f.MarshallParts()
 	if err != nil {
 		return
 	}
-
-	zipWriter = zip.NewWriter(writer)
-
+	zipWriter := zip.NewWriter(writer)
 	for partName, part := range parts {
-		var writer io.Writer
-		writer, err = zipWriter.Create(partName)
+		w, err := zipWriter.Create(partName)
 		if err != nil {
-			return
+			return err
 		}
-		_, err = writer.Write([]byte(part))
+		_, err = w.Write([]byte(part))
 		if err != nil {
-			return
+			return err
 		}
 	}
-
-	err = zipWriter.Close()
-
-	return
+	return zipWriter.Close()
 }
 
 // Add a new Sheet, with the provided name, to a File
-func (f *File) AddSheet(sheetName string) (sheet *Sheet) {
-	sheet = &Sheet{Name: sheetName, File: f}
-	if len(f.Sheets) == 0 {
-		sheet.Selected = true
+func (f *File) AddSheet(sheetName string) (*Sheet, error) {
+	if _, exists := f.Sheet[sheetName]; exists {
+		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))
+	}
+	sheet := &Sheet{
+		Name:     sheetName,
+		File:     f,
+		Selected: len(f.Sheets) == 0,
 	}
 	f.Sheet[sheetName] = sheet
 	f.Sheets = append(f.Sheets, sheet)
-	return sheet
+	return sheet, nil
+}
+
+// Appends an existing Sheet, with the provided name, to a File
+func (f *File) AppendSheet(sheet Sheet, sheetName string) (*Sheet, error) {
+	if _, exists := f.Sheet[sheetName]; exists {
+		return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
+	}
+	sheet.Name = sheetName
+	sheet.File = f
+	sheet.Selected = len(f.Sheets) == 0
+	f.Sheet[sheetName] = &sheet
+	f.Sheets = append(f.Sheets, &sheet)
+	return &sheet, nil
 }
 
 func (f *File) makeWorkbook() xlsxWorkbook {
-	var workbook xlsxWorkbook
-	workbook = xlsxWorkbook{}
-	workbook.FileVersion = xlsxFileVersion{}
-	workbook.FileVersion.AppName = "Go XLSX"
-	workbook.WorkbookPr = xlsxWorkbookPr{
-		BackupFile:  false,
-		ShowObjects: "all"}
-	workbook.BookViews = xlsxBookViews{}
-	workbook.BookViews.WorkBookView = make([]xlsxWorkBookView, 1)
-	workbook.BookViews.WorkBookView[0] = xlsxWorkBookView{
-		ActiveTab:            0,
-		FirstSheet:           0,
-		ShowHorizontalScroll: true,
-		ShowSheetTabs:        true,
-		ShowVerticalScroll:   true,
-		TabRatio:             204,
-		WindowHeight:         8192,
-		WindowWidth:          16384,
-		XWindow:              "0",
-		YWindow:              "0"}
-	workbook.Sheets = xlsxSheets{}
-	workbook.Sheets.Sheet = make([]xlsxSheet, len(f.Sheets))
-	workbook.CalcPr.IterateCount = 100
-	workbook.CalcPr.RefMode = "A1"
-	workbook.CalcPr.Iterate = false
-	workbook.CalcPr.IterateDelta = 0.001
-	return workbook
+	return xlsxWorkbook{
+		FileVersion: xlsxFileVersion{AppName: "Go XLSX"},
+		WorkbookPr:  xlsxWorkbookPr{ShowObjects: "all"},
+		BookViews: xlsxBookViews{
+			WorkBookView: []xlsxWorkBookView{
+				{
+					ShowHorizontalScroll: true,
+					ShowSheetTabs:        true,
+					ShowVerticalScroll:   true,
+					TabRatio:             204,
+					WindowHeight:         8192,
+					WindowWidth:          16384,
+					XWindow:              "0",
+					YWindow:              "0",
+				},
+			},
+		},
+		Sheets: xlsxSheets{Sheet: make([]xlsxSheet, len(f.Sheets))},
+		CalcPr: xlsxCalcPr{
+			IterateCount: 100,
+			RefMode:      "A1",
+			Iterate:      false,
+			IterateDelta: 0.001,
+		},
+	}
 }
 
 // Some tools that read XLSX files have very strict requirements about
@@ -217,6 +222,10 @@ func (f *File) MarshallParts() (map[string]string, error) {
 		f.styles = newXlsxStyleSheet(f.theme)
 	}
 	f.styles.reset()
+	if len(f.Sheets) == 0 {
+		err := errors.New("Workbook must contains atleast one worksheet")
+		return nil, err
+	}
 	for _, sheet := range f.Sheets {
 		xSheet := sheet.makeXLSXSheet(refTable, f.styles)
 		rId := fmt.Sprintf("rId%d", sheetIndex)
@@ -306,7 +315,17 @@ func (file *File) ToSlice() (output [][][]string, err error) {
 			}
 			r := []string{}
 			for _, cell := range row.Cells {
-				r = append(r, cell.String())
+				str, err := cell.FormattedValue()
+				if err != nil {
+					// Recover from strconv.NumError if the value is an empty string,
+					// and insert an empty string in the output.
+					if numErr, ok := err.(*strconv.NumError); ok && numErr.Num == "" {
+						str = ""
+					} else {
+						return output, err
+					}
+				}
+				r = append(r, str)
 			}
 			s = append(s, r)
 		}

+ 109 - 20
common/src/github.com/tealeg/xlsx/file_test.go

@@ -115,6 +115,14 @@ func (l *FileSuite) TestReadStylesFromZipFile(c *C) {
 		NumFmtId:        164}
 	testXf(c, &xf, expectedXf)
 
+	c.Assert(xf.Alignment, NotNil)
+	c.Assert(xf.Alignment.Horizontal, Equals, "general")
+	c.Assert(xf.Alignment.Indent, Equals, 0)
+	c.Assert(xf.Alignment.ShrinkToFit, Equals, false)
+	c.Assert(xf.Alignment.TextRotation, Equals, 0)
+	c.Assert(xf.Alignment.Vertical, Equals, "bottom")
+	c.Assert(xf.Alignment.WrapText, Equals, false)
+
 	cellXfCount = xlsxFile.styles.CellXfs.Count
 	c.Assert(cellXfCount, Equals, 3)
 
@@ -151,6 +159,7 @@ func (l *FileSuite) TestGetStyleFromZipFile(c *C) {
 	var xlsxFile *File
 	var err error
 	var style *Style
+	var val string
 
 	xlsxFile, err = OpenFile("./testdocs/testfile.xlsx")
 	c.Assert(err, IsNil)
@@ -162,7 +171,10 @@ func (l *FileSuite) TestGetStyleFromZipFile(c *C) {
 	row0 := tabelle1.Rows[0]
 	cellFoo := row0.Cells[0]
 	style = cellFoo.GetStyle()
-	c.Assert(cellFoo.String(), Equals, "Foo")
+	if val, err = cellFoo.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, "Foo")
 	c.Assert(style.Fill.BgColor, Equals, "FF33CCCC")
 	c.Assert(style.ApplyFill, Equals, false)
 	c.Assert(style.ApplyFont, Equals, true)
@@ -170,12 +182,18 @@ func (l *FileSuite) TestGetStyleFromZipFile(c *C) {
 	row1 := tabelle1.Rows[1]
 	cellQuuk := row1.Cells[1]
 	style = cellQuuk.GetStyle()
-	c.Assert(cellQuuk.String(), Equals, "Quuk")
+	if val, err = cellQuuk.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, "Quuk")
 	c.Assert(style.Border.Left, Equals, "thin")
 	c.Assert(style.ApplyBorder, Equals, true)
 
 	cellBar := row0.Cells[1]
-	c.Assert(cellBar.String(), Equals, "Bar")
+	if val, err = cellBar.FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	c.Assert(val, Equals, "Bar")
 	c.Assert(cellBar.GetStyle().Fill.BgColor, Equals, "")
 }
 
@@ -206,8 +224,11 @@ func (l *FileSuite) TestCreateSheet(c *C) {
 	row = sheet.Rows[0]
 	c.Assert(len(row.Cells), Equals, 2)
 	cell := row.Cells[0]
-	cellstring := cell.String()
-	c.Assert(cellstring, Equals, "Foo")
+	if val, err := cell.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Foo")
+	}
 }
 
 // Test that we can add a sheet to a File
@@ -215,18 +236,51 @@ func (l *FileSuite) TestAddSheet(c *C) {
 	var f *File
 
 	f = NewFile()
-	sheet := f.AddSheet("MySheet")
+	sheet, err := f.AddSheet("MySheet")
+	c.Assert(err, IsNil)
 	c.Assert(sheet, NotNil)
 	c.Assert(len(f.Sheets), Equals, 1)
 	c.Assert(f.Sheet["MySheet"], Equals, sheet)
 }
 
+// Test that AddSheet returns an error if you try to add two sheets with the same name
+func (l *FileSuite) TestAddSheetWithDuplicateName(c *C) {
+	f := NewFile()
+	_, err := f.AddSheet("MySheet")
+	c.Assert(err, IsNil)
+	_, err = f.AddSheet("MySheet")
+	c.Assert(err, ErrorMatches, "duplicate sheet name 'MySheet'.")
+}
+
+// Test that we can append a sheet to a File
+func (l *FileSuite) TestAppendSheet(c *C) {
+	var f *File
+
+	f = NewFile()
+	s := Sheet{}
+	sheet, err := f.AppendSheet(s, "MySheet")
+	c.Assert(err, IsNil)
+	c.Assert(sheet, NotNil)
+	c.Assert(len(f.Sheets), Equals, 1)
+	c.Assert(f.Sheet["MySheet"], Equals, sheet)
+}
+
+// Test that AppendSheet returns an error if you try to add two sheets with the same name
+func (l *FileSuite) TestAppendSheetWithDuplicateName(c *C) {
+	f := NewFile()
+	s := Sheet{}
+	_, err := f.AppendSheet(s, "MySheet")
+	c.Assert(err, IsNil)
+	_, err = f.AppendSheet(s, "MySheet")
+	c.Assert(err, ErrorMatches, "duplicate sheet name 'MySheet'.")
+}
+
 // Test that we can get the Nth sheet
 func (l *FileSuite) TestNthSheet(c *C) {
 	var f *File
 
 	f = NewFile()
-	sheet := f.AddSheet("MySheet")
+	sheet, _ := f.AddSheet("MySheet")
 	sheetByIndex := f.Sheets[0]
 	sheetByName := f.Sheet["MySheet"]
 	c.Assert(sheetByIndex, NotNil)
@@ -268,11 +322,11 @@ func (l *FileSuite) TestMarshalWorkbook(c *C) {
 func (l *FileSuite) TestMarshalFile(c *C) {
 	var f *File
 	f = NewFile()
-	sheet1 := f.AddSheet("MySheet")
+	sheet1, _ := f.AddSheet("MySheet")
 	row1 := sheet1.AddRow()
 	cell1 := row1.AddCell()
 	cell1.SetString("A cell!")
-	sheet2 := f.AddSheet("AnotherSheet")
+	sheet2, _ := f.AddSheet("AnotherSheet")
 	row2 := sheet2.AddRow()
 	cell2 := row2.AddCell()
 	cell2.SetString("A cell!")
@@ -282,11 +336,13 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 
 	// sheets
 	expectedSheet1 := `<?xml version="1.0" encoding="UTF-8"?>
-<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="0" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="true" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+
 	c.Assert(parts["xl/worksheets/sheet1.xml"], Equals, expectedSheet1)
 
 	expectedSheet2 := `<?xml version="1.0" encoding="UTF-8"?>
-<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="false" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="0" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr filterMode="false"><pageSetUpPr fitToPage="false"></pageSetUpPr></sheetPr><dimension ref="A1"></dimension><sheetViews><sheetView windowProtection="false" showFormulas="false" showGridLines="true" showRowColHeaders="true" showZeros="true" rightToLeft="false" tabSelected="false" showOutlineSymbols="true" defaultGridColor="true" view="normal" topLeftCell="A1" colorId="64" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100" workbookViewId="0"><selection pane="topLeft" activeCell="A1" activeCellId="0" sqref="A1"></selection></sheetView></sheetViews><sheetFormatPr defaultRowHeight="12.85"></sheetFormatPr><cols><col collapsed="false" hidden="false" max="1" min="1" style="1" width="9.5"></col></cols><sheetData><row r="1"><c r="A1" s="1" t="s"><v>0</v></c></row></sheetData><printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"></printOptions><pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"></pageMargins><pageSetup paperSize="9" scale="100" firstPageNumber="1" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="true" horizontalDpi="300" verticalDpi="300" copies="1"></pageSetup><headerFooter differentFirst="false" differentOddEven="false"><oddHeader>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12&amp;A</oddHeader><oddFooter>&amp;C&amp;&#34;Times New Roman,Regular&#34;&amp;12Page &amp;P</oddFooter></headerFooter></worksheet>`
+
 	c.Assert(parts["xl/worksheets/sheet2.xml"], Equals, expectedSheet2)
 
 	// .rels.xml
@@ -660,7 +716,8 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="2"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill><fill><patternFill patternType="lightGray"/></fill></fills><borders count="1"><border><left style="none"></left><right style="none"></right><top style="none"></top><bottom style="none"></bottom></border></borders><cellXfs count="2"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyNumberFormat="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="general" indent="0" shrinkToFit="0" textRotation="0" vertical="bottom" wrapText="0"/></xf></cellXfs></styleSheet>`
+
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 
@@ -669,11 +726,11 @@ func (l *FileSuite) TestSaveFile(c *C) {
 	var tmpPath string = c.MkDir()
 	var f *File
 	f = NewFile()
-	sheet1 := f.AddSheet("MySheet")
+	sheet1, _ := f.AddSheet("MySheet")
 	row1 := sheet1.AddRow()
 	cell1 := row1.AddCell()
 	cell1.Value = "A cell!"
-	sheet2 := f.AddSheet("AnotherSheet")
+	sheet2, _ := f.AddSheet("AnotherSheet")
 	row2 := sheet2.AddRow()
 	cell2 := row2.AddCell()
 	cell2.Value = "A cell!"
@@ -706,6 +763,12 @@ func (s *SliceReaderSuite) TestFileToSlice(c *C) {
 	fileToSliceCheckOutput(c, output)
 }
 
+func (s *SliceReaderSuite) TestFileToSliceMissingCol(c *C) {
+	// Test xlsx file with the A column removed
+	_, err := FileToSlice("./testdocs/testFileToSlice.xlsx")
+	c.Assert(err, IsNil)
+}
+
 func (s *SliceReaderSuite) TestFileObjToSlice(c *C) {
 	f, err := OpenFile("./testdocs/testfile.xlsx")
 	output, err := f.ToSlice()
@@ -739,11 +802,19 @@ func (l *FileSuite) TestReadWorkbookWithTypes(c *C) {
 
 	// string 1
 	c.Assert(sheet.Rows[0].Cells[0].Type(), Equals, CellTypeString)
-	c.Assert(sheet.Rows[0].Cells[0].String(), Equals, "hello world")
+	if val, err := sheet.Rows[0].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "hello world")
+	}
 
 	// string 2
 	c.Assert(sheet.Rows[1].Cells[0].Type(), Equals, CellTypeString)
-	c.Assert(sheet.Rows[1].Cells[0].String(), Equals, "日本語")
+	if val, err := sheet.Rows[1].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "日本語")
+	}
 
 	// integer
 	c.Assert(sheet.Rows[2].Cells[0].Type(), Equals, CellTypeNumeric)
@@ -780,8 +851,17 @@ func (s *SliceReaderSuite) TestFileWithEmptyRows(c *C) {
 	c.Assert(err, IsNil)
 	sheet, ok := f.Sheet["EmptyRows"]
 	c.Assert(ok, Equals, true)
-	c.Assert(sheet.Cell(0, 0).String(), Equals, "")
-	c.Assert(sheet.Cell(2, 0).String(), Equals, "A3")
+
+	if val, err := sheet.Cell(0, 0).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := sheet.Cell(2, 0).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "A3")
+	}
 }
 
 func (s *SliceReaderSuite) TestFileWithEmptyCols(c *C) {
@@ -789,6 +869,15 @@ func (s *SliceReaderSuite) TestFileWithEmptyCols(c *C) {
 	c.Assert(err, IsNil)
 	sheet, ok := f.Sheet["EmptyCols"]
 	c.Assert(ok, Equals, true)
-	c.Assert(sheet.Cell(0, 0).String(), Equals, "")
-	c.Assert(sheet.Cell(0, 2).String(), Equals, "C1")
+
+	if val, err := sheet.Cell(0, 0).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := sheet.Cell(0, 2).FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "C1")
+	}
 }

+ 1 - 1
common/src/github.com/tealeg/xlsx/fuzzy_test.go

@@ -108,7 +108,7 @@ func variationsXML(f *zip.File) chan tokenchange {
 
 	go func() {
 		//Over every token we want to break
-		for TokenToBreak, _ := range tokenList {
+		for TokenToBreak := range tokenList {
 			//Get the ways we can break that token
 			for _, brokenToken := range getTokenVariations(tokenList[TokenToBreak]) {
 				var buf bytes.Buffer

+ 141 - 43
common/src/github.com/tealeg/xlsx/lib.go

@@ -162,10 +162,10 @@ func intOnlyMapF(rune rune) rune {
 	return -1
 }
 
-// getCoordsFromCellIDString returns the zero based cartesian
+// GetCoordsFromCellIDString returns the zero based cartesian
 // coordinates from a cell name in Excel format, e.g. the cellIDString
 // "A1" returns 0, 0 and the "B3" return 1, 2.
-func getCoordsFromCellIDString(cellIDString string) (x, y int, error error) {
+func GetCoordsFromCellIDString(cellIDString string) (x, y int, error error) {
 	var letterPart string = strings.Map(letterOnlyMapF, cellIDString)
 	y, error = strconv.Atoi(strings.Map(intOnlyMapF, cellIDString))
 	if error != nil {
@@ -176,9 +176,9 @@ func getCoordsFromCellIDString(cellIDString string) (x, y int, error 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.
-func getCellIDStringFromCoords(x, y int) string {
+func GetCellIDStringFromCoords(x, y int) string {
 	letterPart := numericToLetters(x)
 	numericPart := y + 1
 	return fmt.Sprintf("%s%d", letterPart, numericPart)
@@ -191,15 +191,11 @@ func getCellIDStringFromCoords(x, y int) string {
 func getMaxMinFromDimensionRef(ref string) (minx, miny, maxx, maxy int, err error) {
 	var parts []string
 	parts = strings.Split(ref, ":")
-	minx, miny, err = getCoordsFromCellIDString(parts[0])
+	minx, miny, err = GetCoordsFromCellIDString(parts[0])
 	if err != nil {
 		return -1, -1, -1, -1, err
 	}
-	if len(parts) == 1 {
-		maxx, maxy = minx, miny
-		return
-	}
-	maxx, maxy, err = getCoordsFromCellIDString(parts[1])
+	maxx, maxy, err = GetCoordsFromCellIDString(parts[1])
 	if err != nil {
 		return -1, -1, -1, -1, err
 	}
@@ -220,7 +216,7 @@ func calculateMaxMinFromWorksheet(worksheet *xlsxWorksheet) (minx, miny, maxx, m
 	maxx = 0
 	for _, row := range worksheet.SheetData.Row {
 		for _, cell := range row.C {
-			x, y, err = getCoordsFromCellIDString(cell.R)
+			x, y, err = GetCoordsFromCellIDString(cell.R)
 			if err != nil {
 				return -1, -1, -1, -1, err
 			}
@@ -285,7 +281,7 @@ func makeRowFromRaw(rawrow xlsxRow, sheet *Sheet) *Row {
 
 	for _, rawcell := range rawrow.C {
 		if rawcell.R != "" {
-			x, _, error := getCoordsFromCellIDString(rawcell.R)
+			x, _, error := GetCoordsFromCellIDString(rawcell.R)
 			if error != nil {
 				panic(fmt.Sprintf("Invalid Cell Coord, %s\n", rawcell.R))
 			}
@@ -298,6 +294,8 @@ func makeRowFromRaw(rawrow xlsxRow, sheet *Sheet) *Row {
 	}
 	upper++
 
+	row.OutlineLevel = rawrow.OutlineLevel
+
 	row.Cells = make([]*Cell, upper)
 	for i := 0; i < upper; i++ {
 		cell = new(Cell)
@@ -327,7 +325,7 @@ func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string
 		return ""
 	}
 	if f.T == "shared" {
-		x, y, err := getCoordsFromCellIDString(rawcell.R)
+		x, y, err := GetCoordsFromCellIDString(rawcell.R)
 		if err != nil {
 			res = f.Content
 		} else {
@@ -340,16 +338,26 @@ func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string
 				dy := y - sharedFormula.y
 				orig := []byte(sharedFormula.formula)
 				var start, end int
+				var stringLiteral bool
 				for end = 0; end < len(orig); end++ {
 					c := orig[end]
-					if c >= 'A' && c <= 'Z' {
+
+					if c == '"' {
+						stringLiteral = !stringLiteral
+					}
+
+					if stringLiteral {
+						continue // Skip characters in quotes
+					}
+
+					if c >= 'A' && c <= 'Z' || c == '$' {
 						res += string(orig[start:end])
 						start = end
 						end++
 						foundNum := false
 						for ; end < len(orig); end++ {
 							idc := orig[end]
-							if idc >= '0' && idc <= '9' {
+							if idc >= '0' && idc <= '9' || idc == '$' {
 								foundNum = true
 							} else if idc >= 'A' && idc <= 'Z' {
 								if foundNum {
@@ -360,16 +368,14 @@ func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string
 							}
 						}
 						if foundNum {
-							fx, fy, _ := getCoordsFromCellIDString(string(orig[start:end]))
-							fx += dx
-							fy += dy
-							res += getCellIDStringFromCoords(fx, fy)
+							cellID := string(orig[start:end])
+							res += shiftCell(cellID, dx, dy)
 							start = end
 						}
 					}
 				}
 				if start < len(orig) {
-					res += string(orig[start:end])
+					res += string(orig[start:])
 				}
 			}
 		}
@@ -379,11 +385,60 @@ func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string
 	return strings.Trim(res, " \t\n\r")
 }
 
+// shiftCell returns the cell shifted according to dx and dy taking into consideration of absolute
+// references with dollar sign ($)
+func shiftCell(cellID string, dx, dy int) string {
+	fx, fy, _ := GetCoordsFromCellIDString(cellID)
+
+	// Is fixed column?
+	fixedCol := strings.Index(cellID, "$") == 0
+
+	// Is fixed row?
+	fixedRow := strings.LastIndex(cellID, "$") > 0
+
+	if !fixedCol {
+		// Shift column
+		fx += dx
+	}
+
+	if !fixedRow {
+		// Shift row
+		fy += dy
+	}
+
+	// New shifted cell
+	shiftedCellID := GetCellIDStringFromCoords(fx, fy)
+
+	if !fixedCol && !fixedRow {
+		return shiftedCellID
+	}
+
+	// There are absolute references, need to put the $ back into the formula.
+	letterPart := strings.Map(letterOnlyMapF, shiftedCellID)
+	numberPart := strings.Map(intOnlyMapF, shiftedCellID)
+
+	result := ""
+
+	if fixedCol {
+		result += "$"
+	}
+
+	result += letterPart
+
+	if fixedRow {
+		result += "$"
+	}
+
+	result += numberPart
+
+	return result
+}
+
 // fillCellData attempts to extract a valid value, usable in
 // CSV form from the raw cell value.  Note - this is not actually
 // general enough - we should support retaining tabs and newlines.
 func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]sharedFormula, cell *Cell) {
-	var data string = rawcell.V
+	var data = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
 		switch rawcell.T {
@@ -413,6 +468,23 @@ func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]shar
 				cell.cellType = CellTypeFormula
 			}
 		}
+	} else {
+		if rawcell.Is != nil {
+			fillCellDataFromInlineString(rawcell, cell)
+		}
+	}
+}
+
+// fillCellDataFromInlineString attempts to get inline string data and put it into a 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
+		}
 	}
 }
 
@@ -434,7 +506,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 		return nil, nil, 0, 0
 	}
 	reftable = file.referenceTable
-	if len(Worksheet.Dimension.Ref) > 0 {
+	if len(Worksheet.Dimension.Ref) > 0 && len(strings.Split(Worksheet.Dimension.Ref, ":")) == 2 {
 		minCol, minRow, maxCol, maxRow, err = getMaxMinFromDimensionRef(Worksheet.Dimension.Ref)
 	} else {
 		minCol, minRow, maxCol, maxRow, err = calculateMaxMinFromWorksheet(Worksheet)
@@ -464,10 +536,11 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 			// columns.
 			for i := rawcol.Min; i <= rawcol.Max && i <= colCount; i++ {
 				col := &Col{
-					Min:    rawcol.Min,
-					Max:    rawcol.Max,
-					Hidden: rawcol.Hidden,
-					Width:  rawcol.Width}
+					Min:          rawcol.Min,
+					Max:          rawcol.Max,
+					Hidden:       rawcol.Hidden,
+					Width:        rawcol.Width,
+					OutlineLevel: rawcol.OutlineLevel}
 				cols[i-1] = col
 				if file.styles != nil {
 					col.style = file.styles.getStyle(rawcol.Style)
@@ -502,6 +575,12 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 		}
 
 		row.Hidden = rawrow.Hidden
+		height, err := strconv.ParseFloat(rawrow.Ht, 64)
+		if err == nil {
+			row.Height = height
+		}
+		row.isCustom = rawrow.CustomHeight
+		row.OutlineLevel = rawrow.OutlineLevel
 
 		insertColIndex = minCol
 		for _, rawcell := range rawrow.C {
@@ -509,28 +588,36 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File, sheet *Sheet) ([]*R
 			if err != nil {
 				panic(err.Error())
 			}
-			x, _, _ := getCoordsFromCellIDString(rawcell.R)
+			x, _, _ := GetCoordsFromCellIDString(rawcell.R)
+
+			// K1000000: Prevent panic when the range specified in the spreadsheet
+			//           view exceeds the actual number of columns in the dataset.
 
 			// Some spreadsheets will omit blank cells
 			// from the data.
 			for x > insertColIndex {
 				// Put an empty Cell into the array
-				row.Cells[insertColIndex] = new(Cell)
+				if insertColIndex < len(row.Cells) {
+					row.Cells[insertColIndex] = new(Cell)
+				}
 				insertColIndex++
 			}
 			cellX := insertColIndex
-			cell := row.Cells[cellX]
-			cell.HMerge = h
-			cell.VMerge = v
-			fillCellData(rawcell, reftable, sharedFormulas, cell)
-			if file.styles != nil {
-				cell.style = file.styles.getStyle(rawcell.S)
-				cell.NumFmt = file.styles.getNumberFormat(rawcell.S)
+
+			if cellX < len(row.Cells) {
+				cell := row.Cells[cellX]
+				cell.HMerge = h
+				cell.VMerge = v
+				fillCellData(rawcell, reftable, sharedFormulas, cell)
+				if file.styles != nil {
+					cell.style = file.styles.getStyle(rawcell.S)
+					cell.NumFmt = file.styles.getNumberFormat(rawcell.S)
+				}
+				cell.date1904 = file.Date1904
+				// Cell is considered hidden if the row or the column of this cell is hidden
+				cell.Hidden = rawrow.Hidden || (len(cols) > cellX && cols[cellX].Hidden)
+				insertColIndex++
 			}
-			cell.date1904 = file.Date1904
-			// Cell is considered hidden if the row or the column of this cell is hidden
-			cell.Hidden = rawrow.Hidden || (len(cols) > cellX && cols[cellX].Hidden)
-			insertColIndex++
 		}
 		if len(rows) > insertRowIndex {
 			rows[insertRowIndex] = row
@@ -572,13 +659,15 @@ func readSheetViews(xSheetViews xlsxSheetViews) []SheetView {
 // into a Sheet struct.  This work can be done in parallel and so
 // readSheetsFromZipFile will spawn an instance of this function per
 // 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) {
+func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *File, sheetXMLMap map[string]string) (errRes error) {
 	result := &indexedSheet{Index: index, Sheet: nil, Error: nil}
 	defer func() {
 		if e := recover(); e != nil {
+
 			switch e.(type) {
 			case error:
 				result.Error = e.(error)
+				errRes = e.(error)
 			default:
 				result.Error = errors.New("unexpected error")
 			}
@@ -591,7 +680,7 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 	if error != nil {
 		result.Error = error
 		sc <- result
-		return
+		return error
 	}
 	sheet := new(Sheet)
 	sheet.File = fi
@@ -601,9 +690,12 @@ func readSheetFromFile(sc chan *indexedSheet, index int, rsheet xlsxSheet, fi *F
 
 	sheet.SheetFormat.DefaultColWidth = worksheet.SheetFormatPr.DefaultColWidth
 	sheet.SheetFormat.DefaultRowHeight = worksheet.SheetFormatPr.DefaultRowHeight
+	sheet.SheetFormat.OutlineLevelCol = worksheet.SheetFormatPr.OutlineLevelCol
+	sheet.SheetFormat.OutlineLevelRow = worksheet.SheetFormatPr.OutlineLevelRow
 
 	result.Sheet = sheet
 	sc <- result
+	return nil
 }
 
 // readSheetsFromZipFile is an internal helper function that loops
@@ -627,6 +719,10 @@ func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]strin
 	}
 	file.Date1904 = workbook.WorkbookPr.Date1904
 
+	for entryNum := range workbook.DefinedNames.DefinedName {
+		file.DefinedNames = append(file.DefinedNames, &workbook.DefinedNames.DefinedName[entryNum])
+	}
+
 	// Only try and read sheets that have corresponding files.
 	// Notably this excludes chartsheets don't right now
 	var workbookSheets []xlsxSheet
@@ -639,12 +735,14 @@ func readSheetsFromZipFile(f *zip.File, file *File, sheetXMLMap map[string]strin
 	sheetsByName := make(map[string]*Sheet, sheetCount)
 	sheets := make([]*Sheet, sheetCount)
 	sheetChan := make(chan *indexedSheet, sheetCount)
-	defer close(sheetChan)
 
 	go func() {
+		defer close(sheetChan)
 		err = nil
 		for i, rawsheet := range workbookSheets {
-			readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap)
+			if err := readSheetFromFile(sheetChan, i, rawsheet, file, sheetXMLMap); err != nil {
+				return
+			}
 		}
 	}()
 

+ 168 - 18
common/src/github.com/tealeg/xlsx/lib_test.go

@@ -28,6 +28,29 @@ func (l *LibSuite) TestReadZipReaderWithFileWithNoWorksheets(c *C) {
 	c.Assert(err.Error(), Equals, "Input xlsx contains no worksheets.")
 }
 
+// Attempt to read data from a file with inlined string sheet data.
+func (l *LibSuite) TestReadWithInlineStrings(c *C) {
+	var xlsxFile *File
+	var err error
+
+	xlsxFile, err = OpenFile("./testdocs/inlineStrings.xlsx")
+	c.Assert(err, IsNil)
+	sheet := xlsxFile.Sheets[0]
+	r1 := sheet.Rows[0]
+	c1 := r1.Cells[1]
+
+	val, err := c1.FormattedValue()
+	if err != nil {
+		c.Error(err)
+		return
+	}
+	if val == "" {
+		c.Error("Expected a string value")
+		return
+	}
+	c.Assert(val, Equals, "HL Retail - North America - Activity by Day - MTD")
+}
+
 // which they are contained from the XLSX file, even when the
 // worksheet files have arbitrary, non-numeric names.
 func (l *LibSuite) TestReadWorkbookRelationsFromZipFileWithFunnyNames(c *C) {
@@ -39,7 +62,11 @@ func (l *LibSuite) TestReadWorkbookRelationsFromZipFileWithFunnyNames(c *C) {
 	bob := xlsxFile.Sheet["Bob"]
 	row1 := bob.Rows[0]
 	cell1 := row1.Cells[0]
-	c.Assert(cell1.String(), Equals, "I am Bob")
+	if val, err := cell1.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "I am Bob")
+	}
 }
 
 // We can marshal WorkBookRels to an xml file
@@ -156,15 +183,15 @@ func (l *LibSuite) TestGetCoordsFromCellIDString(c *C) {
 	var cellIDString string = "A3"
 	var x, y int
 	var err error
-	x, y, err = getCoordsFromCellIDString(cellIDString)
+	x, y, err = GetCoordsFromCellIDString(cellIDString)
 	c.Assert(err, IsNil)
 	c.Assert(x, Equals, 0)
 	c.Assert(y, Equals, 2)
 }
 
 func (l *LibSuite) TestGetCellIDStringFromCoords(c *C) {
-	c.Assert(getCellIDStringFromCoords(0, 0), Equals, "A1")
-	c.Assert(getCellIDStringFromCoords(2, 2), Equals, "C3")
+	c.Assert(GetCellIDStringFromCoords(0, 0), Equals, "A1")
+	c.Assert(GetCellIDStringFromCoords(2, 2), Equals, "C3")
 }
 
 func (l *LibSuite) TestGetMaxMinFromDimensionRef(c *C) {
@@ -289,7 +316,7 @@ func (l *LibSuite) TestReadRowsFromSheet(c *C) {
   </sheetViews>
   <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
   <sheetData>
-    <row r="1" spans="1:2">
+    <row r="1" spans="1:2" ht="123.45" customHeight="1">
       <c r="A1" t="s">
         <v>0</v>
       </c>
@@ -327,6 +354,8 @@ func (l *LibSuite) TestReadRowsFromSheet(c *C) {
 	row := rows[0]
 	c.Assert(row.Sheet, Equals, sheet)
 	c.Assert(len(row.Cells), Equals, 2)
+	c.Assert(row.Height, Equals, 123.45)
+	c.Assert(row.isCustom, Equals, true)
 	cell1 := row.Cells[0]
 	c.Assert(cell1.Value, Equals, "Foo")
 	cell2 := row.Cells[1]
@@ -528,9 +557,17 @@ func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {
 	c.Assert(len(rows[1].Cells), Equals, 0)
 	c.Assert(len(rows[2].Cells), Equals, 0)
 	c.Assert(len(rows[3].Cells), Equals, 1)
-	c.Assert(rows[3].Cells[0].String(), Equals, "ABC")
+	if val, err := rows[3].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "ABC")
+	}
 	c.Assert(len(rows[4].Cells), Equals, 1)
-	c.Assert(rows[4].Cells[0].String(), Equals, "DEF")
+	if val, err := rows[4].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "DEF")
+	}
 }
 
 func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyCols(c *C) {
@@ -583,15 +620,47 @@ func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyCols(c *C) {
 	c.Assert(maxCols, Equals, 4)
 
 	c.Assert(len(rows[0].Cells), Equals, 4)
-	c.Assert(rows[0].Cells[0].String(), Equals, "")
-	c.Assert(rows[0].Cells[1].String(), Equals, "")
-	c.Assert(rows[0].Cells[2].String(), Equals, "ABC")
-	c.Assert(rows[0].Cells[3].String(), Equals, "DEF")
+	if val, err := rows[0].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := rows[0].Cells[1].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := rows[0].Cells[2].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "ABC")
+	}
+	if val, err := rows[0].Cells[3].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "DEF")
+	}
 	c.Assert(len(rows[1].Cells), Equals, 4)
-	c.Assert(rows[1].Cells[0].String(), Equals, "")
-	c.Assert(rows[1].Cells[1].String(), Equals, "")
-	c.Assert(rows[1].Cells[2].String(), Equals, "ABC")
-	c.Assert(rows[1].Cells[3].String(), Equals, "DEF")
+	if val, err := rows[1].Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := rows[1].Cells[1].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "")
+	}
+	if val, err := rows[1].Cells[2].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "ABC")
+	}
+	if val, err := rows[1].Cells[3].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "DEF")
+	}
 
 	c.Assert(len(cols), Equals, 4)
 	c.Assert(cols[0].Width, Equals, 0.0)
@@ -923,7 +992,11 @@ func (l *LibSuite) TestReadRowsFromSheetWithMultipleTypes(c *C) {
 
 	cell1 := row.Cells[0]
 	c.Assert(cell1.Type(), Equals, CellTypeString)
-	c.Assert(cell1.String(), Equals, "Hello World")
+	if val, err := cell1.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Hello World")
+	}
 
 	cell2 := row.Cells[1]
 	c.Assert(cell2.Type(), Equals, CellTypeNumeric)
@@ -992,12 +1065,20 @@ func (l *LibSuite) TestReadRowsFromSheetWithHiddenColumn(c *C) {
 
 	cell1 := row.Cells[0]
 	c.Assert(cell1.Type(), Equals, CellTypeString)
-	c.Assert(cell1.String(), Equals, "This is a test.")
+	if val, err := cell1.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "This is a test.")
+	}
 	c.Assert(cell1.Hidden, Equals, false)
 
 	cell2 := row.Cells[1]
 	c.Assert(cell2.Type(), Equals, CellTypeString)
-	c.Assert(cell2.String(), Equals, "This should be invisible.")
+	if val, err := cell2.FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "This should be invisible.")
+	}
 	c.Assert(cell2.Hidden, Equals, true)
 }
 
@@ -1120,6 +1201,75 @@ func (l *LibSuite) TestSharedFormulas(c *C) {
 	c.Assert(row.Cells[2].Formula(), Equals, "2*C1")
 }
 
+// Test shared formulas that have absolute references ($) in them
+func (l *LibSuite) TestSharedFormulasWithAbsoluteReferences(c *C) {
+	formulas := []string{
+		"A1",
+		"$A1",
+		"A$1",
+		"$A$1",
+		"A1+B1",
+		"$A1+B1",
+		"$A$1+B1",
+		"A1+$B1",
+		"A1+B$1",
+		"A1+$B$1",
+		"$A$1+$B$1",
+		`IF(C23>=E$12,"Q4",IF(C23>=$D$12,"Q3",IF(C23>=C$12,"Q2","Q1")))`,
+		`SUM(D44:H44)*IM_A_DEFINED_NAME`,
+		`IM_A_DEFINED_NAME+SUM(D44:H44)*IM_A_DEFINED_NAME_ALSO`,
+		`SUM(D44:H44)*IM_A_DEFINED_NAME+A1`,
+		"AA1",
+		"$AA1",
+		"AA$1",
+		"$AA$1",
+	}
+
+	expected := []string{
+		"B2",
+		"$A2",
+		"B$1",
+		"$A$1",
+		"B2+C2",
+		"$A2+C2",
+		"$A$1+C2",
+		"B2+$B2",
+		"B2+C$1",
+		"B2+$B$1",
+		"$A$1+$B$1",
+		`IF(D24>=F$12,"Q4",IF(D24>=$D$12,"Q3",IF(D24>=D$12,"Q2","Q1")))`,
+		`SUM(E45:I45)*IM_A_DEFINED_NAME`,
+		`IM_A_DEFINED_NAME+SUM(E45:I45)*IM_A_DEFINED_NAME_ALSO`,
+		`SUM(E45:I45)*IM_A_DEFINED_NAME+B2`,
+		"AB2",
+		"$AA2",
+		"AB$1",
+		"$AA$1",
+	}
+
+	anchorCell := "C4"
+
+	sharedFormulas := map[int]sharedFormula{}
+	x, y, _ := GetCoordsFromCellIDString(anchorCell)
+	for i, formula := range formulas {
+		res := formula
+		sharedFormulas[i] = sharedFormula{x, y, res}
+	}
+
+	for i, formula := range formulas {
+		testCell := xlsxC{
+			R: "D5",
+			F: &xlsxF{
+				Content: formula,
+				T:       "shared",
+				Si:      i,
+			},
+		}
+
+		c.Assert(formulaForCell(testCell, sharedFormulas), Equals, expected[i])
+	}
+}
+
 // Avoid panic when cell.F.T is "e" (for error)
 func (l *LibSuite) TestFormulaForCellPanic(c *C) {
 	cell := xlsxC{R: "A1"}

+ 132 - 0
common/src/github.com/tealeg/xlsx/read.go

@@ -0,0 +1,132 @@
+package xlsx
+
+import (
+	"errors"
+	"reflect"
+	"strconv"
+	"time"
+)
+
+var (
+	errNilInterface     = errors.New("nil pointer is not a valid argument")
+	errNotStructPointer = errors.New("argument must be a pointer to struct")
+	errInvalidTag       = errors.New(`invalid tag: must have the format xlsx:idx`)
+)
+
+//XLSXUnmarshaler is the interface implemented for types that can unmarshal a Row
+//as a representation of themselves.
+type XLSXUnmarshaler interface {
+	Unmarshal(*Row) error
+}
+
+//ReadStruct reads a struct from r to ptr. Accepts a ptr
+//to struct. This code expects a tag xlsx:"N", where N is the index
+//of the cell to be used. Basic types like int,string,float64 and bool
+//are supported
+func (r *Row) ReadStruct(ptr interface{}) error {
+	if ptr == nil {
+		return errNilInterface
+	}
+	//check if the type implements XLSXUnmarshaler. If so,
+	//just let it do the work.
+	unmarshaller, ok := ptr.(XLSXUnmarshaler)
+	if ok {
+		return unmarshaller.Unmarshal(r)
+	}
+	v := reflect.ValueOf(ptr)
+	if v.Kind() != reflect.Ptr {
+		return errNotStructPointer
+	}
+	v = v.Elem()
+	if v.Kind() != reflect.Struct {
+		return errNotStructPointer
+	}
+	n := v.NumField()
+	for i := 0; i < n; i++ {
+		field := v.Type().Field(i)
+		idx := field.Tag.Get("xlsx")
+		//do a recursive check for the field if it is a struct or a pointer
+		//even if it doesn't have a tag
+		//ignore if it has a - or empty tag
+		isTime := false
+		switch {
+		case idx == "-":
+			continue
+		case field.Type.Kind() == reflect.Ptr || field.Type.Kind() == reflect.Struct:
+			var structPtr interface{}
+			if !v.Field(i).CanSet() {
+				continue
+			}
+			if field.Type.Kind() == reflect.Struct {
+				structPtr = v.Field(i).Addr().Interface()
+			} else {
+				structPtr = v.Field(i).Interface()
+			}
+			//check if the container is a time.Time
+			_, isTime = structPtr.(*time.Time)
+			if isTime {
+				break
+			}
+			err := r.ReadStruct(structPtr)
+			if err != nil {
+				return err
+			}
+			continue
+		case len(idx) == 0:
+			continue
+		}
+		pos, err := strconv.Atoi(idx)
+		if err != nil {
+			return errInvalidTag
+		}
+
+		//check if desired position is not out of bounds
+		if pos > len(r.Cells)-1 {
+			continue
+		}
+		cell := r.Cells[pos]
+		fieldV := v.Field(i)
+		//continue if the field is not settable
+		if !fieldV.CanSet() {
+			continue
+		}
+		if isTime {
+			t, err := cell.GetTime(false)
+			if err != nil {
+				return err
+			}
+			if field.Type.Kind() == reflect.Ptr {
+				fieldV.Set(reflect.ValueOf(&t))
+			} else {
+				fieldV.Set(reflect.ValueOf(t))
+			}
+			continue
+		}
+		switch field.Type.Kind() {
+		case reflect.String:
+			value, err := cell.FormattedValue()
+			if err != nil {
+				return err
+			}
+			fieldV.SetString(value)
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+			value, err := cell.Int64()
+			if err != nil {
+				return err
+			}
+			fieldV.SetInt(value)
+		case reflect.Float64:
+			value, err := cell.Float()
+			if err != nil {
+				return err
+			}
+			fieldV.SetFloat(value)
+		case reflect.Bool:
+			value := cell.Bool()
+			fieldV.SetBool(value)
+		}
+	}
+	value := v.Interface()
+	ptr = &value
+	return nil
+}

+ 243 - 0
common/src/github.com/tealeg/xlsx/read_test.go

@@ -0,0 +1,243 @@
+package xlsx
+
+import (
+	"errors"
+	"fmt"
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+type ReadSuite struct{}
+
+var _ = Suite(&ReadSuite{})
+
+var (
+	errorNoPair         = errors.New("Integer to be unmarshaled is not a pair")
+	errorNotEnoughCells = errors.New("Row has not enough cells")
+)
+
+type pairUnmarshaler int
+
+func (i *pairUnmarshaler) Unmarshal(row *Row) error {
+	if len(row.Cells) == 0 {
+		return errorNotEnoughCells
+	}
+	cellInt, err := row.Cells[0].Int()
+	if err != nil {
+		return err
+	}
+	if cellInt%2 != 0 {
+		return errorNoPair
+	}
+	*i = pairUnmarshaler(cellInt)
+	return nil
+}
+
+type structUnmarshaler struct {
+	private bool
+	custom  string
+	normal  int
+}
+
+func (s *structUnmarshaler) Unmarshal(r *Row) error {
+	if len(r.Cells) < 3 {
+		return errorNotEnoughCells
+	}
+	s.private = r.Cells[0].Bool()
+	var err error
+	s.normal, err = r.Cells[2].Int()
+	if err != nil {
+		return err
+	}
+	currency, err := r.Cells[1].FormattedValue()
+	if err != nil {
+		return err
+	}
+	s.custom = fmt.Sprintf("$ %s", currency)
+	return nil
+}
+
+func (r *RowSuite) TestInterface(c *C) {
+	var p pairUnmarshaler
+	var s structUnmarshaler
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestReadTime")
+	row := sheet.AddRow()
+	values := []interface{}{1, "500", true}
+	row.WriteSlice(&values, -1)
+	errPair := row.ReadStruct(&p)
+	err := row.ReadStruct(&s)
+	c.Assert(errPair, Equals, errorNoPair)
+	c.Assert(err, Equals, nil)
+	var empty pairUnmarshaler
+	c.Assert(p, Equals, empty)
+	c.Assert(s.normal, Equals, 1)
+	c.Assert(s.private, Equals, true)
+	c.Assert(s.custom, Equals, "$ 500")
+}
+
+func (r *RowSuite) TestTime(c *C) {
+	type Timer struct {
+		Initial time.Time `xlsx:"0"`
+		Final   time.Time `xlsx:"1"`
+	}
+	initial := time.Date(1990, 12, 30, 10, 30, 30, 0, time.UTC)
+	t := Timer{
+		Initial: initial,
+		Final:   initial.Add(time.Hour * 24),
+	}
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestReadTime")
+	row := sheet.AddRow()
+	row.AddCell().SetDateTime(t.Initial)
+	ctime2 := row.AddCell()
+	ctime2.SetDate(t.Final)
+	t2 := Timer{}
+	err := row.ReadStruct(&t2)
+	if err != nil {
+		c.Error(err)
+		c.FailNow()
+	}
+	//removing ns precition
+	t2.Initial = t2.Initial.Add(time.Duration(-1 * t2.Initial.Nanosecond()))
+	t2.Final = t2.Final.Add(time.Duration(-1 * t2.Final.Nanosecond()))
+	c.Assert(t2.Initial, Equals, t.Initial)
+	c.Assert(t2.Final, Equals, t.Final)
+}
+
+func (r *RowSuite) TestEmbedStruct(c *C) {
+	type Embed struct {
+		privateVal bool   `xlsx:"0"`
+		IgnoredVal int    `xlsx:"-"`
+		VisibleVal string `xlsx:"2"`
+	}
+	type structTest struct {
+		Embed
+		FinalVal string `xlsx:"3"`
+	}
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestRead")
+	row := sheet.AddRow()
+	v := structTest{
+		Embed: Embed{
+			privateVal: true,
+			IgnoredVal: 10,
+			VisibleVal: "--This is a test value--",
+		},
+		FinalVal: "--end of struct",
+	}
+	values := []string{
+		fmt.Sprint(v.privateVal),
+		fmt.Sprint(v.IgnoredVal),
+		fmt.Sprint(v.VisibleVal),
+		fmt.Sprint(v.FinalVal),
+	}
+	row.WriteSlice(&values, -1)
+	for _, cell := range row.Cells {
+		v := cell.String()
+		c.Log(v)
+	}
+	read := new(structTest)
+	err := row.ReadStruct(read)
+	if err != nil {
+		c.Error(err)
+		c.FailNow()
+	}
+	c.Assert(read.privateVal, Equals, false)
+	c.Assert(read.VisibleVal, Equals, v.VisibleVal)
+	c.Assert(read.IgnoredVal, Equals, 0)
+	c.Assert(read.FinalVal, Equals, v.FinalVal)
+}
+
+func (r *RowSuite) TestReadStructPrivateFields(c *C) {
+	type nested struct {
+		IgnoredVal int    `xlsx:"-"`
+		VisibleVal string `xlsx:"6"`
+		privateVal bool   `xlsx:"7"`
+	}
+	type structTest struct {
+		IntVal     int16   `xlsx:"0"`
+		StringVal  string  `xlsx:"1"`
+		FloatVal   float64 `xlsx:"2"`
+		IgnoredVal int     `xlsx:"-"`
+		BoolVal    bool    `xlsx:"4"`
+		Nested     nested
+	}
+	val := structTest{
+		IntVal:     16,
+		StringVal:  "heyheyhey :)!",
+		FloatVal:   3.14159216,
+		IgnoredVal: 7,
+		BoolVal:    true,
+		Nested: nested{
+			privateVal: true,
+			IgnoredVal: 90,
+			VisibleVal: "Hello",
+		},
+	}
+	writtenValues := []string{
+		fmt.Sprint(val.IntVal), val.StringVal, fmt.Sprint(val.FloatVal),
+		fmt.Sprint(val.IgnoredVal), fmt.Sprint(val.BoolVal),
+		fmt.Sprint(val.Nested.IgnoredVal), val.Nested.VisibleVal,
+		fmt.Sprint(val.Nested.privateVal),
+	}
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestRead")
+	row := sheet.AddRow()
+	row.WriteSlice(&writtenValues, -1)
+	for i, cell := range row.Cells {
+		str := cell.String()
+		c.Log(i, " ", str)
+	}
+	readStruct := structTest{}
+	err := row.ReadStruct(&readStruct)
+	if err != nil {
+		c.Error(err)
+		c.FailNow()
+	}
+	c.Assert(err, Equals, nil)
+	c.Assert(readStruct.IntVal, Equals, val.IntVal)
+	c.Assert(readStruct.StringVal, Equals, val.StringVal)
+	c.Assert(readStruct.IgnoredVal, Equals, 0)
+	c.Assert(readStruct.FloatVal, Equals, val.FloatVal)
+	c.Assert(readStruct.BoolVal, Equals, val.BoolVal)
+	c.Assert(readStruct.Nested.IgnoredVal, Equals, 0)
+	c.Assert(readStruct.Nested.VisibleVal, Equals, "Hello")
+	c.Assert(readStruct.Nested.privateVal, Equals, false)
+}
+
+func (r *RowSuite) TestReadStruct(c *C) {
+	type structTest struct {
+		IntVal     int8    `xlsx:"0"`
+		StringVal  string  `xlsx:"1"`
+		FloatVal   float64 `xlsx:"2"`
+		IgnoredVal int     `xlsx:"-"`
+		BoolVal    bool    `xlsx:"4"`
+	}
+	structVal := structTest{
+		IntVal:     10,
+		StringVal:  "heyheyhey :)!",
+		FloatVal:   3.14159216,
+		IgnoredVal: 7,
+		BoolVal:    true,
+	}
+	f := NewFile()
+	sheet, _ := f.AddSheet("TestRead")
+	row := sheet.AddRow()
+	row.WriteStruct(&structVal, -1)
+	for i, cell := range row.Cells {
+		str := cell.String()
+		c.Log(i, " ", str)
+	}
+	readStruct := &structTest{}
+	err := row.ReadStruct(readStruct)
+	c.Log(readStruct)
+	c.Log(structVal)
+	c.Assert(err, Equals, nil)
+	c.Assert(readStruct.IntVal, Equals, structVal.IntVal)
+	c.Assert(readStruct.StringVal, Equals, structVal.StringVal)
+	c.Assert(readStruct.IgnoredVal, Equals, 0)
+	c.Assert(readStruct.FloatVal, Equals, structVal.FloatVal)
+	c.Assert(readStruct.BoolVal, Equals, structVal.BoolVal)
+}

+ 11 - 5
common/src/github.com/tealeg/xlsx/row.go

@@ -1,11 +1,17 @@
 package xlsx
 
 type Row struct {
-	Cells    []*Cell
-	Hidden   bool
-	Sheet    *Sheet
-	Height   float64
-	isCustom bool
+	Cells        []*Cell
+	Hidden       bool
+	Sheet        *Sheet
+	Height       float64
+	OutlineLevel uint8
+	isCustom     bool
+}
+
+func (r *Row) SetHeight(ht float64) {
+	r.Height = ht
+	r.isCustom = true
 }
 
 func (r *Row) SetHeightCM(ht float64) {

+ 1 - 1
common/src/github.com/tealeg/xlsx/row_test.go

@@ -12,7 +12,7 @@ var _ = Suite(&RowSuite{})
 func (r *RowSuite) TestAddCell(c *C) {
 	var f *File
 	f = NewFile()
-	sheet := f.AddSheet("MySheet")
+	sheet, _ := f.AddSheet("MySheet")
 	row := sheet.AddRow()
 	cell := row.AddCell()
 	c.Assert(cell, NotNil)

+ 136 - 19
common/src/github.com/tealeg/xlsx/sheet.go

@@ -18,6 +18,7 @@ type Sheet struct {
 	Selected    bool
 	SheetViews  []SheetView
 	SheetFormat SheetFormat
+	AutoFilter  *AutoFilter
 }
 
 type SheetView struct {
@@ -35,6 +36,13 @@ type Pane struct {
 type SheetFormat struct {
 	DefaultColWidth  float64
 	DefaultRowHeight float64
+	OutlineLevelCol  uint8
+	OutlineLevelRow  uint8
+}
+
+type AutoFilter struct {
+	TopLeftCell     string
+	BottomRightCell string
 }
 
 // Add a new Row to a Sheet
@@ -78,10 +86,17 @@ func (s *Sheet) Col(idx int) *Col {
 // containing the data from the field "A1" on the spreadsheet.
 func (sh *Sheet) Cell(row, col int) *Cell {
 
-	if len(sh.Rows) > row && sh.Rows[row] != nil && len(sh.Rows[row].Cells) > col {
-		return sh.Rows[row].Cells[col]
+	// If the user requests a row beyond what we have, then extend.
+	for len(sh.Rows) <= row {
+		sh.AddRow()
+	}
+
+	r := sh.Rows[row]
+	for len(r.Cells) <= col {
+		r.AddCell()
 	}
-	return new(Cell)
+
+	return r.Cells[col]
 }
 
 //Set the width of a single column or multiple columns.
@@ -103,16 +118,98 @@ func (s *Sheet) SetColWidth(startcol, endcol int, width float64) error {
 	return nil
 }
 
+// When merging cells, the cell may be the 'original' or the 'covered'.
+// First, figure out which cells are merge starting points. Then create
+// the necessary cells underlying the merge area.
+// Then go through all the underlying cells and apply the appropriate
+// border, based on the original cell.
+func (s *Sheet) handleMerged() {
+	merged := make(map[string]*Cell)
+
+	for r, row := range s.Rows {
+		for c, cell := range row.Cells {
+			if cell.HMerge > 0 || cell.VMerge > 0 {
+				coord := fmt.Sprintf("%s%d", numericToLetters(c), r+1)
+				merged[coord] = cell
+			}
+		}
+	}
+
+	// This loop iterates over all cells that should be merged and applies the correct
+	// borders to them depending on their position. If any cells required by the merge
+	// are missing, they will be allocated by s.Cell().
+	for key, cell := range merged {
+		mainstyle := cell.GetStyle()
+
+		top := mainstyle.Border.Top
+		left := mainstyle.Border.Left
+		right := mainstyle.Border.Right
+		bottom := mainstyle.Border.Bottom
+
+		// When merging cells, the upper left cell does not maintain
+		// the original borders
+		mainstyle.Border.Top = "none"
+		mainstyle.Border.Left = "none"
+		mainstyle.Border.Right = "none"
+		mainstyle.Border.Bottom = "none"
+
+		maincol, mainrow, _ := GetCoordsFromCellIDString(key)
+		for rownum := 0; rownum <= cell.VMerge; rownum++ {
+			for colnum := 0; colnum <= cell.HMerge; colnum++ {
+				tmpcell := s.Cell(mainrow+rownum, maincol+colnum)
+				style := tmpcell.GetStyle()
+				style.ApplyBorder = true
+
+				if rownum == 0 {
+					style.Border.Top = top
+				}
+
+				if rownum == (cell.VMerge) {
+					style.Border.Bottom = bottom
+				}
+
+				if colnum == 0 {
+					style.Border.Left = left
+				}
+
+				if colnum == (cell.HMerge) {
+					style.Border.Right = right
+				}
+			}
+		}
+	}
+}
+
 // Dump sheet to its XML representation, intended for internal use only
 func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxWorksheet {
 	worksheet := newXlsxWorksheet()
 	xSheet := xlsxSheetData{}
 	maxRow := 0
 	maxCell := 0
+	var maxLevelCol, maxLevelRow uint8
+
+	// Scan through the sheet and see if there are any merged cells. If there
+	// are, we may need to extend the size of the sheet. There needs to be
+	// phantom cells underlying the area covered by the merged cell
+	s.handleMerged()
+
+	for index, sheetView := range s.SheetViews {
+		if sheetView.Pane != nil {
+			worksheet.SheetViews.SheetView[index].Pane = &xlsxPane{
+				XSplit:      sheetView.Pane.XSplit,
+				YSplit:      sheetView.Pane.YSplit,
+				TopLeftCell: sheetView.Pane.TopLeftCell,
+				ActivePane:  sheetView.Pane.ActivePane,
+				State:       sheetView.Pane.State,
+			}
+
+		}
+	}
 
 	if s.Selected {
 		worksheet.SheetViews.SheetView[0].TabSelected = true
 	}
+
 	if s.SheetFormat.DefaultRowHeight != 0 {
 		worksheet.SheetFormatPr.DefaultRowHeight = s.SheetFormat.DefaultRowHeight
 	}
@@ -136,21 +233,28 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		}
 		colsXfIdList[c] = XfId
 
-		var customWidth int
+		var customWidth bool
 		if col.Width == 0 {
 			col.Width = ColWidth
+			customWidth = false
+
 		} else {
-			customWidth = 1
+			customWidth = true
 		}
 		worksheet.Cols.Col = append(worksheet.Cols.Col,
 			xlsxCol{Min: col.Min,
-				Max:         col.Max,
-				Hidden:      col.Hidden,
-				Width:       col.Width,
-				CustomWidth: customWidth,
-				Collapsed:   col.Collapsed,
-				Style:       XfId,
+				Max:          col.Max,
+				Hidden:       col.Hidden,
+				Width:        col.Width,
+				CustomWidth:  customWidth,
+				Collapsed:    col.Collapsed,
+				OutlineLevel: col.OutlineLevel,
+				Style:        XfId,
 			})
+
+		if col.OutlineLevel > maxLevelCol {
+			maxLevelCol = col.OutlineLevel
+		}
 	}
 
 	for r, row := range s.Rows {
@@ -163,6 +267,10 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 			xRow.CustomHeight = true
 			xRow.Ht = fmt.Sprintf("%g", row.Height)
 		}
+		xRow.OutlineLevel = row.OutlineLevel
+		if row.OutlineLevel > maxLevelRow {
+			maxLevelRow = row.OutlineLevel
+		}
 		for c, cell := range row.Cells {
 			XfId := colsXfIdList[c]
 
@@ -211,6 +319,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xC.V = cell.Value
 				xC.S = XfId
 			}
+
 			xRow.C = append(xRow.C, xC)
 
 			if cell.HMerge > 0 || cell.VMerge > 0 {
@@ -230,10 +339,21 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 		xSheet.Row = append(xSheet.Row, xRow)
 	}
 
+	// Update sheet format with the freshly determined max levels
+	s.SheetFormat.OutlineLevelCol = maxLevelCol
+	s.SheetFormat.OutlineLevelRow = maxLevelRow
+	// .. and then also apply this to the xml worksheet
+	worksheet.SheetFormatPr.OutlineLevelCol = s.SheetFormat.OutlineLevelCol
+	worksheet.SheetFormatPr.OutlineLevelRow = s.SheetFormat.OutlineLevelRow
+
 	if worksheet.MergeCells != nil {
 		worksheet.MergeCells.Count = len(worksheet.MergeCells.Cells)
 	}
 
+	if s.AutoFilter != nil {
+		worksheet.AutoFilter = &xlsxAutoFilter{Ref: fmt.Sprintf("%v:%v", s.AutoFilter.TopLeftCell, s.AutoFilter.BottomRightCell)}
+	}
+
 	worksheet.SheetData = xSheet
 	dimension := xlsxDimension{}
 	dimension.Ref = fmt.Sprintf("A1:%s%d",
@@ -246,7 +366,7 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 }
 
 func handleStyleForXLSX(style *Style, NumFmtId int, styles *xlsxStyleSheet) (XfId int) {
-	xFont, xFill, xBorder, xCellStyleXf, xCellXf := style.makeXLSXStyleElements()
+	xFont, xFill, xBorder, xCellXf := style.makeXLSXStyleElements()
 	fontId := styles.addFont(xFont)
 	fillId := styles.addFill(xFill)
 
@@ -256,10 +376,6 @@ func handleStyleForXLSX(style *Style, NumFmtId int, styles *xlsxStyleSheet) (XfI
 	styles.addFill(greyfill)
 
 	borderId := styles.addBorder(xBorder)
-	xCellStyleXf.FontId = fontId
-	xCellStyleXf.FillId = fillId
-	xCellStyleXf.BorderId = borderId
-	xCellStyleXf.NumFmtId = builtInNumFmtIndex_GENERAL
 	xCellXf.FontId = fontId
 	xCellXf.FillId = fillId
 	xCellXf.BorderId = borderId
@@ -269,12 +385,13 @@ func handleStyleForXLSX(style *Style, NumFmtId int, styles *xlsxStyleSheet) (XfI
 		xCellXf.ApplyNumberFormat = true
 	}
 
-	xCellStyleXf.Alignment.Horizontal = style.Alignment.Horizontal
-	xCellStyleXf.Alignment.Vertical = style.Alignment.Vertical
 	xCellXf.Alignment.Horizontal = style.Alignment.Horizontal
+	xCellXf.Alignment.Indent = style.Alignment.Indent
+	xCellXf.Alignment.ShrinkToFit = style.Alignment.ShrinkToFit
+	xCellXf.Alignment.TextRotation = style.Alignment.TextRotation
 	xCellXf.Alignment.Vertical = style.Alignment.Vertical
+	xCellXf.Alignment.WrapText = style.Alignment.WrapText
 
-	styles.addCellStyleXf(xCellStyleXf)
 	XfId = styles.addCellXf(xCellXf)
 	return
 }

Plik diff jest za duży
+ 42 - 37
common/src/github.com/tealeg/xlsx/sheet_test.go


+ 41 - 38
common/src/github.com/tealeg/xlsx/style.go

@@ -5,31 +5,32 @@ import "strconv"
 // Style is a high level structure intended to provide user access to
 // the contents of Style within an XLSX file.
 type Style struct {
-	Border         Border
-	Fill           Fill
-	Font           Font
-	ApplyBorder    bool
-	ApplyFill      bool
-	ApplyFont      bool
-	ApplyAlignment bool
-	Alignment      Alignment
+	Border          Border
+	Fill            Fill
+	Font            Font
+	ApplyBorder     bool
+	ApplyFill       bool
+	ApplyFont       bool
+	ApplyAlignment  bool
+	Alignment       Alignment
+	NamedStyleIndex *int
 }
 
 // Return a new Style structure initialised with the default values.
 func NewStyle() *Style {
 	return &Style{
-		Font:   *DefaultFont(),
-		Border: *DefaultBorder(),
-		Fill:   *DefaultFill(),
+		Alignment: *DefaultAlignment(),
+		Border:    *DefaultBorder(),
+		Fill:      *DefaultFill(),
+		Font:      *DefaultFont(),
 	}
 }
 
 // Generate the underlying XLSX style elements that correspond to the Style.
-func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBorder xlsxBorder, xCellStyleXf xlsxXf, xCellXf xlsxXf) {
+func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBorder xlsxBorder, xCellXf xlsxXf) {
 	xFont = xlsxFont{}
 	xFill = xlsxFill{}
 	xBorder = xlsxBorder{}
-	xCellStyleXf = xlsxXf{}
 	xCellXf = xlsxXf{}
 	xFont.Sz.Val = strconv.Itoa(style.Font.Size)
 	xFont.Name.Val = style.Font.Name
@@ -77,13 +78,9 @@ func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBo
 	xCellXf.ApplyFill = style.ApplyFill
 	xCellXf.ApplyFont = style.ApplyFont
 	xCellXf.ApplyAlignment = style.ApplyAlignment
-	xCellStyleXf.ApplyBorder = style.ApplyBorder
-	xCellStyleXf.ApplyFill = style.ApplyFill
-	xCellStyleXf.ApplyFont = style.ApplyFont
-	xCellStyleXf.ApplyAlignment = style.ApplyAlignment
-	xCellStyleXf.NumFmtId = 0
-
-	xCellStyleXf.Alignment = xlsxAlignment{Horizontal: style.Alignment.Horizontal, Vertical: style.Alignment.Vertical}
+	if style.NamedStyleIndex != nil {
+		xCellXf.XfId = style.NamedStyleIndex
+	}
 	return
 }
 
@@ -107,14 +104,10 @@ type Border struct {
 
 func NewBorder(left, right, top, bottom string) *Border {
 	return &Border{
-		Left:        left,
-		LeftColor:   "",
-		Right:       right,
-		RightColor:  "",
-		Top:         top,
-		TopColor:    "",
-		Bottom:      bottom,
-		BottomColor: "",
+		Left:   left,
+		Right:  right,
+		Top:    top,
+		Bottom: bottom,
 	}
 }
 
@@ -127,7 +120,11 @@ type Fill struct {
 }
 
 func NewFill(patternType, fgColor, bgColor string) *Fill {
-	return &Fill{PatternType: patternType, FgColor: fgColor, BgColor: bgColor}
+	return &Fill{
+		PatternType: patternType,
+		FgColor:     fgColor,
+		BgColor:     bgColor,
+	}
 }
 
 type Font struct {
@@ -146,17 +143,16 @@ func NewFont(size int, name string) *Font {
 }
 
 type Alignment struct {
-	Horizontal string
-	Vertical   string
+	Horizontal   string
+	Indent       int
+	ShrinkToFit  bool
+	TextRotation int
+	Vertical     string
+	WrapText     bool
 }
 
-var defaultFontSize int
-var defaultFontName string
-
-func init() {
-	defaultFontSize = 12
-	defaultFontName = "Verdana"
-}
+var defaultFontSize = 12
+var defaultFontName = "Verdana"
 
 func SetDefaultFont(size int, name string) {
 	defaultFontSize = size
@@ -175,3 +171,10 @@ func DefaultFill() *Fill {
 func DefaultBorder() *Border {
 	return NewBorder("none", "none", "none", "none")
 }
+
+func DefaultAlignment() *Alignment {
+	return &Alignment{
+		Horizontal: "general",
+		Vertical:   "bottom",
+	}
+}

+ 1 - 6
common/src/github.com/tealeg/xlsx/style_test.go

@@ -35,9 +35,7 @@ func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	style.ApplyFill = true
 
 	style.ApplyFont = true
-	xFont, xFill, xBorder, xCellStyleXf, xCellXf := style.makeXLSXStyleElements()
-	// c.Assert(xNumFmt.NumFmtId, Equals, 164)
-	// c.Assert(xNumFmt.FormatCode, Equals, "GENERAL")
+	xFont, xFill, xBorder, xCellXf := style.makeXLSXStyleElements()
 	c.Assert(xFont.Sz.Val, Equals, "12")
 	c.Assert(xFont.Name.Val, Equals, "Verdana")
 	c.Assert(xFont.B, NotNil)
@@ -50,9 +48,6 @@ func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	c.Assert(xBorder.Right.Style, Equals, "thin")
 	c.Assert(xBorder.Top.Style, Equals, "thin")
 	c.Assert(xBorder.Bottom.Style, Equals, "thin")
-	c.Assert(xCellStyleXf.ApplyBorder, Equals, true)
-	c.Assert(xCellStyleXf.ApplyFill, Equals, true)
-	c.Assert(xCellStyleXf.ApplyFont, Equals, true)
 	c.Assert(xCellXf.ApplyBorder, Equals, true)
 	c.Assert(xCellXf.ApplyFill, Equals, true)
 	c.Assert(xCellXf.ApplyFont, Equals, true)

+ 55 - 45
common/src/github.com/tealeg/xlsx/write.go

@@ -1,6 +1,10 @@
 package xlsx
 
-import "reflect"
+import (
+	"fmt"
+	"reflect"
+	"time"
+)
 
 // Writes an array to row r. Accepts a pointer to array type 'e',
 // and writes the number of columns to write, 'cols'. If 'cols' is < 0,
@@ -11,49 +15,51 @@ func (r *Row) WriteSlice(e interface{}, cols int) int {
 		return cols
 	}
 
-	t := reflect.TypeOf(e).Elem()
-	if t.Kind() != reflect.Slice { // is 'e' even a slice?
+	// make sure 'e' is a Ptr to Slice
+	v := reflect.ValueOf(e)
+	if v.Kind() != reflect.Ptr {
 		return -1
 	}
 
-	// it's a slice, so open up its values
-	v := reflect.ValueOf(e).Elem()
+	v = v.Elem()
+	if v.Kind() != reflect.Slice {
+		return -1
+	}
 
+	// it's a slice, so open up its values
 	n := v.Len()
 	if cols < n && cols > 0 {
 		n = cols
 	}
 
-	var i int
-	switch t.Elem().Kind() { // underlying type of slice
-	case reflect.String:
-		for i = 0; i < n; i++ {
-			cell := r.AddCell()
-			cell.SetString(v.Index(i).Interface().(string))
-		}
-	case reflect.Int, reflect.Int8,
-		reflect.Int16, reflect.Int32:
-		for i = 0; i < n; i++ {
-			cell := r.AddCell()
-			cell.SetInt(v.Index(i).Interface().(int))
-		}
-	case reflect.Int64:
-		for i = 0; i < n; i++ {
+	var setCell func(reflect.Value)
+	setCell = func(val reflect.Value) {
+		switch t := val.Interface().(type) {
+		case time.Time:
 			cell := r.AddCell()
-			cell.SetInt64(v.Index(i).Interface().(int64))
-		}
-	case reflect.Bool:
-		for i = 0; i < n; i++ {
+			cell.SetValue(t)
+		case fmt.Stringer: // check Stringer first
 			cell := r.AddCell()
-			cell.SetBool(v.Index(i).Interface().(bool))
-		}
-	case reflect.Float64, reflect.Float32:
-		for i = 0; i < n; i++ {
-			cell := r.AddCell()
-			cell.SetFloat(v.Index(i).Interface().(float64))
+			cell.SetString(t.String())
+		default:
+			switch val.Kind() { // underlying type of slice
+			case reflect.String, reflect.Int, reflect.Int8,
+				reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.Float32:
+				cell := r.AddCell()
+				cell.SetValue(val.Interface())
+			case reflect.Bool:
+				cell := r.AddCell()
+				cell.SetBool(t.(bool))
+			case reflect.Interface:
+				setCell(reflect.ValueOf(t))
+			}
 		}
 	}
 
+	var i int
+	for i = 0; i < n; i++ {
+		setCell(v.Index(i))
+	}
 	return i
 }
 
@@ -78,23 +84,27 @@ func (r *Row) WriteStruct(e interface{}, cols int) int {
 
 	var k int
 	for i := 0; i < n; i, k = i+1, k+1 {
-		f := v.Field(i).Kind()
-		cell := r.AddCell()
+		f := v.Field(i)
 
-		switch f {
-		case reflect.Int, reflect.Int8,
-			reflect.Int16, reflect.Int32:
-			cell.SetInt(v.Field(i).Interface().(int))
-		case reflect.Int64:
-			cell.SetInt64(v.Field(i).Interface().(int64))
-		case reflect.String:
-			cell.SetString(v.Field(i).Interface().(string))
-		case reflect.Float64, reflect.Float32:
-			cell.SetFloat(v.Field(i).Interface().(float64))
-		case reflect.Bool:
-			cell.SetBool(v.Field(i).Interface().(bool))
+		switch t := f.Interface().(type) {
+		case time.Time:
+			cell := r.AddCell()
+			cell.SetValue(t)
+		case fmt.Stringer: // check Stringer first
+			cell := r.AddCell()
+			cell.SetString(t.String())
 		default:
-			k-- // nothing set so reset to previous
+			switch f.Kind() {
+			case reflect.String, reflect.Int, reflect.Int8,
+				reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.Float32:
+				cell := r.AddCell()
+				cell.SetValue(f.Interface())
+			case reflect.Bool:
+				cell := r.AddCell()
+				cell.SetBool(t.(bool))
+			default:
+				k-- // nothing set so reset to previous
+			}
 		}
 	}
 

+ 108 - 10
common/src/github.com/tealeg/xlsx/write_test.go

@@ -1,6 +1,9 @@
 package xlsx
 
 import (
+	"math"
+	"time"
+
 	. "gopkg.in/check.v1"
 )
 
@@ -8,36 +11,70 @@ type WriteSuite struct{}
 
 var _ = Suite(&WriteSuite{})
 
+type testStringerImpl struct {
+	Value string
+}
+
+func (this testStringerImpl) String() string {
+	return this.Value
+}
+
 // Test if we can write a struct to a row
 func (r *RowSuite) TestWriteStruct(c *C) {
 	var f *File
 	f = NewFile()
-	sheet := f.AddSheet("Test1")
+	sheet, _ := f.AddSheet("Test1")
 	row := sheet.AddRow()
 	type e struct {
-		FirstName string
-		Age       int
-		GPA       float64
-		LikesPHP  bool
+		FirstName   string
+		Age         int
+		GPA         float64
+		LikesPHP    bool
+		Stringer    testStringerImpl
+		StringerPtr *testStringerImpl
+		Time        time.Time
 	}
 	testStruct := e{
 		"Eric",
 		20,
 		3.94,
 		false,
+		testStringerImpl{"Stringer"},
+		&testStringerImpl{"Pointer to Stringer"},
+		time.Unix(0, 0),
 	}
-	row.WriteStruct(&testStruct, -1)
+	cnt := row.WriteStruct(&testStruct, -1)
+	c.Assert(cnt, Equals, 7)
 	c.Assert(row, NotNil)
 
-	c0 := row.Cells[0].String()
+	var (
+		c0, c4, c5 string
+		err        error
+		c6         float64
+	)
+	if c0, err = row.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	}
 	c1, e1 := row.Cells[1].Int()
 	c2, e2 := row.Cells[2].Float()
 	c3 := row.Cells[3].Bool()
+	if c4, err = row.Cells[4].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	if c5, err = row.Cells[5].FormattedValue(); err != nil {
+		c.Error(err)
+	}
+	if c6, err = row.Cells[6].Float(); err != nil {
+		c.Error(err)
+	}
 
 	c.Assert(c0, Equals, "Eric")
 	c.Assert(c1, Equals, 20)
 	c.Assert(c2, Equals, 3.94)
 	c.Assert(c3, Equals, false)
+	c.Assert(c4, Equals, "Stringer")
+	c.Assert(c5, Equals, "Pointer to Stringer")
+	c.Assert(math.Floor(c6), Equals, 25569.0)
 
 	c.Assert(e1, Equals, nil)
 	c.Assert(e2, Equals, nil)
@@ -47,19 +84,26 @@ func (r *RowSuite) TestWriteStruct(c *C) {
 func (r *RowSuite) TestWriteSlice(c *C) {
 	var f *File
 	f = NewFile()
-	sheet := f.AddSheet("Test1")
+	sheet, _ := f.AddSheet("Test1")
 
 	type strA []string
 	type intA []int
 	type floatA []float64
 	type boolA []bool
+	type interfaceA []interface{}
+	type stringerA []testStringerImpl
+	type stringerPtrA []*testStringerImpl
 
 	s0 := strA{"Eric"}
 	row0 := sheet.AddRow()
 	row0.WriteSlice(&s0, -1)
 	c.Assert(row0, NotNil)
-	c0 := row0.Cells[0].String()
-	c.Assert(c0, Equals, "Eric")
+
+	if val, err := row0.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Eric")
+	}
 
 	s1 := intA{10}
 	row1 := sheet.AddRow()
@@ -83,4 +127,58 @@ func (r *RowSuite) TestWriteSlice(c *C) {
 	c.Assert(row3, NotNil)
 	c3 := row3.Cells[0].Bool()
 	c.Assert(c3, Equals, true)
+
+	s4 := interfaceA{"Eric", 10, 3.94, true, time.Unix(0, 0)}
+	row4 := sheet.AddRow()
+	row4.WriteSlice(&s4, -1)
+	c.Assert(row4, NotNil)
+	if val, err := row4.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Eric")
+	}
+	c41, e41 := row4.Cells[1].Int()
+	c.Assert(e41, Equals, nil)
+	c.Assert(c41, Equals, 10)
+	c42, e42 := row4.Cells[2].Float()
+	c.Assert(e42, Equals, nil)
+	c.Assert(c42, Equals, 3.94)
+	c43 := row4.Cells[3].Bool()
+	c.Assert(c43, Equals, true)
+
+	c44, e44 := row4.Cells[4].Float()
+	c.Assert(e44, Equals, nil)
+	c.Assert(math.Floor(c44), Equals, 25569.0)
+
+	s5 := stringerA{testStringerImpl{"Stringer"}}
+	row5 := sheet.AddRow()
+	row5.WriteSlice(&s5, -1)
+	c.Assert(row5, NotNil)
+
+	if val, err := row5.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Stringer")
+	}
+
+	s6 := stringerPtrA{&testStringerImpl{"Pointer to Stringer"}}
+	row6 := sheet.AddRow()
+	row6.WriteSlice(&s6, -1)
+	c.Assert(row6, NotNil)
+
+	if val, err := row6.Cells[0].FormattedValue(); err != nil {
+		c.Error(err)
+	} else {
+		c.Assert(val, Equals, "Pointer to Stringer")
+	}
+
+	s7 := "expects -1 on non pointer to slice"
+	row7 := sheet.AddRow()
+	c.Assert(row7, NotNil)
+	s7_ret := row7.WriteSlice(s7, -1)
+	c.Assert(s7_ret, Equals, -1)
+	s7_ret = row7.WriteSlice(&s7, -1)
+	c.Assert(s7_ret, Equals, -1)
+	s7_ret = row7.WriteSlice([]string{s7}, -1)
+	c.Assert(s7_ret, Equals, -1)
 }

+ 199 - 139
common/src/github.com/tealeg/xlsx/xmlStyle.go

@@ -8,6 +8,7 @@
 package xlsx
 
 import (
+	"bytes"
 	"encoding/xml"
 	"fmt"
 	"strconv"
@@ -57,6 +58,14 @@ var builtInNumFmt = map[int]string{
 	49: "@",
 }
 
+var builtInNumFmtInv = make(map[string]int, 40)
+
+func init() {
+	for k, v := range builtInNumFmt {
+		builtInNumFmtInv[v] = k
+	}
+}
+
 const (
 	builtInNumFmtIndex_GENERAL = int(0)
 	builtInNumFmtIndex_INT     = int(1)
@@ -72,66 +81,76 @@ const (
 type xlsxStyleSheet struct {
 	XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
 
-	Fonts        xlsxFonts        `xml:"fonts,omitempty"`
-	Fills        xlsxFills        `xml:"fills,omitempty"`
-	Borders      xlsxBorders      `xml:"borders,omitempty"`
-	CellStyleXfs xlsxCellStyleXfs `xml:"cellStyleXfs,omitempty"`
-	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
-	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
+	Fonts        xlsxFonts         `xml:"fonts,omitempty"`
+	Fills        xlsxFills         `xml:"fills,omitempty"`
+	Borders      xlsxBorders       `xml:"borders,omitempty"`
+	CellStyles   *xlsxCellStyles   `xml:"cellStyles,omitempty"`
+	CellStyleXfs *xlsxCellStyleXfs `xml:"cellStyleXfs,omitempty"`
+	CellXfs      xlsxCellXfs       `xml:"cellXfs,omitempty"`
+	NumFmts      xlsxNumFmts       `xml:"numFmts,omitempty"`
 
-	theme          *theme
+	theme *theme
+
+	sync.RWMutex   // protects the following
 	styleCache     map[int]*Style
 	numFmtRefTable map[int]xlsxNumFmt
-	lock           *sync.RWMutex
 }
 
 func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
-	stylesheet := new(xlsxStyleSheet)
-	stylesheet.theme = t
-	stylesheet.styleCache = make(map[int]*Style)
-	stylesheet.lock = new(sync.RWMutex)
-	return stylesheet
+	return &xlsxStyleSheet{
+		theme:      t,
+		styleCache: make(map[int]*Style),
+	}
 }
 
 func (styles *xlsxStyleSheet) reset() {
 	styles.Fonts = xlsxFonts{}
 	styles.Fills = xlsxFills{}
 	styles.Borders = xlsxBorders{}
-	styles.CellStyleXfs = xlsxCellStyleXfs{}
+
+	// Microsoft seems to want an emtpy border to start with
+	styles.addBorder(
+		xlsxBorder{
+			Left:   xlsxLine{Style: "none"},
+			Right:  xlsxLine{Style: "none"},
+			Top:    xlsxLine{Style: "none"},
+			Bottom: xlsxLine{Style: "none"},
+		})
+
+	styles.CellStyleXfs = &xlsxCellStyleXfs{}
+
 	// add default xf
-	styles.CellXfs = xlsxCellXfs{Count: 1, Xf: []xlsxXf{xlsxXf{}}}
+	styles.CellXfs = xlsxCellXfs{Count: 1, Xf: []xlsxXf{{}}}
 	styles.NumFmts = xlsxNumFmts{}
 }
 
-func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
-	styles.lock.RLock()
+func (styles *xlsxStyleSheet) getStyle(styleIndex int) *Style {
+	styles.RLock()
 	style, ok := styles.styleCache[styleIndex]
-	styles.lock.RUnlock()
+	styles.RUnlock()
 	if ok {
-		return
+		return style
 	}
-	var styleXf xlsxXf
-	style = &Style{}
-	style.Border = Border{}
-	style.Fill = Fill{}
-	style.Font = Font{}
+
+	style = new(Style)
+
+	var namedStyleXf xlsxXf
 
 	xfCount := styles.CellXfs.Count
 	if styleIndex > -1 && xfCount > 0 && styleIndex <= xfCount {
 		xf := styles.CellXfs.Xf[styleIndex]
 
-		// Google docs can produce output that has fewer
-		// CellStyleXfs than CellXfs - this copes with that.
-		if styleIndex < styles.CellStyleXfs.Count {
-			styleXf = styles.CellStyleXfs.Xf[styleIndex]
+		if xf.XfId != nil && styles.CellStyleXfs != nil {
+			namedStyleXf = styles.CellStyleXfs.Xf[*xf.XfId]
+			style.NamedStyleIndex = xf.XfId
 		} else {
-			styleXf = xlsxXf{}
+			namedStyleXf = xlsxXf{}
 		}
 
-		style.ApplyBorder = xf.ApplyBorder || styleXf.ApplyBorder
-		style.ApplyFill = xf.ApplyFill || styleXf.ApplyFill
-		style.ApplyFont = xf.ApplyFont || styleXf.ApplyFont
-		style.ApplyAlignment = xf.ApplyAlignment || styleXf.ApplyAlignment
+		style.ApplyBorder = xf.ApplyBorder || namedStyleXf.ApplyBorder
+		style.ApplyFill = xf.ApplyFill || namedStyleXf.ApplyFill
+		style.ApplyFont = xf.ApplyFont || namedStyleXf.ApplyFont
+		style.ApplyAlignment = xf.ApplyAlignment || namedStyleXf.ApplyAlignment
 
 		if xf.BorderId > -1 && xf.BorderId < styles.Borders.Count {
 			var border xlsxBorder
@@ -178,9 +197,11 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 		if xf.Alignment.Vertical != "" {
 			style.Alignment.Vertical = xf.Alignment.Vertical
 		}
-		styles.lock.Lock()
+		style.Alignment.WrapText = xf.Alignment.WrapText
+
+		styles.Lock()
 		styles.styleCache[styleIndex] = style
-		styles.lock.Unlock()
+		styles.Unlock()
 	}
 	return style
 }
@@ -188,9 +209,8 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 func (styles *xlsxStyleSheet) argbValue(color xlsxColor) string {
 	if color.Theme != nil && styles.theme != nil {
 		return styles.theme.themeColor(int64(*color.Theme), color.Tint)
-	} else {
-		return color.RGB
 	}
+	return color.RGB
 }
 
 // Excel styles can reference number formats that are built-in, all of which
@@ -230,7 +250,7 @@ func (styles *xlsxStyleSheet) addFont(xFont xlsxFont) (index int) {
 	}
 	styles.Fonts.Font = append(styles.Fonts.Font, xFont)
 	index = styles.Fonts.Count
-	styles.Fonts.Count += 1
+	styles.Fonts.Count++
 	return
 }
 
@@ -243,7 +263,7 @@ func (styles *xlsxStyleSheet) addFill(xFill xlsxFill) (index int) {
 	}
 	styles.Fills.Fill = append(styles.Fills.Fill, xFill)
 	index = styles.Fills.Count
-	styles.Fills.Count += 1
+	styles.Fills.Count++
 	return
 }
 
@@ -256,12 +276,16 @@ func (styles *xlsxStyleSheet) addBorder(xBorder xlsxBorder) (index int) {
 	}
 	styles.Borders.Border = append(styles.Borders.Border, xBorder)
 	index = styles.Borders.Count
-	styles.Borders.Count += 1
+
+	styles.Borders.Count++
 	return
 }
 
 func (styles *xlsxStyleSheet) addCellStyleXf(xCellStyleXf xlsxXf) (index int) {
 	var cellStyleXf xlsxXf
+	if styles.CellStyleXfs == nil {
+		styles.CellStyleXfs = &xlsxCellStyleXfs{Count: 0}
+	}
 	for index, cellStyleXf = range styles.CellStyleXfs.Xf {
 		if cellStyleXf.Equals(xCellStyleXf) {
 			return index
@@ -269,7 +293,7 @@ func (styles *xlsxStyleSheet) addCellStyleXf(xCellStyleXf xlsxXf) (index int) {
 	}
 	styles.CellStyleXfs.Xf = append(styles.CellStyleXfs.Xf, xCellStyleXf)
 	index = styles.CellStyleXfs.Count
-	styles.CellStyleXfs.Count += 1
+	styles.CellStyleXfs.Count++
 	return
 }
 
@@ -283,7 +307,7 @@ func (styles *xlsxStyleSheet) addCellXf(xCellXf xlsxXf) (index int) {
 
 	styles.CellXfs.Xf = append(styles.CellXfs.Xf, xCellXf)
 	index = styles.CellXfs.Count
-	styles.CellXfs.Count += 1
+	styles.CellXfs.Count++
 	return
 }
 
@@ -293,11 +317,7 @@ func (styles *xlsxStyleSheet) newNumFmt(formatCode string) xlsxNumFmt {
 		return xlsxNumFmt{NumFmtId: 0, FormatCode: "general"}
 	}
 	// built in NumFmts in xmlStyle.go, traverse from the const.
-	numFmts := make(map[string]int)
-	for k, v := range builtInNumFmt {
-		numFmts[v] = k
-	}
-	numFmtId, ok := numFmts[formatCode]
+	numFmtId, ok := builtInNumFmtInv[formatCode]
 	if ok {
 		return xlsxNumFmt{NumFmtId: numFmtId, FormatCode: formatCode}
 	}
@@ -311,12 +331,12 @@ func (styles *xlsxStyleSheet) newNumFmt(formatCode string) xlsxNumFmt {
 
 	// The user define NumFmtId. The one less than 164 in built in.
 	numFmtId = builtinNumFmtsCount + 1
-	styles.lock.Lock()
-	defer styles.lock.Unlock()
+	styles.Lock()
+	defer styles.Unlock()
 	for {
 		// get a unused NumFmtId
 		if _, ok = styles.numFmtRefTable[numFmtId]; ok {
-			numFmtId += 1
+			numFmtId++
 		} else {
 			styles.addNumFmt(xlsxNumFmt{NumFmtId: numFmtId, FormatCode: formatCode})
 			break
@@ -338,63 +358,63 @@ func (styles *xlsxStyleSheet) addNumFmt(xNumFmt xlsxNumFmt) {
 		}
 		styles.NumFmts.NumFmt = append(styles.NumFmts.NumFmt, xNumFmt)
 		styles.numFmtRefTable[xNumFmt.NumFmtId] = xNumFmt
-		styles.NumFmts.Count += 1
+		styles.NumFmts.Count++
 	}
 }
 
-func (styles *xlsxStyleSheet) Marshal() (result string, err error) {
-	var xNumFmts string
-	var xfonts string
-	var xfills string
-	var xborders string
-	var xcellStyleXfs string
-	var xcellXfs string
+func (styles *xlsxStyleSheet) Marshal() (string, error) {
+	result := xml.Header + `<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
 
-	var outputFontMap map[int]int = make(map[int]int)
-	var outputFillMap map[int]int = make(map[int]int)
-	var outputBorderMap map[int]int = make(map[int]int)
-
-	result = xml.Header
-	result += `<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
-
-	xNumFmts, err = styles.NumFmts.Marshal()
+	xNumFmts, err := styles.NumFmts.Marshal()
 	if err != nil {
-		return
+		return "", err
 	}
 	result += xNumFmts
 
-	xfonts, err = styles.Fonts.Marshal(outputFontMap)
+	outputFontMap := make(map[int]int)
+	xfonts, err := styles.Fonts.Marshal(outputFontMap)
 	if err != nil {
-		return
+		return "", err
 	}
 	result += xfonts
 
-	xfills, err = styles.Fills.Marshal(outputFillMap)
+	outputFillMap := make(map[int]int)
+	xfills, err := styles.Fills.Marshal(outputFillMap)
 	if err != nil {
-		return
+		return "", err
 	}
 	result += xfills
 
-	xborders, err = styles.Borders.Marshal(outputBorderMap)
+	outputBorderMap := make(map[int]int)
+	xborders, err := styles.Borders.Marshal(outputBorderMap)
 	if err != nil {
-		return
+		return "", err
 	}
 	result += xborders
 
-	xcellStyleXfs, err = styles.CellStyleXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
-	if err != nil {
-		return
+	if styles.CellStyleXfs != nil {
+		xcellStyleXfs, err := styles.CellStyleXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+		if err != nil {
+			return "", err
+		}
+		result += xcellStyleXfs
 	}
-	result += xcellStyleXfs
 
-	xcellXfs, err = styles.CellXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
+	xcellXfs, err := styles.CellXfs.Marshal(outputBorderMap, outputFillMap, outputFontMap)
 	if err != nil {
-		return
+		return "", err
 	}
 	result += xcellXfs
 
-	result += `</styleSheet>`
-	return
+	if styles.CellStyles != nil {
+		xcellStyles, err := styles.CellStyles.Marshal()
+		if err != nil {
+			return "", err
+		}
+		result += xcellStyles
+	}
+
+	return result + "</styleSheet>", nil
 }
 
 // xlsxNumFmts directly maps the numFmts element in the namespace
@@ -432,7 +452,12 @@ type xlsxNumFmt struct {
 }
 
 func (numFmt *xlsxNumFmt) Marshal() (result string, err error) {
-	return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, numFmt.FormatCode), nil
+	formatCode := &bytes.Buffer{}
+	if err := xml.EscapeText(formatCode, []byte(numFmt.FormatCode)); err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf(`<numFmt numFmtId="%d" formatCode="%s"/>`, numFmt.NumFmtId, formatCode), nil
 }
 
 // xlsxFonts directly maps the fonts element in the namespace
@@ -458,7 +483,7 @@ func (fonts *xlsxFonts) Marshal(outputFontMap map[int]int) (result string, err e
 		}
 		if xfont != "" {
 			outputFontMap[i] = emittedCount
-			emittedCount += 1
+			emittedCount++
 			subparts += xfont
 		}
 	}
@@ -499,7 +524,7 @@ func (font *xlsxFont) Equals(other xlsxFont) bool {
 }
 
 func (font *xlsxFont) Marshal() (result string, err error) {
-	result = `<font>`
+	result = "<font>"
 	if font.Sz.Val != "" {
 		result += fmt.Sprintf(`<sz val="%s"/>`, font.Sz.Val)
 	}
@@ -524,8 +549,7 @@ func (font *xlsxFont) Marshal() (result string, err error) {
 	if font.U != nil {
 		result += "<u/>"
 	}
-	result += `</font>`
-	return
+	return result + "</font>", nil
 }
 
 // xlsxVal directly maps the val element in the namespace
@@ -549,27 +573,27 @@ type xlsxFills struct {
 	Fill  []xlsxFill `xml:"fill,omitempty"`
 }
 
-func (fills *xlsxFills) Marshal(outputFillMap map[int]int) (result string, err error) {
-	emittedCount := 0
-	subparts := ""
+func (fills *xlsxFills) Marshal(outputFillMap map[int]int) (string, error) {
+	var subparts string
+	var emittedCount int
 	for i, fill := range fills.Fill {
-		var xfill string
-		xfill, err = fill.Marshal()
+		xfill, err := fill.Marshal()
 		if err != nil {
-			return
+			return "", err
 		}
 		if xfill != "" {
 			outputFillMap[i] = emittedCount
-			emittedCount += 1
+			emittedCount++
 			subparts += xfill
 		}
 	}
+	var result string
 	if emittedCount > 0 {
 		result = fmt.Sprintf(`<fills count="%d">`, emittedCount)
 		result += subparts
 		result += `</fills>`
 	}
-	return
+	return result, nil
 }
 
 // xlsxFill directly maps the fill element in the namespace
@@ -655,7 +679,7 @@ func (color *xlsxColor) Equals(other xlsxColor) bool {
 // as I need.
 type xlsxBorders struct {
 	Count  int          `xml:"count,attr"`
-	Border []xlsxBorder `xml:"border,omitempty"`
+	Border []xlsxBorder `xml:"border"`
 }
 
 func (borders *xlsxBorders) Marshal(outputBorderMap map[int]int) (result string, err error) {
@@ -670,7 +694,7 @@ func (borders *xlsxBorders) Marshal(outputBorderMap map[int]int) (result string,
 		}
 		if xborder != "" {
 			outputBorderMap[i] = emittedCount
-			emittedCount += 1
+			emittedCount++
 			subparts += xborder
 		}
 	}
@@ -697,46 +721,39 @@ func (border *xlsxBorder) Equals(other xlsxBorder) bool {
 	return border.Left.Equals(other.Left) && border.Right.Equals(other.Right) && border.Top.Equals(other.Top) && border.Bottom.Equals(other.Bottom)
 }
 
+// To get borders to work correctly in Excel, you have to always start with an
+// empty set of borders. There was logic in this function that would strip out
+// empty elements, but unfortunately that would cause the border to fail.
+
 func (border *xlsxBorder) Marshal() (result string, err error) {
-	emit := false
 	subparts := ""
-	if border.Left.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
-		if border.Left.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
-		}
-		subparts += `</left>`
-	}
-	if border.Right.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
-		if border.Right.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
-		}
-		subparts += `</right>`
+	subparts += fmt.Sprintf(`<left style="%s">`, border.Left.Style)
+	if border.Left.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Left.Color.RGB)
 	}
-	if border.Top.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
-		if border.Top.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
-		}
-		subparts += `</top>`
+	subparts += `</left>`
+
+	subparts += fmt.Sprintf(`<right style="%s">`, border.Right.Style)
+	if border.Right.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Right.Color.RGB)
 	}
-	if border.Bottom.Style != "" {
-		emit = true
-		subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
-		if border.Bottom.Color.RGB != "" {
-			subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
-		}
-		subparts += `</bottom>`
+	subparts += `</right>`
+
+	subparts += fmt.Sprintf(`<top style="%s">`, border.Top.Style)
+	if border.Top.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Top.Color.RGB)
 	}
-	if emit {
-		result += `<border>`
-		result += subparts
-		result += `</border>`
+	subparts += `</top>`
+
+	subparts += fmt.Sprintf(`<bottom style="%s">`, border.Bottom.Style)
+	if border.Bottom.Color.RGB != "" {
+		subparts += fmt.Sprintf(`<color rgb="%s"/>`, border.Bottom.Color.RGB)
 	}
+	subparts += `</bottom>`
+
+	result += `<border>`
+	result += subparts
+	result += `</border>`
 	return
 }
 
@@ -753,6 +770,39 @@ func (line *xlsxLine) Equals(other xlsxLine) bool {
 	return line.Style == other.Style && line.Color.Equals(other.Color)
 }
 
+type xlsxCellStyles struct {
+	XMLName   xml.Name        `xml:"cellStyles"`
+	Count     int             `xml:"count,attr"`
+	CellStyle []xlsxCellStyle `xml:"cellStyle,omitempty"`
+}
+
+func (cellStyles *xlsxCellStyles) Marshal() (result string, err error) {
+	if cellStyles.Count > 0 {
+		result = fmt.Sprintf(`<cellStyles count="%d">`, cellStyles.Count)
+		for _, cellStyle := range cellStyles.CellStyle {
+			var xCellStyle []byte
+			xCellStyle, err = xml.Marshal(cellStyle)
+			if err != nil {
+				return
+			}
+			result += string(xCellStyle)
+		}
+		result += `</cellStyles>`
+	}
+	return
+
+}
+
+type xlsxCellStyle struct {
+	XMLName       xml.Name `xml:"cellStyle"`
+	BuiltInId     *int     `xml:"builtInId,attr,omitempty"`
+	CustomBuiltIn *bool    `xml:"customBuiltIn,attr,omitempty"`
+	Hidden        *bool    `xml:"hidden,attr,omitempty"`
+	ILevel        *bool    `xml:"iLevel,attr,omitempty"`
+	Name          string   `xml:"name,attr"`
+	XfId          int      `xml:"xfId,attr"`
+}
+
 // xlsxCellStyleXfs directly maps the cellStyleXfs element in the
 // namespace http://schemas.openxmlformats.org/spreadsheetml/2006/main
 // - currently I have not checked it for completeness - it does as
@@ -818,6 +868,7 @@ type xlsxXf struct {
 	FillId            int           `xml:"fillId,attr"`
 	FontId            int           `xml:"fontId,attr"`
 	NumFmtId          int           `xml:"numFmtId,attr"`
+	XfId              *int          `xml:"xfId,attr,omitempty"`
 	Alignment         xlsxAlignment `xml:"alignment"`
 }
 
@@ -831,19 +882,23 @@ func (xf *xlsxXf) Equals(other xlsxXf) bool {
 		xf.FillId == other.FillId &&
 		xf.FontId == other.FontId &&
 		xf.NumFmtId == other.NumFmtId &&
+		(xf.XfId == other.XfId ||
+			((xf.XfId != nil && other.XfId != nil) &&
+				*xf.XfId == *other.XfId)) &&
 		xf.Alignment.Equals(other.Alignment)
 }
 
 func (xf *xlsxXf) Marshal(outputBorderMap, outputFillMap, outputFontMap map[int]int) (result string, err error) {
-	var xAlignment string
-	result = fmt.Sprintf(`<xf applyAlignment="%b" applyBorder="%b" applyFont="%b" applyFill="%b" applyNumberFormat="%b" applyProtection="%b" borderId="%d" fillId="%d" fontId="%d" numFmtId="%d">`, bool2Int(xf.ApplyAlignment), bool2Int(xf.ApplyBorder), bool2Int(xf.ApplyFont), bool2Int(xf.ApplyFill), bool2Int(xf.ApplyNumberFormat), bool2Int(xf.ApplyProtection), outputBorderMap[xf.BorderId], outputFillMap[xf.FillId], outputFontMap[xf.FontId], xf.NumFmtId)
-	xAlignment, err = xf.Alignment.Marshal()
+	result = fmt.Sprintf(`<xf applyAlignment="%b" applyBorder="%b" applyFont="%b" applyFill="%b" applyNumberFormat="%b" applyProtection="%b" borderId="%d" fillId="%d" fontId="%d" numFmtId="%d"`, bool2Int(xf.ApplyAlignment), bool2Int(xf.ApplyBorder), bool2Int(xf.ApplyFont), bool2Int(xf.ApplyFill), bool2Int(xf.ApplyNumberFormat), bool2Int(xf.ApplyProtection), outputBorderMap[xf.BorderId], outputFillMap[xf.FillId], outputFontMap[xf.FontId], xf.NumFmtId)
+	if xf.XfId != nil {
+		result += fmt.Sprintf(` xfId="%d"`, *xf.XfId)
+	}
+	result += ">"
+	xAlignment, err := xf.Alignment.Marshal()
 	if err != nil {
-		return
+		return result, err
 	}
-	result += xAlignment
-	result += `</xf>`
-	return
+	return result + xAlignment + "</xf>", nil
 }
 
 type xlsxAlignment struct {
@@ -865,8 +920,13 @@ func (alignment *xlsxAlignment) Equals(other xlsxAlignment) bool {
 }
 
 func (alignment *xlsxAlignment) Marshal() (result string, err error) {
-	result = fmt.Sprintf(`<alignment horizontal="%s" indent="%d" shrinkToFit="%b" textRotation="%d" vertical="%s" wrapText="%b"/>`, alignment.Horizontal, alignment.Indent, bool2Int(alignment.ShrinkToFit), alignment.TextRotation, alignment.Vertical, bool2Int(alignment.WrapText))
-	return
+	if alignment.Horizontal == "" {
+		alignment.Horizontal = "general"
+	}
+	if alignment.Vertical == "" {
+		alignment.Vertical = "bottom"
+	}
+	return fmt.Sprintf(`<alignment horizontal="%s" indent="%d" shrinkToFit="%b" textRotation="%d" vertical="%s" wrapText="%b"/>`, alignment.Horizontal, alignment.Indent, bool2Int(alignment.ShrinkToFit), alignment.TextRotation, alignment.Vertical, bool2Int(alignment.WrapText)), nil
 }
 
 func bool2Int(b bool) int {

+ 40 - 4
common/src/github.com/tealeg/xlsx/xmlStyle_test.go

@@ -60,6 +60,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
 }
 
 // Test we produce valid output for a style file with one border definition.
+// Empty elements are required to accommodate for Excel quirks.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 	styles := newXlsxStyleSheet(nil)
 	styles.Borders = xlsxBorders{}
@@ -67,11 +68,11 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 	styles.Borders.Border = make([]xlsxBorder, 1)
 	border := xlsxBorder{}
 	border.Left.Style = "solid"
-	border.Top.Style = "none"
+	border.Top.Style = ""
 	styles.Borders.Border[0] = border
-
 	expected := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"></left><top style="none"></top></border></borders></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><borders count="1"><border><left style="solid"></left><right style=""></right><top style=""></top><bottom style=""></bottom></border></borders></styleSheet>`
+
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, expected)
@@ -80,7 +81,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 // Test we produce valid output for a style file with one cellStyleXf definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
 	styles := newXlsxStyleSheet(nil)
-	styles.CellStyleXfs = xlsxCellStyleXfs{}
+	styles.CellStyleXfs = &xlsxCellStyleXfs{}
 	styles.CellStyleXfs.Count = 1
 	styles.CellStyleXfs.Xf = make([]xlsxXf, 1)
 	xf := xlsxXf{}
@@ -109,6 +110,26 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
 	c.Assert(string(result), Equals, expected)
 }
 
+// Test we produce valid output for a style file with one cellStyle definition.
+func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyle(c *C) {
+	var builtInId int
+	styles := newXlsxStyleSheet(nil)
+	styles.CellStyles = &xlsxCellStyles{Count: 1}
+	styles.CellStyles.CellStyle = make([]xlsxCellStyle, 1)
+
+	builtInId = 31
+	styles.CellStyles.CellStyle[0] = xlsxCellStyle{
+		Name:      "Bob",
+		BuiltInId: &builtInId, // XXX Todo - work out built-ins!
+		XfId:      0,
+	}
+	expected := `<?xml version="1.0" encoding="UTF-8"?>
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><cellStyles count="1"><cellStyle builtInId="31" name="Bob" xfId="0"></cellStyle></cellStyles></styleSheet>`
+	result, err := styles.Marshal()
+	c.Assert(err, IsNil)
+	c.Assert(string(result), Equals, expected)
+}
+
 // Test we produce valid output for a style file with one cellXf
 // definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellXf(c *C) {
@@ -301,6 +322,21 @@ func (x *XMLStyleSuite) TestXfEquals(c *C) {
 	xfB.NumFmtId = 0
 	// for sanity
 	c.Assert(xfA.Equals(xfB), Equals, true)
+
+	var i1 int = 1
+
+	xfA.XfId = &i1
+	c.Assert(xfA.Equals(xfB), Equals, false)
+
+	xfB.XfId = &i1
+	c.Assert(xfA.Equals(xfB), Equals, true)
+
+	var i2 int = 1
+	xfB.XfId = &i2
+	c.Assert(xfA.Equals(xfB), Equals, true)
+
+	i2 = 2
+	c.Assert(xfA.Equals(xfB), Equals, false)
 }
 
 func (s *CellSuite) TestNewNumFmt(c *C) {

+ 18 - 3
common/src/github.com/tealeg/xlsx/xmlWorkbook.go

@@ -130,10 +130,25 @@ type xlsxDefinedNames struct {
 // namespace http://schemas.openxmlformats.org/spreadsheetml/2006/main
 // - currently I have not checked it for completeness - it does as
 // much as I need.
+// for a descriptions of the attributes see
+// https://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.definedname.aspx
 type xlsxDefinedName struct {
-	Data         string `xml:",chardata"`
-	Name         string `xml:"name,attr"`
-	LocalSheetID string `xml:"localSheetId,attr"`
+	Data              string `xml:",chardata"`
+	Name              string `xml:"name,attr"`
+	Comment           string `xml:"comment,attr,omitempty"`
+	CustomMenu        string `xml:"customMenu,attr,omitempty"`
+	Description       string `xml:"description,attr,omitempty"`
+	Help              string `xml:"help,attr,omitempty"`
+	ShortcutKey       string `xml:"shortcutKey,attr,omitempty"`
+	StatusBar         string `xml:"statusBar,attr,omitempty"`
+	LocalSheetID      int    `xml:"localSheetId,attr,omitempty"`
+	FunctionGroupID   int    `xml:"functionGroupId,attr,omitempty"`
+	Function          bool   `xml:"function,attr,omitempty"`
+	Hidden            bool   `xml:"hidden,attr,omitempty"`
+	VbProcedure       bool   `xml:"vbProcedure,attr,omitempty"`
+	PublishToServer   bool   `xml:"publishToServer,attr,omitempty"`
+	WorkbookParameter bool   `xml:"workbookParameter,attr,omitempty"`
+	Xlm               bool   `xml:"xml,attr,omitempty"`
 }
 
 // xlsxCalcPr directly maps the calcPr element from the namespace

+ 5 - 2
common/src/github.com/tealeg/xlsx/xmlWorkbook_test.go

@@ -47,7 +47,8 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
                    state="veryHidden"/>
           </sheets>
           <definedNames>
-            <definedName name="monitors"
+            <definedName name="monitors" comment="this is the comment"
+                         description="give cells a name"
                          localSheetId="0">Sheet1!$A$1533</definedName>
           </definedNames>
           <calcPr calcId="125725"/>
@@ -77,8 +78,10 @@ func (w *WorkbookSuite) TestUnmarshallWorkbookXML(c *C) {
 	c.Assert(workbook.DefinedNames.DefinedName, HasLen, 1)
 	dname := workbook.DefinedNames.DefinedName[0]
 	c.Assert(dname.Data, Equals, "Sheet1!$A$1533")
-	c.Assert(dname.LocalSheetID, Equals, "0")
+	c.Assert(dname.LocalSheetID, Equals, 0)
 	c.Assert(dname.Name, Equals, "monitors")
+	c.Assert(dname.Comment, Equals, "this is the comment")
+	c.Assert(dname.Description, Equals, "give cells a name")
 	c.Assert(workbook.CalcPr.CalcId, Equals, "125725")
 }
 

+ 29 - 19
common/src/github.com/tealeg/xlsx/xmlWorksheet.go

@@ -17,6 +17,7 @@ type xlsxWorksheet struct {
 	SheetFormatPr xlsxSheetFormatPr `xml:"sheetFormatPr"`
 	Cols          *xlsxCols         `xml:"cols,omitempty"`
 	SheetData     xlsxSheetData     `xml:"sheetData"`
+	AutoFilter    *xlsxAutoFilter   `xml:"autoFilter,omitempty"`
 	MergeCells    *xlsxMergeCells   `xml:"mergeCells,omitempty"`
 	PrintOptions  xlsxPrintOptions  `xml:"printOptions"`
 	PageMargins   xlsxPageMargins   `xml:"pageMargins"`
@@ -105,6 +106,8 @@ type xlsxPageMargins struct {
 type xlsxSheetFormatPr struct {
 	DefaultColWidth  float64 `xml:"defaultColWidth,attr,omitempty"`
 	DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
+	OutlineLevelCol  uint8   `xml:"outlineLevelCol,attr,omitempty"`
+	OutlineLevelRow  uint8   `xml:"outlineLevelRow,attr,omitempty"`
 }
 
 // xlsxSheetViews directly maps the sheetViews element in the namespace
@@ -136,8 +139,8 @@ type xlsxSheetView struct {
 	ZoomScaleNormal         float64         `xml:"zoomScaleNormal,attr"`
 	ZoomScalePageLayoutView float64         `xml:"zoomScalePageLayoutView,attr"`
 	WorkbookViewId          int             `xml:"workbookViewId,attr"`
-	Selection               []xlsxSelection `xml:"selection"`
 	Pane                    *xlsxPane       `xml:"pane"`
+	Selection               []xlsxSelection `xml:"selection"`
 }
 
 // xlsxSelection directly maps the selection element in the namespace
@@ -193,13 +196,14 @@ type xlsxCols struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxCol struct {
-	Collapsed   bool    `xml:"collapsed,attr"`
-	Hidden      bool    `xml:"hidden,attr"`
-	Max         int     `xml:"max,attr"`
-	Min         int     `xml:"min,attr"`
-	Style       int     `xml:"style,attr"`
-	Width       float64 `xml:"width,attr"`
-	CustomWidth int     `xml:"customWidth,attr,omitempty"`
+	Collapsed    bool    `xml:"collapsed,attr"`
+	Hidden       bool    `xml:"hidden,attr"`
+	Max          int     `xml:"max,attr"`
+	Min          int     `xml:"min,attr"`
+	Style        int     `xml:"style,attr"`
+	Width        float64 `xml:"width,attr"`
+	CustomWidth  bool    `xml:"customWidth,attr,omitempty"`
+	OutlineLevel uint8   `xml:"outlineLevel,attr,omitempty"`
 }
 
 // xlsxDimension directly maps the dimension element in the namespace
@@ -230,6 +234,11 @@ type xlsxRow struct {
 	C            []xlsxC `xml:"c"`
 	Ht           string  `xml:"ht,attr,omitempty"`
 	CustomHeight bool    `xml:"customHeight,attr,omitempty"`
+	OutlineLevel uint8   `xml:"outlineLevel,attr,omitempty"`
+}
+
+type xlsxAutoFilter struct {
+	Ref string `xml:"ref,attr"`
 }
 
 type xlsxMergeCell struct {
@@ -249,13 +258,13 @@ func (mc *xlsxMergeCells) getExtent(cellRef string) (int, int, error) {
 		return 0, 0, nil
 	}
 	for _, cell := range mc.Cells {
-		if strings.HasPrefix(cell.Ref, cellRef) {
+		if strings.HasPrefix(cell.Ref, cellRef+":") {
 			parts := strings.Split(cell.Ref, ":")
-			startx, starty, err := getCoordsFromCellIDString(parts[0])
+			startx, starty, err := GetCoordsFromCellIDString(parts[0])
 			if err != nil {
 				return -1, -1, err
 			}
-			endx, endy, err := getCoordsFromCellIDString(parts[1])
+			endx, endy, err := GetCoordsFromCellIDString(parts[1])
 			if err != nil {
 				return -2, -2, err
 			}
@@ -266,19 +275,20 @@ func (mc *xlsxMergeCells) getExtent(cellRef string) (int, int, error) {
 }
 
 // xlsxC directly maps the c element in the namespace
-// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxC struct {
-	R string `xml:"r,attr"`           // Cell ID, e.g. A1
-	S int    `xml:"s,attr,omitempty"` // Style reference.
-	T string `xml:"t,attr,omitempty"` // Type.
-	V string `xml:"v,omitempty"`      // Value
-	F *xlsxF `xml:"f,omitempty"`      // Formula
+	R  string  `xml:"r,attr"`           // Cell ID, e.g. A1
+	S  int     `xml:"s,attr,omitempty"` // Style reference.
+	T  string  `xml:"t,attr,omitempty"` // Type.
+	F  *xlsxF  `xml:"f,omitempty"`      // Formula
+	V  string  `xml:"v,omitempty"`      // Value
+	Is *xlsxSI `xml:"is,omitempty"`     // Inline String.
 }
 
-// xlsxC directly maps the f element in the namespace
-// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// xlsxF directly maps the f element in the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxF struct {

+ 12 - 4
common/src/github.com/tealeg/xlsx/xmlWorksheet_test.go

@@ -93,6 +93,7 @@ func (w *WorksheetSuite) TestUnmarshallWorksheet(c *C) {
               </c>
             </row>
           </sheetData>
+          <autoFilter ref="A1:Z4" />
           <printOptions headings="false"
                         gridLines="false"
                         gridLinesSet="true"
@@ -141,6 +142,8 @@ func (w *WorksheetSuite) TestUnmarshallWorksheet(c *C) {
 	c.Assert(cell.R, Equals, "A1")
 	c.Assert(cell.T, Equals, "s")
 	c.Assert(cell.V, Equals, "0")
+	c.Assert(worksheet.AutoFilter, NotNil)
+	c.Assert(worksheet.AutoFilter.Ref, Equals, "A1:Z4")
 }
 
 // MergeCells information is correctly read from the worksheet.
@@ -188,11 +191,16 @@ func (w *WorksheetSuite) TestUnmarshallWorksheetWithMergeCells(c *C) {
 // MergeCells.getExtents returns the horizontal and vertical extent of
 // a merge that begins at a given reference.
 func (w *WorksheetSuite) TestMergeCellsGetExtent(c *C) {
-	mc := xlsxMergeCells{Count: 1}
-	mc.Cells = make([]xlsxMergeCell, 1)
-	mc.Cells[0] = xlsxMergeCell{Ref: "A1:B5"}
+	mc := xlsxMergeCells{Count: 2}
+	mc.Cells = make([]xlsxMergeCell, 2)
+	mc.Cells[0] = xlsxMergeCell{Ref: "A11:A12"}
+	mc.Cells[1] = xlsxMergeCell{Ref: "A1:C5"}
 	h, v, err := mc.getExtent("A1")
 	c.Assert(err, IsNil)
-	c.Assert(h, Equals, 1)
+	c.Assert(h, Equals, 2)
 	c.Assert(v, Equals, 4)
+	h, v, err = mc.getExtent("A11")
+	c.Assert(err, IsNil)
+	c.Assert(h, Equals, 0)
+	c.Assert(v, Equals, 1)
 }

+ 1 - 1
common/src/github.com/xuri/excelize/README.md

@@ -11,7 +11,7 @@
 
 ## Introduction
 
-Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Microsoft Excel™ 2007 and later. Support save file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/xuri/excelize).
+Excelize is a library written in pure Golang and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Microsoft Excel™ 2007 and later. Support save file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize).
 
 ## Basic Usage
 

+ 7 - 1
common/src/github.com/xuri/excelize/excelize_test.go

@@ -682,6 +682,10 @@ func TestCopySheet(t *testing.T) {
 	if err != nil {
 		t.Log(err)
 	}
+	xlsx.SetCellValue("Sheet4", "F1", "Hello")
+	if xlsx.GetCellValue("Sheet1", "F1") == "Hello" {
+		t.Error("Invalid value \"Hello\" in Sheet1")
+	}
 	err = xlsx.Save()
 	if err != nil {
 		t.Log(err)
@@ -900,7 +904,7 @@ func TestRemoveRow(t *testing.T) {
 func TestConditionalFormat(t *testing.T) {
 	xlsx := NewFile()
 	for j := 1; j <= 10; j++ {
-		for i := 0; i <= 10; i++ {
+		for i := 0; i <= 15; i++ {
 			xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
 		}
 	}
@@ -937,6 +941,8 @@ func TestConditionalFormat(t *testing.T) {
 	xlsx.SetConditionalFormat("Sheet1", "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
 	// Data Bars: Gradient Fill.
 	xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+	// Use a formula to determine which cells to format.
+	xlsx.SetConditionalFormat("Sheet1", "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))
 	err = xlsx.SaveAs("./test/Workbook_conditional_format.xlsx")
 	if err != nil {
 		t.Log(err)

+ 12 - 0
common/src/github.com/xuri/excelize/lib.go

@@ -3,6 +3,7 @@ package excelize
 import (
 	"archive/zip"
 	"bytes"
+	"encoding/gob"
 	"io"
 	"log"
 	"math"
@@ -104,3 +105,14 @@ func intOnlyMapF(rune rune) rune {
 	}
 	return -1
 }
+
+// deepCopy provides method to creates a deep copy of whatever is passed to it
+// and returns the copy in an interface. The returned value will need to be
+// asserted to the correct type.
+func deepCopy(dst, src interface{}) error {
+	var buf bytes.Buffer
+	if err := gob.NewEncoder(&buf).Encode(src); err != nil {
+		return err
+	}
+	return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
+}

+ 2 - 1
common/src/github.com/xuri/excelize/sheet.go

@@ -435,7 +435,8 @@ func (f *File) CopySheet(from, to int) error {
 // target worksheet index.
 func (f *File) copySheet(from, to int) {
 	sheet := f.workSheetReader("sheet" + strconv.Itoa(from))
-	worksheet := *sheet
+	worksheet := xlsxWorksheet{}
+	deepCopy(&worksheet, &sheet)
 	path := "xl/worksheets/sheet" + strconv.Itoa(to) + ".xml"
 	if len(worksheet.SheetViews.SheetView) > 0 {
 		worksheet.SheetViews.SheetView[0].TabSelected = false

+ 14 - 2
common/src/github.com/xuri/excelize/styles.go

@@ -793,7 +793,7 @@ var validType = map[string]string{
 	"2_color_scale": "2_color_scale",
 	"3_color_scale": "3_color_scale",
 	"data_bar":      "dataBar",
-	"formula":       "expression", // Doesn't support currently
+	"formula":       "expression",
 }
 
 // criteriaType defined the list of valid criteria types.
@@ -2540,6 +2540,7 @@ func (f *File) SetConditionalFormat(sheet, area, formatSet string) {
 		"2_color_scale":   drawCondFmtColorScale,
 		"3_color_scale":   drawCondFmtColorScale,
 		"dataBar":         drawCondFmtDataBar,
+		"expression":      drawConfFmtExp,
 	}
 
 	xlsx := f.workSheetReader(sheet)
@@ -2554,7 +2555,7 @@ func (f *File) SetConditionalFormat(sheet, area, formatSet string) {
 		}
 		// Check for valid criteria types.
 		ct, ok = criteriaType[v.Criteria]
-		if !ok {
+		if !ok && vt != "expression" {
 			continue
 		}
 
@@ -2672,6 +2673,17 @@ func drawCondFmtDataBar(p int, ct string, format *formatConditional) *xlsxCfRule
 	}
 }
 
+// drawConfFmtExp provides function to create conditional formatting rule for
+// expression by given priority, criteria type and format settings.
+func drawConfFmtExp(p int, ct string, format *formatConditional) *xlsxCfRule {
+	return &xlsxCfRule{
+		Priority: p + 1,
+		Type:     validType[format.Type],
+		Formula:  []string{format.Criteria},
+		DxfID:    &format.Format,
+	}
+}
+
 // getPaletteColor provides function to convert the RBG color by given string.
 func getPaletteColor(color string) string {
 	return "FF" + strings.Replace(strings.ToUpper(color), "#", "", -1)

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików