Browse Source

Initial support for themes

Right now this is only used to color cells that use theme colors
Brian Smith 10 years ago
parent
commit
be4c70439f
9 changed files with 388 additions and 19 deletions
  1. 2 1
      file.go
  2. 145 0
      hsl.go
  3. 29 3
      lib.go
  4. 4 4
      sheet_test.go
  5. 47 0
      theme.go
  6. 83 0
      theme_test.go
  7. 17 5
      xmlStyle.go
  8. 6 6
      xmlStyle_test.go
  9. 55 0
      xmlTheme.go

+ 2 - 1
file.go

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

+ 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
+}

+ 29 - 3
lib.go

@@ -651,7 +651,7 @@ func readSharedStringsFromZipFile(f *zip.File) (*RefTable, error) {
 // readStylesFromZipFile() is an internal helper function to
 // readStylesFromZipFile() is an internal helper function to
 // extract a style table from the style.xml file within
 // extract a style table from the style.xml file within
 // the XLSX zip file.
 // the XLSX zip file.
-func readStylesFromZipFile(f *zip.File) (*xlsxStyleSheet, error) {
+func readStylesFromZipFile(f *zip.File, theme *theme) (*xlsxStyleSheet, error) {
 	var style *xlsxStyleSheet
 	var style *xlsxStyleSheet
 	var error error
 	var error error
 	var rc io.ReadCloser
 	var rc io.ReadCloser
@@ -660,7 +660,7 @@ func readStylesFromZipFile(f *zip.File) (*xlsxStyleSheet, error) {
 	if error != nil {
 	if error != nil {
 		return nil, error
 		return nil, error
 	}
 	}
-	style = newXlsxStyleSheet()
+	style = newXlsxStyleSheet(theme)
 	decoder = xml.NewDecoder(rc)
 	decoder = xml.NewDecoder(rc)
 	error = decoder.Decode(style)
 	error = decoder.Decode(style)
 	if error != nil {
 	if error != nil {
@@ -677,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
 type WorkBookRels map[string]string
 
 
 func (w *WorkBookRels) MakeXLSXWorkbookRels() xlsxWorkbookRels {
 func (w *WorkBookRels) MakeXLSXWorkbookRels() xlsxWorkbookRels {
@@ -769,6 +784,7 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 	var sheets []*Sheet
 	var sheets []*Sheet
 	var style *xlsxStyleSheet
 	var style *xlsxStyleSheet
 	var styles *zip.File
 	var styles *zip.File
+	var themeFile *zip.File
 	var v *zip.File
 	var v *zip.File
 	var workbook *zip.File
 	var workbook *zip.File
 	var workbookRels *zip.File
 	var workbookRels *zip.File
@@ -787,6 +803,8 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 			workbookRels = v
 			workbookRels = v
 		case "xl/styles.xml":
 		case "xl/styles.xml":
 			styles = v
 			styles = v
+		case "xl/theme/theme1.xml":
+			themeFile = v
 		default:
 		default:
 			if len(v.Name) > 14 {
 			if len(v.Name) > 14 {
 				if v.Name[0:13] == "xl/worksheets" {
 				if v.Name[0:13] == "xl/worksheets" {
@@ -805,8 +823,16 @@ func ReadZipReader(r *zip.Reader) (*File, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	file.referenceTable = reftable
 	file.referenceTable = reftable
+	if themeFile != nil {
+		theme, err := readThemeFromZipFile(themeFile)
+		if err != nil {
+			return nil, err
+		}
+
+		file.theme = theme
+	}
 	if styles != nil {
 	if styles != nil {
-		style, err = readStylesFromZipFile(styles)
+		style, err = readStylesFromZipFile(styles, file.theme)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}

+ 4 - 4
sheet_test.go

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

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

+ 17 - 5
xmlStyle.go

@@ -29,13 +29,15 @@ type xlsxStyleSheet struct {
 	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
 	CellXfs      xlsxCellXfs      `xml:"cellXfs,omitempty"`
 	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
 	NumFmts      xlsxNumFmts      `xml:"numFmts,omitempty"`
 
 
+	theme      *theme
 	styleCache map[int]*Style // `-`
 	styleCache map[int]*Style // `-`
 	numFmtRefTable map[int]xlsxNumFmt `xml:"-"`
 	numFmtRefTable map[int]xlsxNumFmt `xml:"-"`
 	lock       *sync.RWMutex
 	lock       *sync.RWMutex
 }
 }
 
 
-func newXlsxStyleSheet() *xlsxStyleSheet {
+func newXlsxStyleSheet(t *theme) *xlsxStyleSheet {
 	stylesheet := new(xlsxStyleSheet)
 	stylesheet := new(xlsxStyleSheet)
+	stylesheet.theme = t
 	stylesheet.styleCache = make(map[int]*Style)
 	stylesheet.styleCache = make(map[int]*Style)
 	stylesheet.lock = new(sync.RWMutex)
 	stylesheet.lock = new(sync.RWMutex)
 	return stylesheet
 	return stylesheet
@@ -91,8 +93,8 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
 		if xf.FillId > -1 && xf.FillId < styles.Fills.Count {
 			xFill := styles.Fills.Fill[xf.FillId]
 			xFill := styles.Fills.Fill[xf.FillId]
 			style.Fill.PatternType = xFill.PatternFill.PatternType
 			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 {
 		if xf.FontId > -1 && xf.FontId < styles.Fonts.Count {
@@ -101,7 +103,7 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 			style.Font.Name = xfont.Name.Val
 			style.Font.Name = xfont.Name.Val
 			style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
 			style.Font.Family, _ = strconv.Atoi(xfont.Family.Val)
 			style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
 			style.Font.Charset, _ = strconv.Atoi(xfont.Charset.Val)
-			style.Font.Color = xfont.Color.RGB
+			style.Font.Color = styles.argbValue(xfont.Color)
 
 
 			if xfont.B != nil {
 			if xfont.B != nil {
 				style.Font.Bold = true
 				style.Font.Bold = true
@@ -123,6 +125,14 @@ func (styles *xlsxStyleSheet) getStyle(styleIndex int) (style *Style) {
 	return style
 	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
 // Excel styles can reference number formats that are built-in, all of which
 // have an id less than 164. This is a possibly incomplete list comprised of as
 // have an id less than 164. This is a possibly incomplete list comprised of as
 // many of them as I could find.
 // many of them as I could find.
@@ -577,7 +587,9 @@ func (patternFill *xlsxPatternFill) Marshal() (result string, err error) {
 // currently I have not checked it for completeness - it does as much
 // currently I have not checked it for completeness - it does as much
 // as I need.
 // as I need.
 type xlsxColor struct {
 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 {
 func (color *xlsxColor) Equals(other xlsxColor) bool {

+ 6 - 6
xmlStyle_test.go

@@ -10,7 +10,7 @@ var _ = Suite(&XMLStyleSuite{})
 
 
 // Test we produce valid output for an empty style file.
 // Test we produce valid output for an empty style file.
 func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
 func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	result, err := styles.Marshal()
 	result, err := styles.Marshal()
 	c.Assert(err, IsNil)
 	c.Assert(err, IsNil)
 	c.Assert(string(result), Equals, `<?xml version="1.0" encoding="UTF-8"?>
 	c.Assert(string(result), Equals, `<?xml version="1.0" encoding="UTF-8"?>
@@ -19,7 +19,7 @@ func (x *XMLStyleSuite) TestMarshalEmptyXlsxStyleSheet(c *C) {
 
 
 // Test we produce valid output for a style file with one font definition.
 // Test we produce valid output for a style file with one font definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Fonts = xlsxFonts{}
 	styles.Fonts = xlsxFonts{}
 	styles.Fonts.Count = 1
 	styles.Fonts.Count = 1
 	styles.Fonts.Font = make([]xlsxFont, 1)
 	styles.Fonts.Font = make([]xlsxFont, 1)
@@ -37,7 +37,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFont(c *C) {
 
 
 // Test we produce valid output for a style file with one fill definition.
 // Test we produce valid output for a style file with one fill definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Fills = xlsxFills{}
 	styles.Fills = xlsxFills{}
 	styles.Fills.Count = 1
 	styles.Fills.Count = 1
 	styles.Fills.Fill = make([]xlsxFill, 1)
 	styles.Fills.Fill = make([]xlsxFill, 1)
@@ -58,7 +58,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithAFill(c *C) {
 
 
 // Test we produce valid output for a style file with one border definition.
 // Test we produce valid output for a style file with one border definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.Borders = xlsxBorders{}
 	styles.Borders = xlsxBorders{}
 	styles.Borders.Count = 1
 	styles.Borders.Count = 1
 	styles.Borders.Border = make([]xlsxBorder, 1)
 	styles.Borders.Border = make([]xlsxBorder, 1)
@@ -76,7 +76,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithABorder(c *C) {
 
 
 // Test we produce valid output for a style file with one cellStyleXf definition.
 // Test we produce valid output for a style file with one cellStyleXf definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.CellStyleXfs = xlsxCellStyleXfs{}
 	styles.CellStyleXfs = xlsxCellStyleXfs{}
 	styles.CellStyleXfs.Count = 1
 	styles.CellStyleXfs.Count = 1
 	styles.CellStyleXfs.Xf = make([]xlsxXf, 1)
 	styles.CellStyleXfs.Xf = make([]xlsxXf, 1)
@@ -109,7 +109,7 @@ func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellStyleXf(c *C) {
 // Test we produce valid output for a style file with one cellXf
 // Test we produce valid output for a style file with one cellXf
 // definition.
 // definition.
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellXf(c *C) {
 func (x *XMLStyleSuite) TestMarshalXlsxStyleSheetWithACellXf(c *C) {
-	styles := newXlsxStyleSheet()
+	styles := newXlsxStyleSheet(nil)
 	styles.CellXfs = xlsxCellXfs{}
 	styles.CellXfs = xlsxCellXfs{}
 	styles.CellXfs.Count = 1
 	styles.CellXfs.Count = 1
 	styles.CellXfs.Xf = make([]xlsxXf, 1)
 	styles.CellXfs.Xf = make([]xlsxXf, 1)

+ 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"`
+}