浏览代码

Fixed merge conflict

Colin Fox 10 年之前
父节点
当前提交
1c45890ff2
共有 17 个文件被更改,包括 673 次插入61 次删除
  1. 28 17
      cell.go
  2. 15 0
      cell_test.go
  3. 2 1
      file.go
  4. 6 6
      file_test.go
  5. 145 0
      hsl.go
  6. 97 8
      lib.go
  7. 60 0
      lib_test.go
  8. 3 3
      sheet.go
  9. 4 4
      sheet_test.go
  10. 28 5
      style.go
  11. 6 0
      style_test.go
  12. 47 0
      theme.go
  13. 83 0
      theme_test.go
  14. 55 7
      xmlStyle.go
  15. 27 9
      xmlStyle_test.go
  16. 55 0
      xmlTheme.go
  17. 12 1
      xmlWorksheet.go

+ 28 - 17
cell.go

@@ -6,8 +6,10 @@ import (
 	"strconv"
 )
 
+// CellType is an int type for storing metadata about the data type in the cell.
 type CellType int
 
+// Known types for cell values.
 const (
 	CellTypeString CellType = iota
 	CellTypeFormula
@@ -38,6 +40,7 @@ type CellInterface interface {
 	FormattedValue() string
 }
 
+// NewCell creates a cell and adds it to a row.
 func NewCell(r *Row) *Cell {
 	return &Cell{style: NewStyle(), Row: r}
 }
@@ -48,11 +51,12 @@ func (c *Cell) Merge(hcells, vcells int) {
 	c.VMerge = vcells
 }
 
+// Type returns the CellType of a cell. See CellType constants for more details.
 func (c *Cell) Type() CellType {
 	return c.cellType
 }
 
-// Set string
+// SetString sets the value of a cell to a string.
 func (c *Cell) SetString(s string) {
 	c.Value = s
 	c.formula = ""
@@ -64,13 +68,13 @@ func (c *Cell) String() string {
 	return c.FormattedValue()
 }
 
-// Set float
+// SetFloat sets the value of a cell to a float.
 func (c *Cell) SetFloat(n float64) {
 	c.SetFloatWithFormat(n, "0.00e+00")
 }
 
 /*
-	Set float with format. The followings are samples of format samples.
+	The following are samples of format samples.
 
 	* "0.00e+00"
 	* "0", "#,##0"
@@ -80,6 +84,9 @@ func (c *Cell) SetFloat(n float64) {
 	* "0%", "0.00%"
 	* "0.00e+00", "##0.0e+0"
 */
+
+// SetFloatWithFormat sets the value of a cell to a float and applies
+// formatting to the cell.
 func (c *Cell) SetFloatWithFormat(n float64, format string) {
 	// tmp value. final value is formatted by FormattedValue() method
 	c.Value = fmt.Sprintf("%e", n)
@@ -89,7 +96,7 @@ func (c *Cell) SetFloatWithFormat(n float64, format string) {
 	c.cellType = CellTypeNumeric
 }
 
-// Returns the value of cell as a number
+// Float returns the value of cell as a number.
 func (c *Cell) Float() (float64, error) {
 	f, err := strconv.ParseFloat(c.Value, 64)
 	if err != nil {
@@ -98,7 +105,7 @@ func (c *Cell) Float() (float64, error) {
 	return f, nil
 }
 
-// Set a 64-bit integer
+// 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 = "0"
@@ -106,7 +113,7 @@ func (c *Cell) SetInt64(n int64) {
 	c.cellType = CellTypeNumeric
 }
 
-// Returns the value of cell as 64-bit integer
+// Int64 returns the value of cell as 64-bit integer.
 func (c *Cell) Int64() (int64, error) {
 	f, err := strconv.ParseInt(c.Value, 10, 64)
 	if err != nil {
@@ -115,7 +122,7 @@ func (c *Cell) Int64() (int64, error) {
 	return f, nil
 }
 
-// Set integer
+// SetInt sets a cell's value to an integer.
 func (c *Cell) SetInt(n int) {
 	c.Value = fmt.Sprintf("%d", n)
 	c.numFmt = "0"
@@ -123,7 +130,7 @@ func (c *Cell) SetInt(n int) {
 	c.cellType = CellTypeNumeric
 }
 
-// Returns the value of cell as integer
+// Int returns the value of cell as integer.
 // Has max 53 bits of precision
 // See: float64(int64(math.MaxInt))
 func (c *Cell) Int() (int, error) {
@@ -134,7 +141,7 @@ func (c *Cell) Int() (int, error) {
 	return int(f), nil
 }
 
-// Set boolean
+// SetBool sets a cell's value to a boolean.
 func (c *Cell) SetBool(b bool) {
 	if b {
 		c.Value = "1"
@@ -144,18 +151,20 @@ func (c *Cell) SetBool(b bool) {
 	c.cellType = CellTypeBool
 }
 
-// Get boolean
+// Bool returns a boolean from a cell's value.
+// TODO: Determine if the current return value is
+// appropriate for types other than CellTypeBool.
 func (c *Cell) Bool() bool {
 	return c.Value == "1"
 }
 
-// Set formula
+// SetFormula sets the format string for a cell.
 func (c *Cell) SetFormula(formula string) {
 	c.formula = formula
 	c.cellType = CellTypeFormula
 }
 
-// Returns formula
+// Formula returns the formula string for the cell.
 func (c *Cell) Formula() string {
 	return c.formula
 }
@@ -170,7 +179,7 @@ func (c *Cell) SetStyle(style *Style) {
 	c.style = style
 }
 
-// The number format string is returnable from a cell.
+// GetNumberFormat returns the number format string for a cell.
 func (c *Cell) GetNumberFormat() string {
 	return c.numFmt
 }
@@ -199,15 +208,17 @@ func (c *Cell) formatToInt(format string) string {
 	return fmt.Sprintf(format, int(f))
 }
 
-// Return the formatted version of the value.
+// 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 {
-	var numberFormat string = c.GetNumberFormat()
+	var numberFormat = c.GetNumberFormat()
 	switch numberFormat {
-	case "general":
+	case "general", "@":
 		return c.Value
 	case "0", "#,##0":
 		return c.formatToInt("%d")
-	case "0.00", "#,##0.00", "@":
+	case "0.00", "#,##0.00":
 		return c.formatToFloat("%.2f")
 	case "#,##0 ;(#,##0)", "#,##0 ;[red](#,##0)":
 		f, err := strconv.ParseFloat(c.Value, 64)

+ 15 - 0
cell_test.go

@@ -105,6 +105,9 @@ func (l *CellSuite) TestFormattedValue(c *C) {
 	negativeCell.numFmt = "general"
 	c.Assert(negativeCell.FormattedValue(), Equals, "-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")
 
@@ -280,3 +283,15 @@ func (s *CellSuite) TestSetterGetters(c *C) {
 	c.Assert(cell.Formula(), Equals, "10+20")
 	c.Assert(cell.Type(), Equals, CellTypeFormula)
 }
+
+// TestOddInput is a regression test for #101. When the number format
+// was "@" (string), the input below caused a crash in strconv.ParseFloat.
+// The solution was to check if cell.Value was both a CellTypeString and
+// had a numFmt of "general" or "@" and short-circuit FormattedValue() if so.
+func (s *CellSuite) TestOddInput(c *C) {
+	cell := Cell{}
+	odd := `[1],[12,"DATE NOT NULL DEFAULT '0000-00-00'"]`
+	cell.Value = odd
+	cell.numFmt = "@"
+	c.Assert(cell.String(), Equals, odd)
+}

+ 2 - 1
file.go

@@ -18,6 +18,7 @@ type File struct {
 	styles         *xlsxStyleSheet
 	Sheets         []*Sheet
 	Sheet          map[string]*Sheet
+	theme          *theme
 }
 
 // Create a new File
@@ -170,7 +171,7 @@ func (f *File) MarshallParts() (map[string]string, error) {
 	sheetIndex := 1
 
 	if f.styles == nil {
-		f.styles = newXlsxStyleSheet()
+		f.styles = newXlsxStyleSheet(f.theme)
 	}
 	f.styles.reset()
 	for _, sheet := range f.Sheets {

+ 6 - 6
file_test.go

@@ -775,10 +775,10 @@ func (s *SliceReaderSuite) TestFileWithEmptyRows(c *C) {
 }
 
 func (s *SliceReaderSuite) TestFileWithEmptyCols(c *C) {
-  f, err := OpenFile("./testdocs/empty_rows.xlsx")
-  c.Assert(err, IsNil)
-  sheet, ok := f.Sheet["EmptyCols"]
-  c.Assert(ok, Equals, true)
-  c.Assert(sheet.Cell(0, 0).String(), Equals, "")
-  c.Assert(sheet.Cell(0, 2).String(), Equals, "C1")
+	f, err := OpenFile("./testdocs/empty_rows.xlsx")
+	c.Assert(err, IsNil)
+	sheet, ok := f.Sheet["EmptyCols"]
+	c.Assert(ok, Equals, true)
+	c.Assert(sheet.Cell(0, 0).String(), Equals, "")
+	c.Assert(sheet.Cell(0, 2).String(), Equals, "C1")
 }

+ 145 - 0
hsl.go

@@ -0,0 +1,145 @@
+/*
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+	 * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+	 * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+	 * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package xlsx
+
+import (
+	"image/color"
+	"math"
+)
+
+// HSLModel converts any color.Color to a HSL color.
+var HSLModel = color.ModelFunc(hslModel)
+
+// HSL represents a cylindrical coordinate of points in an RGB color model.
+//
+// Values are in the range 0 to 1.
+type HSL struct {
+	H, S, L float64
+}
+
+// RGBA returns the alpha-premultiplied red, green, blue and alpha values
+// for the HSL.
+func (c HSL) RGBA() (uint32, uint32, uint32, uint32) {
+	r, g, b := HSLToRGB(c.H, c.S, c.L)
+	return uint32(r) * 0x101, uint32(g) * 0x101, uint32(b) * 0x101, 0xffff
+}
+
+// hslModel converts a color.Color to HSL.
+func hslModel(c color.Color) color.Color {
+	if _, ok := c.(HSL); ok {
+		return c
+	}
+	r, g, b, _ := c.RGBA()
+	h, s, l := RGBToHSL(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+	return HSL{h, s, l}
+}
+
+// RGBToHSL converts an RGB triple to a HSL triple.
+//
+// Ported from http://goo.gl/Vg1h9
+func RGBToHSL(r, g, b uint8) (h, s, l float64) {
+	fR := float64(r) / 255
+	fG := float64(g) / 255
+	fB := float64(b) / 255
+	max := math.Max(math.Max(fR, fG), fB)
+	min := math.Min(math.Min(fR, fG), fB)
+	l = (max + min) / 2
+	if max == min {
+		// Achromatic.
+		h, s = 0, 0
+	} else {
+		// Chromatic.
+		d := max - min
+		if l > 0.5 {
+			s = d / (2.0 - max - min)
+		} else {
+			s = d / (max + min)
+		}
+		switch max {
+		case fR:
+			h = (fG - fB) / d
+			if fG < fB {
+				h += 6
+			}
+		case fG:
+			h = (fB-fR)/d + 2
+		case fB:
+			h = (fR-fG)/d + 4
+		}
+		h /= 6
+	}
+	return
+}
+
+// HSLToRGB converts an HSL triple to a RGB triple.
+//
+// Ported from http://goo.gl/Vg1h9
+func HSLToRGB(h, s, l float64) (r, g, b uint8) {
+	var fR, fG, fB float64
+	if s == 0 {
+		fR, fG, fB = l, l, l
+	} else {
+		var q float64
+		if l < 0.5 {
+			q = l * (1 + s)
+		} else {
+			q = l + s - s*l
+		}
+		p := 2*l - q
+		fR = hueToRGB(p, q, h+1.0/3)
+		fG = hueToRGB(p, q, h)
+		fB = hueToRGB(p, q, h-1.0/3)
+	}
+	r = uint8((fR * 255) + 0.5)
+	g = uint8((fG * 255) + 0.5)
+	b = uint8((fB * 255) + 0.5)
+	return
+}
+
+// hueToRGB is a helper function for HSLToRGB.
+func hueToRGB(p, q, t float64) float64 {
+	if t < 0 {
+		t += 1
+	}
+	if t > 1 {
+		t -= 1
+	}
+	if t < 1.0/6 {
+		return p + (q-p)*6*t
+	}
+	if t < 0.5 {
+		return q
+	}
+	if t < 2.0/3 {
+		return p + (q-p)*(2.0/3-t)*6
+	}
+	return p
+}

+ 97 - 8
lib.go

@@ -311,10 +311,72 @@ func makeEmptyRow() *Row {
 	return row
 }
 
+type sharedFormula struct {
+	x, y    int
+	formula string
+}
+
+func formulaForCell(rawcell xlsxC, sharedFormulas map[int]sharedFormula) string {
+	var res string
+
+	f := rawcell.F
+	if f.T == "shared" {
+		x, y, err := getCoordsFromCellIDString(rawcell.R)
+		if err != nil {
+			res = f.Content
+		} else {
+			if f.Ref != "" {
+				res = f.Content
+				sharedFormulas[f.Si] = sharedFormula{x, y, res}
+			} else {
+				sharedFormula := sharedFormulas[f.Si]
+				dx := x - sharedFormula.x
+				dy := y - sharedFormula.y
+				orig := []byte(sharedFormula.formula)
+				var start, end int
+				for end = 0; end < len(orig); end++ {
+					c := orig[end]
+					if c >= 'A' && c <= 'Z' {
+						res += string(orig[start:end])
+						start = end
+						end++
+						foundNum := false
+						for ; end < len(orig); end++ {
+							idc := orig[end]
+							if idc >= '0' && idc <= '9' {
+								foundNum = true
+							} else if idc >= 'A' && idc <= 'Z' {
+								if foundNum {
+									break
+								}
+							} else {
+								break
+							}
+						}
+						if foundNum {
+							fx, fy, _ := getCoordsFromCellIDString(string(orig[start:end]))
+							fx += dx
+							fy += dy
+							res += getCellIDStringFromCoords(fx, fy)
+							start = end
+						}
+					}
+				}
+				if start < len(orig) {
+					res += string(orig[start:end])
+				}
+			}
+		}
+	} else {
+		res = f.Content
+	}
+	return strings.Trim(res, " \t\n\r")
+}
+
 // 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, cell *Cell) {
+func fillCellData(rawcell xlsxC, reftable *RefTable, sharedFormulas map[int]sharedFormula, cell *Cell) {
 	var data string = rawcell.V
 	if len(data) > 0 {
 		vval := strings.Trim(data, " \t\n\r")
@@ -331,17 +393,17 @@ func fillCellData(rawcell xlsxC, reftable *RefTable, cell *Cell) {
 			cell.cellType = CellTypeBool
 		case "e": // Error
 			cell.Value = vval
-			cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+			cell.formula = formulaForCell(rawcell, sharedFormulas)
 			cell.cellType = CellTypeError
 		default:
-			if len(rawcell.F) == 0 {
+			if rawcell.F == nil {
 				// Numeric
 				cell.Value = vval
 				cell.cellType = CellTypeNumeric
 			} else {
 				// Formula
 				cell.Value = vval
-				cell.formula = strings.Trim(rawcell.F, " \t\n\r")
+				cell.formula = formulaForCell(rawcell, sharedFormulas)
 				cell.cellType = CellTypeFormula
 			}
 		}
@@ -360,6 +422,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 	var reftable *RefTable
 	var err error
 	var insertRowIndex, insertColIndex int
+	sharedFormulas := map[int]sharedFormula{}
 
 	if len(Worksheet.SheetData.Row) == 0 {
 		return nil, nil, 0, 0
@@ -436,7 +499,7 @@ func readRowsFromSheet(Worksheet *xlsxWorksheet, file *File) ([]*Row, []*Col, in
 			}
 			cellX := insertColIndex
 			cell := row.Cells[cellX]
-			fillCellData(rawcell, reftable, cell)
+			fillCellData(rawcell, reftable, sharedFormulas, cell)
 			if file.styles != nil {
 				cell.style = file.styles.getStyle(rawcell.S)
 				cell.numFmt = file.styles.getNumberFormat(rawcell.S)
@@ -588,7 +651,7 @@ func readSharedStringsFromZipFile(f *zip.File) (*RefTable, error) {
 // readStylesFromZipFile() is an internal helper function to
 // extract a style table from the style.xml file within
 // the XLSX zip file.
-func readStylesFromZipFile(f *zip.File) (*xlsxStyleSheet, error) {
+func readStylesFromZipFile(f *zip.File, theme *theme) (*xlsxStyleSheet, error) {
 	var style *xlsxStyleSheet
 	var error error
 	var rc io.ReadCloser
@@ -597,7 +660,7 @@ func readStylesFromZipFile(f *zip.File) (*xlsxStyleSheet, error) {
 	if error != nil {
 		return nil, error
 	}
-	style = newXlsxStyleSheet()
+	style = newXlsxStyleSheet(theme)
 	decoder = xml.NewDecoder(rc)
 	error = decoder.Decode(style)
 	if error != nil {
@@ -614,6 +677,21 @@ func buildNumFmtRefTable(style *xlsxStyleSheet) {
 	}
 }
 
+func readThemeFromZipFile(f *zip.File) (*theme, error) {
+	rc, err := f.Open()
+	if err != nil {
+		return nil, err
+	}
+
+	var themeXml xlsxTheme
+	err = xml.NewDecoder(rc).Decode(&themeXml)
+	if err != nil {
+		return nil, err
+	}
+
+	return newTheme(themeXml), nil
+}
+
 type WorkBookRels map[string]string
 
 func (w *WorkBookRels) MakeXLSXWorkbookRels() xlsxWorkbookRels {
@@ -706,6 +784,7 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 	var sheets []*Sheet
 	var style *xlsxStyleSheet
 	var styles *zip.File
+	var themeFile *zip.File
 	var v *zip.File
 	var workbook *zip.File
 	var workbookRels *zip.File
@@ -724,6 +803,8 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 			workbookRels = v
 		case "xl/styles.xml":
 			styles = v
+		case "xl/theme/theme1.xml":
+			themeFile = v
 		default:
 			if len(v.Name) > 14 {
 				if v.Name[0:13] == "xl/worksheets" {
@@ -742,8 +823,16 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 		return nil, err
 	}
 	file.referenceTable = reftable
+	if themeFile != nil {
+		theme, err := readThemeFromZipFile(themeFile)
+		if err != nil {
+			return nil, err
+		}
+
+		file.theme = theme
+	}
 	if styles != nil {
-		style, err = readStylesFromZipFile(styles)
+		style, err = readStylesFromZipFile(styles, file.theme)
 		if err != nil {
 			return nil, err
 		}

+ 60 - 0
lib_test.go

@@ -834,3 +834,63 @@ func (l *LibSuite) TestReadRowFromRawWithPartialCoordinates(c *C) {
 	c.Assert(row, NotNil)
 	c.Assert(row.Cells, HasLen, 27)
 }
+
+func (l *LibSuite) TestSharedFormulas(c *C) {
+	var sheetxml = bytes.NewBufferString(`
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+           xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
+  <dimension ref="A1:C2"/>
+  <sheetViews>
+    <sheetView tabSelected="1" workbookViewId="0">
+      <selection activeCell="C1" sqref="C1"/>
+    </sheetView>
+  </sheetViews>
+  <sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
+  <sheetData>
+    <row r="1" spans="1:3">
+      <c r="A1">
+        <v>1</v>
+      </c>
+      <c r="B1">
+        <v>2</v>
+      </c>
+      <c r="C1">
+        <v>3</v>
+      </c>
+    </row>
+    <row r="2" spans="1:3">
+      <c r="A2">
+        <v>2</v>
+		<f t="shared" ref="A2:C2" si="0">2*A1</f>
+      </c>
+      <c r="B2">
+        <v>4</v>
+		<f t="shared" si="0"/>
+      </c>
+      <c r="C2">
+        <v>6</v>
+		<f t="shared" si="0"/>
+      </c>
+    </row>
+  </sheetData>
+  <pageMargins left="0.7" right="0.7"
+               top="0.78740157499999996"
+               bottom="0.78740157499999996"
+               header="0.3"
+               footer="0.3"/>
+</worksheet>`)
+
+	worksheet := new(xlsxWorksheet)
+	err := xml.NewDecoder(sheetxml).Decode(worksheet)
+	c.Assert(err, IsNil)
+
+	file := new(File)
+	rows, _, maxCols, maxRows := readRowsFromSheet(worksheet, file)
+	c.Assert(maxCols, Equals, 3)
+	c.Assert(maxRows, Equals, 2)
+
+	row := rows[1]
+	c.Assert(row.Cells[1].Formula(), Equals, "2*B1")
+	c.Assert(row.Cells[2].Formula(), Equals, "2*C1")
+}

+ 3 - 3
sheet.go

@@ -75,7 +75,7 @@ func (sh *Sheet) Cell(row, col int) *Cell {
 //Set the width of a single column or multiple columns.
 func (s *Sheet) SetColWidth(startcol, endcol int, width float64) error {
 	if startcol > endcol {
-		return fmt.Errorf("Could not set width for range %g-%g: startcol must be less than endcol.", startcol, endcol)
+		return fmt.Errorf("Could not set width for range %d-%d: startcol must be less than endcol.", startcol, endcol)
 	}
 	col := &Col{
 		Min:       startcol + 1,
@@ -147,11 +147,11 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xC.S = XfId
 			case CellTypeFormula:
 				xC.V = cell.Value
-				xC.F = cell.formula
+				xC.F = &xlsxF{Content: cell.formula}
 				xC.S = XfId
 			case CellTypeError:
 				xC.V = cell.Value
-				xC.F = cell.formula
+				xC.F = &xlsxF{Content: cell.formula}
 				xC.T = "e"
 				xC.S = XfId
 			}

+ 4 - 4
sheet_test.go

@@ -28,7 +28,7 @@ func (s *SheetSuite) TestMakeXLSXSheetFromRows(c *C) {
 	cell := row.AddCell()
 	cell.Value = "A cell!"
 	refTable := NewSharedStringRefTable()
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
 	c.Assert(xSheet.Dimension.Ref, Equals, "A1")
 	c.Assert(xSheet.SheetData.Row, HasLen, 1)
@@ -74,7 +74,7 @@ func (s *SheetSuite) TestMakeXLSXSheetAlsoPopulatesXLSXSTyles(c *C) {
 	cell2.SetStyle(style2)
 
 	refTable := NewSharedStringRefTable()
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	worksheet := sheet.makeXLSXSheet(refTable, styles)
 
 	c.Assert(styles.Fonts.Count, Equals, 1)
@@ -116,7 +116,7 @@ func (s *SheetSuite) TestMarshalSheet(c *C) {
 	cell := row.AddCell()
 	cell.Value = "A cell!"
 	refTable := NewSharedStringRefTable()
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
 
 	output := bytes.NewBufferString(xml.Header)
@@ -139,7 +139,7 @@ func (s *SheetSuite) TestMarshalSheetWithMultipleCells(c *C) {
 	cell = row.AddCell()
 	cell.Value = "A cell (with value 2)!"
 	refTable := NewSharedStringRefTable()
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	xSheet := sheet.makeXLSXSheet(refTable, styles)
 
 	output := bytes.NewBufferString(xml.Header)

+ 28 - 5
style.go

@@ -11,6 +11,7 @@ type Style struct {
 	ApplyBorder bool
 	ApplyFill   bool
 	ApplyFont   bool
+	Alignment   Alignment
 }
 
 // Return a new Style structure initialised with the default values.
@@ -34,6 +35,21 @@ func (style *Style) makeXLSXStyleElements() (xFont xlsxFont, xFill xlsxFill, xBo
 	xFont.Family.Val = strconv.Itoa(style.Font.Family)
 	xFont.Charset.Val = strconv.Itoa(style.Font.Charset)
 	xFont.Color.RGB = style.Font.Color
+	if style.Font.Bold {
+		xFont.B = &struct{}{}
+	} else {
+		xFont.B = nil
+	}
+	if style.Font.Italic {
+		xFont.I = &struct{}{}
+	} else {
+		xFont.I = nil
+	}
+	if style.Font.Underline {
+		xFont.U = &struct{}{}
+	} else {
+		xFont.U = nil
+	}
 	xPatternFill := xlsxPatternFill{}
 	xPatternFill.PatternType = style.Fill.PatternType
 	xPatternFill.FgColor.RGB = style.Fill.FgColor
@@ -80,17 +96,24 @@ func NewFill(patternType, fgColor, bgColor string) *Fill {
 }
 
 type Font struct {
-	Size    int
-	Name    string
-	Family  int
-	Charset int
-	Color   string
+	Size      int
+	Name      string
+	Family    int
+	Charset   int
+	Color     string
+	Bold      bool
+	Italic    bool
+	Underline bool
 }
 
 func NewFont(size int, name string) *Font {
 	return &Font{Size: size, Name: name}
 }
 
+type Alignment struct {
+	Horizontal string
+}
+
 func DefaulFont() *Font {
 	return NewFont(12, "Verdana")
 }

+ 6 - 0
style_test.go

@@ -23,6 +23,9 @@ func (s *StyleSuite) TestNewStyleDefaults(c *C) {
 func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	style := NewStyle()
 	font := *NewFont(12, "Verdana")
+	font.Bold = true
+	font.Italic = true
+	font.Underline = true
 	style.Font = font
 	fill := *NewFill("solid", "00FF0000", "FF000000")
 	style.Fill = fill
@@ -37,6 +40,9 @@ func (s *StyleSuite) TestMakeXLSXStyleElements(c *C) {
 	// c.Assert(xNumFmt.FormatCode, Equals, "GENERAL")
 	c.Assert(xFont.Sz.Val, Equals, "12")
 	c.Assert(xFont.Name.Val, Equals, "Verdana")
+	c.Assert(xFont.B, NotNil)
+	c.Assert(xFont.I, NotNil)
+	c.Assert(xFont.U, NotNil)
 	c.Assert(xFill.PatternFill.PatternType, Equals, "solid")
 	c.Assert(xFill.PatternFill.FgColor.RGB, Equals, "00FF0000")
 	c.Assert(xFill.PatternFill.BgColor.RGB, Equals, "FF000000")

+ 47 - 0
theme.go

@@ -0,0 +1,47 @@
+package xlsx
+
+import (
+	"fmt"
+	"strconv"
+)
+
+type theme struct {
+	colors []string
+}
+
+func newTheme(themeXml xlsxTheme) *theme {
+	clrMap := map[string]string{}
+	clrSchemes := themeXml.ThemeElements.ClrScheme.Children
+	for _, scheme := range clrSchemes {
+		var rgbColor string
+		if scheme.SysClr != nil {
+			rgbColor = scheme.SysClr.LastClr
+		} else {
+			rgbColor = scheme.SrgbClr.Val
+		}
+		clrMap[scheme.XMLName.Local] = rgbColor
+	}
+	colors := []string{clrMap["lt1"], clrMap["dk1"], clrMap["lt2"], clrMap["dk2"], clrMap["accent1"],
+		clrMap["accent2"], clrMap["accent3"], clrMap["accent4"], clrMap["accent5"],
+		clrMap["accent6"], clrMap["hlink"], clrMap["folHlink"]}
+	return &theme{colors}
+}
+
+func (t *theme) themeColor(index int64, tint float64) string {
+	baseColor := t.colors[index]
+	if tint == 0 {
+		return "FF" + baseColor
+	} else {
+		r, _ := strconv.ParseInt(baseColor[0:2], 16, 64)
+		g, _ := strconv.ParseInt(baseColor[2:4], 16, 64)
+		b, _ := strconv.ParseInt(baseColor[4:6], 16, 64)
+		h, s, l := RGBToHSL(uint8(r), uint8(g), uint8(b))
+		if tint < 0 {
+			l *= (1 + tint)
+		} else {
+			l = l*(1-tint) + (1 - (1 - tint))
+		}
+		br, bg, bb := HSLToRGB(h, s, l)
+		return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb)
+	}
+}

+ 83 - 0
theme_test.go

@@ -0,0 +1,83 @@
+package xlsx
+
+import (
+	"bytes"
+	"encoding/xml"
+
+	. "gopkg.in/check.v1"
+)
+
+type ThemeSuite struct{}
+
+var _ = Suite(&ThemeSuite{})
+
+func (s *ThemeSuite) TestThemeColors(c *C) {
+	themeXmlBytes := bytes.NewBufferString(`
+<?xml version="1.0"?>
+<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
+<a:themeElements>
+  <a:clrScheme name="Office">
+    <a:dk1>
+      <a:sysClr val="windowText" lastClr="000000"/>
+    </a:dk1>
+    <a:lt1>
+      <a:sysClr val="window" lastClr="FFFFFF"/>
+    </a:lt1>
+    <a:dk2>
+      <a:srgbClr val="1F497D"/>
+    </a:dk2>
+    <a:lt2>
+      <a:srgbClr val="EEECE1"/>
+    </a:lt2>
+    <a:accent1>
+      <a:srgbClr val="4F81BD"/>
+    </a:accent1>
+    <a:accent2>
+      <a:srgbClr val="C0504D"/>
+    </a:accent2>
+    <a:accent3>
+      <a:srgbClr val="9BBB59"/>
+    </a:accent3>
+    <a:accent4>
+      <a:srgbClr val="8064A2"/>
+    </a:accent4>
+    <a:accent5>
+      <a:srgbClr val="4BACC6"/>
+    </a:accent5>
+    <a:accent6>
+      <a:srgbClr val="F79646"/>
+    </a:accent6>
+    <a:hlink>
+      <a:srgbClr val="0000FF"/>
+    </a:hlink>
+    <a:folHlink>
+      <a:srgbClr val="800080"/>
+    </a:folHlink>
+  </a:clrScheme>
+</a:themeElements>
+</a:theme>
+	`)
+	var themeXml xlsxTheme
+	err := xml.NewDecoder(themeXmlBytes).Decode(&themeXml)
+	c.Assert(err, IsNil)
+
+	clrSchemes := themeXml.ThemeElements.ClrScheme.Children
+	c.Assert(len(clrSchemes), Equals, 12)
+
+	dk1Scheme := clrSchemes[0]
+	c.Assert(dk1Scheme.XMLName.Local, Equals, "dk1")
+	c.Assert(dk1Scheme.SrgbClr, IsNil)
+	c.Assert(dk1Scheme.SysClr, NotNil)
+	c.Assert(dk1Scheme.SysClr.Val, Equals, "windowText")
+	c.Assert(dk1Scheme.SysClr.LastClr, Equals, "000000")
+
+	dk2Scheme := clrSchemes[2]
+	c.Assert(dk2Scheme.XMLName.Local, Equals, "dk2")
+	c.Assert(dk2Scheme.SysClr, IsNil)
+	c.Assert(dk2Scheme.SrgbClr, NotNil)
+	c.Assert(dk2Scheme.SrgbClr.Val, Equals, "1F497D")
+
+	theme := newTheme(themeXml)
+	c.Assert(theme.themeColor(0, 0), Equals, "FFFFFFFF")
+	c.Assert(theme.themeColor(2, 0), Equals, "FFEEECE1")
+}

+ 55 - 7
xmlStyle.go

@@ -29,13 +29,15 @@ type xlsxStyleSheet struct {
 	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
 	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
 
-	styleCache map[int]*Style // `-`
-	numFmtRefTable map[int]xlsxNumFmt `xml:"-"`
-	lock       *sync.RWMutex
+	theme          *theme
+	styleCache     map[int]*Style
+	numFmtRefTable map[int]xlsxNumFmt
+	lock           *sync.RWMutex
 }
 
-func newXlsxStyleSheet() *xlsxStyleSheet {
+func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
 	stylesheet := new(xlsxStyleSheet)
+	stylesheet.theme = t
 	stylesheet.styleCache = make(map[int]*Style)
 	stylesheet.lock = new(sync.RWMutex)
 	return stylesheet
@@ -91,8 +93,8 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
 			xFill := styles.Fills.Fill[xf.FillId]
 			style.Fill.PatternType = xFill.PatternFill.PatternType
-			style.Fill.FgColor = xFill.PatternFill.FgColor.RGB
-			style.Fill.BgColor = xFill.PatternFill.BgColor.RGB
+			style.Fill.FgColor = styles.argbValue(xFill.PatternFill.FgColor)
+			style.Fill.BgColor = styles.argbValue(xFill.PatternFill.BgColor)
 		}
 
 		if xf.FontId > -1 && xf.FontId < styles.Fonts.Count {
@@ -101,13 +103,34 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 			style.Font.Name = xfont.Name.Val
 			style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
 			style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
+			style.Font.Color = styles.argbValue(xfont.Color)
+
+			if xfont.B != nil {
+				style.Font.Bold = true
+			}
+			if xfont.I != nil {
+				style.Font.Italic = true
+			}
+			if xfont.U != nil {
+				style.Font.Underline = true
+			}
+		}
+		if xf.Alignment.Horizontal != "" {
+			style.Alignment.Horizontal = xf.Alignment.Horizontal
 		}
 		styles.lock.Lock()
 		styles.styleCache[styleIndex] = style
 		styles.lock.Unlock()
 	}
 	return 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
+	}
 }
 
 // Excel styles can reference number formats that are built-in, all of which
@@ -153,6 +176,8 @@ func getBuiltinNumberFormat(numFmtId int) string {
 		return "m/d/yy h:mm"
 	case 37:
 		return "#,##0 ;(#,##0)"
+	case 38:
+		return "#,##0 ;[Red](#,##0)"
 	case 39:
 		return "#,##0.00;(#,##0.00)"
 	case 40:
@@ -420,9 +445,21 @@ type xlsxFont struct {
 	Family  xlsxVal   `xml:"family,omitempty"`
 	Charset xlsxVal   `xml:"charset,omitempty"`
 	Color   xlsxColor `xml:"color,omitempty"`
+	B       *struct{} `xml:"b,omitempty"`
+	I       *struct{} `xml:"i,omitempty"`
+	U       *struct{} `xml:"u,omitempty"`
 }
 
 func (font *xlsxFont) Equals(other xlsxFont) bool {
+	if (font.B == nil && other.B != nil) || (font.B != nil && other.B == nil) {
+		return false
+	}
+	if (font.I == nil && other.I != nil) || (font.I != nil && other.I == nil) {
+		return false
+	}
+	if (font.U == nil && other.U != nil) || (font.U != nil && other.U == nil) {
+		return false
+	}
 	return font.Sz.Equals(other.Sz) && font.Name.Equals(other.Name) && font.Family.Equals(other.Family) && font.Charset.Equals(other.Charset) && font.Color.Equals(other.Color)
 }
 
@@ -443,6 +480,15 @@ func (font *xlsxFont) Marshal() (result string, err error) {
 	if font.Color.RGB != "" {
 		result += fmt.Sprintf(`<color rgb="%s"/>`, font.Color.RGB)
 	}
+	if font.B != nil {
+		result += "<b/>"
+	}
+	if font.I != nil {
+		result += "<i/>"
+	}
+	if font.U != nil {
+		result += "<u/>"
+	}
 	result += `</font>`
 	return
 }
@@ -559,7 +605,9 @@ func (patternFill *xlsxPatternFill) Marshal() (result string, err error) {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxColor struct {
-	RGB string `xml:"rgb,attr,omitempty"`
+	RGB   string  `xml:"rgb,attr,omitempty"`
+	Theme *int    `xml:"theme,attr,omitempty"`
+	Tint  float64 `xml:"tint,attr,omitempty"`
 }
 
 func (color *xlsxColor) Equals(other xlsxColor) bool {

+ 27 - 9
xmlStyle_test.go

@@ -10,7 +10,7 @@ var _ = Suite(&XMLStyleSuite{})
 
 // Test we produce valid output for an empty style file.
 func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, `<?xml version="1.0" encoding="UTF-8"?>
@@ -19,17 +19,20 @@ func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
 
 // Test we produce valid output for a style file with one font definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Fonts = xlsxFonts{}
 	styles.Fonts.Count = 1
 	styles.Fonts.Font = make([]xlsxFont, 1)
 	font := xlsxFont{}
 	font.Sz.Val = "10"
 	font.Name.Val = "Andale Mono"
+	font.B = &struct{}{}
+	font.I = &struct{}{}
+	font.U = &struct{}{}
 	styles.Fonts.Font[0] = font
 
 	expected := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="10"/><name val="Andale Mono"/></font></fonts></styleSheet>`
+<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="10"/><name val="Andale Mono"/><b/><i/><u/></font></fonts></styleSheet>`
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, expected)
@@ -37,7 +40,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
 
 // Test we produce valid output for a style file with one fill definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Fills = xlsxFills{}
 	styles.Fills.Count = 1
 	styles.Fills.Fill = make([]xlsxFill, 1)
@@ -58,7 +61,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
 
 // Test we produce valid output for a style file with one border definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Borders = xlsxBorders{}
 	styles.Borders.Count = 1
 	styles.Borders.Border = make([]xlsxBorder, 1)
@@ -76,7 +79,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()
+	styles := newXlsxStyleSheet(nil)
 	styles.CellStyleXfs = xlsxCellStyleXfs{}
 	styles.CellStyleXfs.Count = 1
 	styles.CellStyleXfs.Xf = make([]xlsxXf, 1)
@@ -109,7 +112,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
 // Test we produce valid output for a style file with one cellXf
 // definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellXf(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.CellXfs = xlsxCellXfs{}
 	styles.CellXfs.Count = 1
 	styles.CellXfs.Xf = make([]xlsxXf, 1)
@@ -159,11 +162,17 @@ func (x *XMLStyleSuite) TestFontEquals(c *C) {
 	fontA := xlsxFont{Sz: xlsxVal{Val: "11"},
 		Color:  xlsxColor{RGB: "FFFF0000"},
 		Name:   xlsxVal{Val: "Calibri"},
-		Family: xlsxVal{Val: "2"}}
+		Family: xlsxVal{Val: "2"},
+		B:      &struct{}{},
+		I:      &struct{}{},
+		U:      &struct{}{}}
 	fontB := xlsxFont{Sz: xlsxVal{Val: "11"},
 		Color:  xlsxColor{RGB: "FFFF0000"},
 		Name:   xlsxVal{Val: "Calibri"},
-		Family: xlsxVal{Val: "2"}}
+		Family: xlsxVal{Val: "2"},
+		B:      &struct{}{},
+		I:      &struct{}{},
+		U:      &struct{}{}}
 
 	c.Assert(fontA.Equals(fontB), Equals, true)
 	fontB.Sz.Val = "12"
@@ -178,6 +187,15 @@ func (x *XMLStyleSuite) TestFontEquals(c *C) {
 	fontB.Family.Val = "1"
 	c.Assert(fontA.Equals(fontB), Equals, false)
 	fontB.Family.Val = "2"
+	fontB.B = nil
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.B = &struct{}{}
+	fontB.I = nil
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.I = &struct{}{}
+	fontB.U = nil
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.U = &struct{}{}
 	// For sanity
 	c.Assert(fontA.Equals(fontB), Equals, true)
 }

+ 55 - 0
xmlTheme.go

@@ -0,0 +1,55 @@
+package xlsx
+
+import "encoding/xml"
+
+// xlsxTheme directly maps the theme element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxTheme struct {
+	ThemeElements xlsxThemeElements `xml:"themeElements"`
+}
+
+// xlsxThemeElements directly maps the themeElements element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxThemeElements struct {
+	ClrScheme xlsxClrScheme `xml:"clrScheme"`
+}
+
+// xlsxClrScheme directly maps the clrScheme element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxClrScheme struct {
+	Name     string            `xml:"name,attr"`
+	Children []xlsxClrSchemeEl `xml:",any"`
+}
+
+// xlsxClrScheme maps to children of the clrScheme element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxClrSchemeEl struct {
+	XMLName xml.Name
+	SysClr  *xlsxSysClr  `xml:"sysClr"`
+	SrgbClr *xlsxSrgbClr `xml:"srgbClr"`
+}
+
+// xlsxSysClr directly maps the sysClr element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSysClr struct {
+	Val     string `xml:"val,attr"`
+	LastClr string `xml:"lastClr,attr"`
+}
+
+// xlsxSrgbClr directly maps the srgbClr element in the namespace
+// http://schemas.openxmlformats.org/drawingml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxSrgbClr struct {
+	Val string `xml:"val,attr"`
+}

+ 12 - 1
xmlWorksheet.go

@@ -246,7 +246,18 @@ type xlsxC struct {
 	S int    `xml:"s,attr"`           // Style reference.
 	T string `xml:"t,attr,omitempty"` // Type.
 	V string `xml:"v"`                // Value
-	F string `xml:"f,omitempty"`      // Formula
+	F *xlsxF `xml:"f,omitempty"`      // Formula
+}
+
+// xlsxC directly maps the f element in the namespace
+// http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
+// currently I have not checked it for completeness - it does as much
+// as I need.
+type xlsxF struct {
+	Content string `xml:",chardata"`
+	T       string `xml:"t,attr,omitempty"`   // Formula type
+	Ref     string `xml:"ref,attr,omitempty"` // Shared formula ref
+	Si      int    `xml:"si,attr,omitempty"`  // Shared formula index
 }
 
 // Create a new XLSX Worksheet with default values populated.