Procházet zdrojové kódy

Merge remote-tracking branch 'tealeg/master'

Conflicts:
	sheet.go
Brian Smith před 10 roky
rodič
revize
4975f0af52
15 změnil soubory, kde provedl 280 přidání a 108 odebrání
  1. 47 19
      cell.go
  2. 39 0
      cell_test.go
  3. 9 0
      file.go
  4. 11 8
      file_test.go
  5. 56 56
      lib.go
  6. 2 2
      lib_test.go
  7. 34 10
      sheet.go
  8. 1 1
      sheet_test.go
  9. 15 0
      style.go
  10. 6 0
      style_test.go
  11. 3 3
      theme.go
  12. 1 1
      write.go
  13. 22 4
      xmlStyle.go
  14. 21 3
      xmlStyle_test.go
  15. 13 1
      xmlWorksheet.go

+ 47 - 19
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
@@ -27,6 +29,8 @@ type Cell struct {
 	numFmt   string
 	date1904 bool
 	Hidden   bool
+	HMerge   int
+	VMerge   int
 	cellType CellType
 }
 
@@ -36,15 +40,23 @@ 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}
 }
 
+// Merge with other cells, horizontally and/or vertically.
+func (c *Cell) Merge(hcells, vcells int) {
+	c.HMerge = hcells
+	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 = ""
@@ -56,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"
@@ -72,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)
@@ -81,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 {
@@ -90,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"
@@ -98,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 {
@@ -107,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"
@@ -115,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) {
@@ -126,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"
@@ -136,18 +151,29 @@ 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"
+	// If bool, just return the value.
+	if c.cellType == CellTypeBool {
+		return c.Value == "1"
+	}
+	// If numeric, base it on a non-zero.
+	if c.cellType == CellTypeNumeric {
+		return c.Value != "0"
+	}
+	// Return whether there's an empty string.
+	return c.Value != ""
 }
 
-// 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
 }
@@ -162,7 +188,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
 }
@@ -191,15 +217,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)
@@ -275,7 +303,7 @@ func (c *Cell) FormattedValue() string {
 		t := TimeFromExcelTime(f, c.date1904)
 		return fmt.Sprintf("%0d%0d.%d", t.Minute(), t.Second(), t.Nanosecond()/1000)
 
-	case "yyyy\\-mm\\-dd":
+	case "yyyy\\-mm\\-dd", "yyyy\\-mm\\-dd;@":
 		return c.formatToTime("2006\\-01\\-02")
 	case "dd/mm/yy":
 		return c.formatToTime("02/01/06")

+ 39 - 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,39 @@ 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)
+}
+
+// TestBool tests basic Bool getting and setting booleans.
+func (s *CellSuite) TestBool(c *C) {
+	cell := Cell{}
+	cell.SetBool(true)
+	c.Assert(cell.Value, Equals, "1")
+	c.Assert(cell.Bool(), Equals, true)
+	cell.SetBool(false)
+	c.Assert(cell.Value, Equals, "0")
+	c.Assert(cell.Bool(), Equals, false)
+}
+
+// TestStringBool tests calling Bool on a non CellTypeBool value.
+func (s *CellSuite) TestStringBool(c *C) {
+	cell := Cell{}
+	cell.SetInt(0)
+	c.Assert(cell.Bool(), Equals, false)
+	cell.SetInt(1)
+	c.Assert(cell.Bool(), Equals, true)
+	cell.SetString("")
+	c.Assert(cell.Bool(), Equals, false)
+	cell.SetString("0")
+	c.Assert(cell.Bool(), Equals, true)
+}

+ 9 - 0
file.go

@@ -7,6 +7,7 @@ import (
 	"io"
 	"os"
 	"strconv"
+	"strings"
 )
 
 // File is a high level structure providing a slice of Sheet structs
@@ -203,6 +204,14 @@ func (f *File) MarshallParts() (map[string]string, error) {
 		return parts, err
 	}
 
+	// Make it work with Mac Numbers.
+	// Dirty hack to fix issues #63 and #91; encoding/xml currently
+	// "doesn't allow for additional namespaces to be defined in the root element of the document,"
+	// as described by @tealeg in the comments for #63.
+	oldXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`
+	newXmlns := `<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">`
+	parts["xl/workbook.xml"] = strings.Replace(parts["xl/workbook.xml"], oldXmlns, newXmlns, 1)
+
 	parts["_rels/.rels"] = TEMPLATE__RELS_DOT_RELS
 	parts["docProps/app.xml"] = TEMPLATE_DOCPROPS_APP
 	// TODO - do this properly, modification and revision information

+ 11 - 8
file_test.go

@@ -636,8 +636,11 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	c.Assert(parts["xl/_rels/workbook.xml.rels"], Equals, expectedXLSXWorkbookRels)
 
 	// workbook.xml
+    // Note that the following XML snippet is just pasted in here to correspond to the hack
+    // added in file.go to support Apple Numbers so the test passes.
+    // `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"`
 	expectedWorkbook := `<?xml version="1.0" encoding="UTF-8"?>
-<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fileVersion appName="Go XLSX"></fileVersion><workbookPr showObjects="all" date1904="false"></workbookPr><workbookProtection></workbookProtection><bookViews><workbookView showHorizontalScroll="true" showVerticalScroll="true" showSheetTabs="true" tabRatio="204" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"></workbookView></bookViews><sheets><sheet name="MySheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1" state="visible"></sheet><sheet name="AnotherSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2" state="visible"></sheet></sheets><definedNames></definedNames><calcPr iterateCount="100" refMode="A1" iterateDelta="0.001"></calcPr></workbook>`
+<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><fileVersion appName="Go XLSX"></fileVersion><workbookPr showObjects="all" date1904="false"></workbookPr><workbookProtection></workbookProtection><bookViews><workbookView showHorizontalScroll="true" showVerticalScroll="true" showSheetTabs="true" tabRatio="204" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"></workbookView></bookViews><sheets><sheet name="MySheet" sheetId="1" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId1" state="visible"></sheet><sheet name="AnotherSheet" sheetId="2" xmlns:relationships="http://schemas.openxmlformats.org/officeDocument/2006/relationships" relationships:id="rId2" state="visible"></sheet></sheets><definedNames></definedNames><calcPr iterateCount="100" refMode="A1" iterateDelta="0.001"></calcPr></workbook>`
 	c.Assert(parts["xl/workbook.xml"], Equals, expectedWorkbook)
 
 	// [Content_Types].xml
@@ -650,7 +653,7 @@ func (l *FileSuite) TestMarshalFile(c *C) {
 	// For now we only allow simple string data in the
 	// spreadsheet.  Style support will follow.
 	expectedStyles := `<?xml version="1.0" encoding="UTF-8"?>
-<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="1"><font><sz val="12"/><name val="Verdana"/><family val="0"/><charset val="0"/></font></fonts><fills count="1"><fill><patternFill patternType="none"><fgColor rgb="FFFFFFFF"/><bgColor rgb="00000000"/></patternFill></fill></fills><borders count="1"><border><left style="none"/><right style="none"/><top style="none"/><bottom style="none"/></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
+<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="lightGrey"/></fill></fills><borders count="1"><border><left style="none"/><right style="none"/><top style="none"/><bottom style="none"/></border></borders><cellStyleXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellStyleXfs><cellXfs count="1"><xf applyAlignment="0" applyBorder="0" applyFont="0" applyFill="0" applyProtection="0" borderId="0" fillId="0" fontId="0" numFmtId="0"><alignment horizontal="" indent="0" shrinkToFit="0" textRotation="0" vertical="" wrapText="0"/></xf></cellXfs></styleSheet>`
 	c.Assert(parts["xl/styles.xml"], Equals, expectedStyles)
 }
 
@@ -775,10 +778,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")
 }

+ 56 - 56
lib.go

@@ -312,65 +312,65 @@ func makeEmptyRow() *Row {
 }
 
 type sharedFormula struct {
-   x, y    int
-   formula string
+	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")
+	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

+ 2 - 2
lib_test.go

@@ -317,8 +317,8 @@ func (l *LibSuite) TestReadRowsFromSheet(c *C) {
 	sheetView := worksheet.SheetViews.SheetView[0]
 	c.Assert(sheetView.Pane, NotNil)
 	pane := sheetView.Pane
-	c.Assert(pane.XSplit, Equals, 0)
-	c.Assert(pane.YSplit, Equals, 1)
+	c.Assert(pane.XSplit, Equals, 0.0)
+	c.Assert(pane.YSplit, Equals, 1.0)
 }
 
 func (l *LibSuite) TestReadRowsFromSheetWithLeadingEmptyRows(c *C) {

+ 34 - 10
sheet.go

@@ -8,13 +8,13 @@ import (
 // Sheet is a high level structure intended to provide user access to
 // the contents of a particular sheet within an XLSX file.
 type Sheet struct {
-	Name   string
-	File   *File
-	Rows   []*Row
-	Cols   []*Col
-	MaxRow int
-	MaxCol int
-	Hidden bool
+	Name        string
+	File        *File
+	Rows        []*Row
+	Cols        []*Col
+	MaxRow      int
+	MaxCol      int
+	Hidden      bool
 	SheetViews  []SheetView
 	SheetFormat SheetFormat
 }
@@ -78,10 +78,10 @@ func (sh *Sheet) Cell(row, col int) *Cell {
 	return new(Cell)
 }
 
-//Set the width of a single column or multipel columns.
+//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,
@@ -97,7 +97,7 @@ func (s *Sheet) SetColWidth(startcol, endcol int, width float64) error {
 	return nil
 }
 
-// Dump sheet to it's XML representation, intended for internal use only
+// Dump sheet to its XML representation, intended for internal use only
 func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxWorksheet {
 	worksheet := newXlsxWorksheet()
 	xSheet := xlsxSheetData{}
@@ -116,6 +116,12 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xFont, xFill, xBorder, xCellStyleXf, xCellXf := style.makeXLSXStyleElements()
 				fontId := styles.addFont(xFont)
 				fillId := styles.addFill(xFill)
+
+				// HACK - adding light grey fill, as in OO and Google
+				greyfill := xlsxFill{}
+				greyfill.PatternFill.PatternType = "lightGrey"
+				styles.addFill(greyfill)
+
 				borderId := styles.addBorder(xBorder)
 				xCellStyleXf.FontId = fontId
 				xCellStyleXf.FillId = fillId
@@ -156,10 +162,28 @@ func (s *Sheet) makeXLSXSheet(refTable *RefTable, styles *xlsxStyleSheet) *xlsxW
 				xC.S = XfId
 			}
 			xRow.C = append(xRow.C, xC)
+
+			if cell.HMerge > 0 || cell.VMerge > 0 {
+				// r == rownum, c == colnum
+				mc := xlsxMergeCell{}
+				start := fmt.Sprintf("%s%d", numericToLetters(c), r+1)
+				endcol := c + cell.HMerge
+				endrow := r + cell.VMerge + 1
+				end := fmt.Sprintf("%s%d", numericToLetters(endcol), endrow)
+				mc.Ref = start + ":" + end
+				if worksheet.MergeCells == nil {
+					worksheet.MergeCells = &xlsxMergeCells{}
+				}
+				worksheet.MergeCells.Cells = append(worksheet.MergeCells.Cells, mc)
+			}
 		}
 		xSheet.Row = append(xSheet.Row, xRow)
 	}
 
+	if worksheet.MergeCells != nil {
+		worksheet.MergeCells.Count = len(worksheet.MergeCells.Cells)
+	}
+
 	worksheet.Cols = xlsxCols{Col: []xlsxCol{}}
 	for _, col := range s.Cols {
 		if col.Width == 0 {

+ 1 - 1
sheet_test.go

@@ -81,7 +81,7 @@ func (s *SheetSuite) TestMakeXLSXSheetAlsoPopulatesXLSXSTyles(c *C) {
 	c.Assert(styles.Fonts.Font[0].Sz.Val, Equals, "10")
 	c.Assert(styles.Fonts.Font[0].Name.Val, Equals, "Verdana")
 
-	c.Assert(styles.Fills.Count, Equals, 1)
+	c.Assert(styles.Fills.Count, Equals, 2)
 	c.Assert(styles.Fills.Fill[0].PatternFill.PatternType, Equals, "solid")
 	c.Assert(styles.Fills.Fill[0].PatternFill.FgColor.RGB, Equals, "FFFFFFFF")
 	c.Assert(styles.Fills.Fill[0].PatternFill.BgColor.RGB, Equals, "00000000")

+ 15 - 0
style.go

@@ -35,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 = &xlsxVal{}
+	} else {
+		xFont.B = nil
+	}
+	if style.Font.Italic {
+		xFont.I = &xlsxVal{}
+	} else {
+		xFont.I = nil
+	}
+	if style.Font.Underline {
+		xFont.U = &xlsxVal{}
+	} else {
+		xFont.U = nil
+	}
 	xPatternFill := xlsxPatternFill{}
 	xPatternFill.PatternType = style.Fill.PatternType
 	xPatternFill.FgColor.RGB = style.Fill.FgColor

+ 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")

+ 3 - 3
theme.go

@@ -22,8 +22,8 @@ func newTheme(themeXml xlsxTheme) *theme {
 		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"]}
+		clrMap["accent2"], clrMap["accent3"], clrMap["accent4"], clrMap["accent5"],
+		clrMap["accent6"], clrMap["hlink"], clrMap["folHlink"]}
 	return &theme{colors}
 }
 
@@ -39,7 +39,7 @@ func (t *theme) themeColor(index int64, tint float64) string {
 		if tint < 0 {
 			l *= (1 + tint)
 		} else {
-			l = l*(1 - tint) + (1 - (1 - tint))
+			l = l*(1-tint) + (1 - (1 - tint))
 		}
 		br, bg, bb := HSLToRGB(h, s, l)
 		return fmt.Sprintf("FF%02X%02X%02X", br, bg, bb)

+ 1 - 1
write.go

@@ -4,7 +4,7 @@ import "reflect"
 
 // Writes an array to row r. Accepts a pointer to array type 'e',
 // and writes the number of columns to write, 'cols'. If 'cols' is < 0,
-// the entire array will be written if possible. Retuens -1 if the 'e'
+// the entire array will be written if possible. Returns -1 if the 'e'
 // doesn't point to an array, otherwise the number of columns written.
 func (r *Row) WriteSlice(e interface{}, cols int) int {
 	if cols == 0 {

+ 22 - 4
xmlStyle.go

@@ -29,10 +29,10 @@ type xlsxStyleSheet struct {
 	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
 	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
 
-	theme      *theme
-	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(t *theme) *xlsxStyleSheet {
@@ -451,6 +451,15 @@ type xlsxFont struct {
 }
 
 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)
 }
 
@@ -471,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
 }

+ 21 - 3
xmlStyle_test.go

@@ -26,10 +26,13 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
 	font := xlsxFont{}
 	font.Sz.Val = "10"
 	font.Name.Val = "Andale Mono"
+	font.B = &xlsxVal{}
+	font.I = &xlsxVal{}
+	font.U = &xlsxVal{}
 	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)
@@ -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:      &xlsxVal{},
+		I:      &xlsxVal{},
+		U:      &xlsxVal{}}
 	fontB := xlsxFont{Sz: xlsxVal{Val: "11"},
 		Color:  xlsxColor{RGB: "FFFF0000"},
 		Name:   xlsxVal{Val: "Calibri"},
-		Family: xlsxVal{Val: "2"}}
+		Family: xlsxVal{Val: "2"},
+		B:      &xlsxVal{},
+		I:      &xlsxVal{},
+		U:      &xlsxVal{}}
 
 	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 = &xlsxVal{}
+	fontB.I = nil
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.I = &xlsxVal{}
+	fontB.U = nil
+	c.Assert(fontA.Equals(fontB), Equals, false)
+	fontB.U = &xlsxVal{}
 	// For sanity
 	c.Assert(fontA.Equals(fontB), Equals, true)
 }

+ 13 - 1
xmlWorksheet.go

@@ -16,6 +16,7 @@ type xlsxWorksheet struct {
 	SheetFormatPr xlsxSheetFormatPr `xml:"sheetFormatPr"`
 	Cols          xlsxCols          `xml:"cols"`
 	SheetData     xlsxSheetData     `xml:"sheetData"`
+	MergeCells    *xlsxMergeCells   `xml:"mergeCells,omitempty"`
 	PrintOptions  xlsxPrintOptions  `xml:"printOptions"`
 	PageMargins   xlsxPageMargins   `xml:"pageMargins"`
 	PageSetUp     xlsxPageSetUp     `xml:"pageSetup"`
@@ -101,7 +102,7 @@ type xlsxPageMargins struct {
 // currently I have not checked it for completeness - it does as much
 // as I need.
 type xlsxSheetFormatPr struct {
-	DefaultColWidth  float64 `xml:"defaultColWidth,attr"`
+	DefaultColWidth  float64 `xml:"defaultColWidth,attr,omitempty"`
 	DefaultRowHeight float64 `xml:"defaultRowHeight,attr"`
 }
 
@@ -227,6 +228,16 @@ type xlsxRow struct {
 	C      []xlsxC `xml:"c"`
 }
 
+type xlsxMergeCell struct {
+	Ref string `xml:"ref,attr"` // ref: horiz "A1:C1", vert "B3:B6", both  "D3:G4"
+}
+
+type xlsxMergeCells struct {
+	XMLName xml.Name        //`xml:"mergeCells,omitempty"`
+	Count   int             `xml:"count,attr,omitempty"`
+	Cells   []xlsxMergeCell `xml:"mergeCell,omitempty"`
+}
+
 // xlsxC directly maps the c element in the namespace
 // http://schemas.openxmlformats.org/sprceadsheetml/2006/main -
 // currently I have not checked it for completeness - it does as much
@@ -312,5 +323,6 @@ func newXlsxWorksheet() (worksheet *xlsxWorksheet) {
 	worksheet.HeaderFooter.OddHeader[0] = xlsxOddHeader{Content: `&C&"Times New Roman,Regular"&12&A`}
 	worksheet.HeaderFooter.OddFooter = make([]xlsxOddFooter, 1)
 	worksheet.HeaderFooter.OddFooter[0] = xlsxOddFooter{Content: `&C&"Times New Roman,Regular"&12Page &P`}
+
 	return
 }