diff --git a/cell.go b/cell.go
index 4635ce18..0151865c 100644
--- a/cell.go
+++ b/cell.go
@@ -60,6 +60,7 @@ func (ct *CellType) fallbackTo(cellData string, fallback CellType) CellType {
type Cell struct {
Row *Row
Value string
+ RichText []RichTextRun
formula string
style *Style
NumFmt string
@@ -155,6 +156,15 @@ func (c *Cell) Type() CellType {
// SetString sets the value of a cell to a string.
func (c *Cell) SetString(s string) {
c.Value = s
+ c.RichText = nil
+ c.formula = ""
+ c.cellType = CellTypeString
+}
+
+// SetRichText sets the value of a cell to a set of the rich text.
+func (c *Cell) SetRichText(r []RichTextRun) {
+ c.Value = ""
+ c.RichText = append([]RichTextRun(nil), r...)
c.formula = ""
c.cellType = CellTypeString
}
diff --git a/format_code.go b/format_code.go
index 2c7206b6..3952b397 100644
--- a/format_code.go
+++ b/format_code.go
@@ -85,13 +85,19 @@ func (fullFormat *parsedNumberFormat) FormatValue(cell *Cell) (string, error) {
case CellTypeInline:
fallthrough
case CellTypeStringFormula:
+ var cellValue string
+ if len(cell.RichText) > 0 {
+ cellValue = richTextToPlainText(cell.RichText)
+ } else {
+ cellValue = cell.Value
+ }
textFormat := cell.parsedNumFmt.textFormat
// This switch statement is only for String formats
switch textFormat.reducedFormatString {
case builtInNumFmt[builtInNumFmtIndex_GENERAL]: // General is literally "general"
- return cell.Value, nil
+ return cellValue, nil
case builtInNumFmt[builtInNumFmtIndex_STRING]: // String is "@"
- return textFormat.prefix + cell.Value + textFormat.suffix, nil
+ return textFormat.prefix + cellValue + textFormat.suffix, nil
case "":
// If cell is not "General" and there is not an "@" symbol in the format, then the cell's value is not
// used when determining what to display. It would be completely legal to have a format of "Error"
@@ -99,7 +105,7 @@ func (fullFormat *parsedNumberFormat) FormatValue(cell *Cell) (string, error) {
// have a prefix of "Error" and a reduced format string of "" (empty string).
return textFormat.prefix + textFormat.suffix, nil
default:
- return cell.Value, errors.New("invalid or unsupported format, unsupported string format")
+ return cellValue, errors.New("invalid or unsupported format, unsupported string format")
}
case CellTypeDate:
// These are dates that are stored in date format instead of being stored as numbers with a format to turn them
diff --git a/lib.go b/lib.go
index 96e7a062..f8cfa4d3 100644
--- a/lib.go
+++ b/lib.go
@@ -464,7 +464,7 @@ func fillCellData(rawCell xlsxC, refTable *RefTable, sharedFormulas map[int]shar
if err != nil {
panic(err)
}
- cell.Value = refTable.ResolveSharedString(ref)
+ cell.Value, cell.RichText = refTable.ResolveSharedString(ref)
}
case "inlineStr":
cell.cellType = CellTypeInline
@@ -496,13 +496,12 @@ func fillCellData(rawCell xlsxC, refTable *RefTable, sharedFormulas map[int]shar
// fillCellDataFromInlineString attempts to get inline string data and put it into a Cell.
func fillCellDataFromInlineString(rawcell xlsxC, cell *Cell) {
cell.Value = ""
+ cell.RichText = nil
if rawcell.Is != nil {
- if rawcell.Is.T != "" {
- cell.Value = strings.Trim(rawcell.Is.T, " \t\n\r")
+ if rawcell.Is.T != nil {
+ cell.Value = strings.Trim(rawcell.Is.T.getText(), " \t\n\r")
} else {
- for _, r := range rawcell.Is.R {
- cell.Value += r.T
- }
+ cell.RichText = xmlToRichText(rawcell.Is.R)
}
}
}
diff --git a/reftable.go b/reftable.go
index f4f6bc03..fb711b26 100644
--- a/reftable.go
+++ b/reftable.go
@@ -1,19 +1,27 @@
package xlsx
+type plainTextOrRichText struct {
+ plainText string
+ isRichText bool
+ richText []RichTextRun
+}
+
type RefTable struct {
- indexedStrings []string
+ indexedStrings []plainTextOrRichText
knownStrings map[string]int
+ knownRichTexts map[string][]int
isWrite bool
}
-// NewSharedStringRefTable() creates a new, empty RefTable.
+// NewSharedStringRefTable creates a new, empty RefTable.
func NewSharedStringRefTable() *RefTable {
rt := RefTable{}
rt.knownStrings = make(map[string]int)
+ rt.knownRichTexts = make(map[string][]int)
return &rt
}
-// MakeSharedStringRefTable() takes an xlsxSST struct and converts
+// MakeSharedStringRefTable takes an xlsxSST struct and converts
// it's contents to an slice of strings used to refer to string values
// by numeric index - this is the model used within XLSX worksheet (a
// numeric reference is stored to a shared cell value).
@@ -22,19 +30,16 @@ func MakeSharedStringRefTable(source *xlsxSST) *RefTable {
reftable.isWrite = false
for _, si := range source.SI {
if len(si.R) > 0 {
- newString := ""
- for j := 0; j < len(si.R); j++ {
- newString = newString + si.R[j].T
- }
- reftable.AddString(newString)
+ richText := xmlToRichText(si.R)
+ reftable.AddRichText(richText)
} else {
- reftable.AddString(si.T)
+ reftable.AddString(si.T.getText())
}
}
return reftable
}
-// makeXlsxSST() takes a RefTable and returns and
+// makeXlsxSST takes a RefTable and returns and
// equivalent xlsxSST representation.
func (rt *RefTable) makeXLSXSST() xlsxSST {
sst := xlsxSST{}
@@ -42,18 +47,28 @@ func (rt *RefTable) makeXLSXSST() xlsxSST {
sst.UniqueCount = sst.Count
for _, ref := range rt.indexedStrings {
si := xlsxSI{}
- si.T = ref
+ if ref.isRichText {
+ si.R = richTextToXml(ref.richText)
+ } else {
+ si.T = &xlsxT{Text: ref.plainText}
+ }
sst.SI = append(sst.SI, si)
}
return sst
}
-// Resolvesharedstring() looks up a string value by numeric index from
-// a provided reference table (just a slice of strings in the correct
-// order). This function only exists to provide clarity of purpose
-// via it's name.
-func (rt *RefTable) ResolveSharedString(index int) string {
- return rt.indexedStrings[index]
+// ResolveSharedString looks up a string value or the rich text by numeric index from
+// a provided reference table (just a slice of strings in the correct order).
+// If the rich text was found, non-empty slice will be returned in richText.
+// This function only exists to provide clarity of purpose via it's name.
+func (rt *RefTable) ResolveSharedString(index int) (plainText string, richText []RichTextRun) {
+ ptrt := rt.indexedStrings[index]
+ if ptrt.isRichText {
+ richText = ptrt.richText
+ } else {
+ plainText = ptrt.plainText
+ }
+ return
}
// AddString adds a string to the reference table and return it's
@@ -66,12 +81,49 @@ func (rt *RefTable) AddString(str string) int {
return index
}
}
- rt.indexedStrings = append(rt.indexedStrings, str)
+ ptrt := plainTextOrRichText{plainText: str, isRichText: false}
+ rt.indexedStrings = append(rt.indexedStrings, ptrt)
index := len(rt.indexedStrings) - 1
rt.knownStrings[str] = index
return index
}
+// AddRichText adds a set of rich text to the reference table and return it's
+// numeric index. If a set of rich text already exists then it simply returns
+// the existing index.
+func (rt *RefTable) AddRichText(r []RichTextRun) int {
+ plain := richTextToPlainText(r)
+ if rt.isWrite {
+ indices, ok := rt.knownRichTexts[plain]
+ if ok {
+ for _, index := range indices {
+ if areRichTextsEqual(rt.indexedStrings[index].richText, r) {
+ return index
+ }
+ }
+ }
+ }
+ ptrt := plainTextOrRichText{isRichText: true}
+ ptrt.richText = append(ptrt.richText, r...)
+ rt.indexedStrings = append(rt.indexedStrings, ptrt)
+ index := len(rt.indexedStrings) - 1
+ rt.knownRichTexts[plain] = append(rt.knownRichTexts[plain], index)
+ return index
+}
+
+func areRichTextsEqual(r1 []RichTextRun, r2 []RichTextRun) bool {
+ if len(r1) != len(r2) {
+ return false
+ }
+ for i, rt1 := range r1 {
+ rt2 := r2[i]
+ if !rt1.Equals(&rt2) {
+ return false
+ }
+ }
+ return true
+}
+
func (rt *RefTable) Length() int {
return len(rt.indexedStrings)
}
diff --git a/reftable_test.go b/reftable_test.go
index cde4d39d..d1cda087 100644
--- a/reftable_test.go
+++ b/reftable_test.go
@@ -26,12 +26,29 @@ func (s *RefTableSuite) SetUpTest(c *C) {
Bar
- Baz
+ Baz
+
Quuk
- `)
+
+
+
+
+
+
+ Text1
+
+
+
+
+
+
+ Text2
+
+
+ `)
}
// We can add a new string to the RefTable
@@ -39,15 +56,21 @@ func (s *RefTableSuite) TestRefTableAddString(c *C) {
refTable := NewSharedStringRefTable()
index := refTable.AddString("Foo")
c.Assert(index, Equals, 0)
- c.Assert(refTable.ResolveSharedString(0), Equals, "Foo")
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
}
func (s *RefTableSuite) TestCreateNewSharedStringRefTable(c *C) {
refTable := NewSharedStringRefTable()
refTable.AddString("Foo")
refTable.AddString("Bar")
- c.Assert(refTable.ResolveSharedString(0), Equals, "Foo")
- c.Assert(refTable.ResolveSharedString(1), Equals, "Bar")
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
+ p, r = refTable.ResolveSharedString(1)
+ c.Assert(p, Equals, "Bar")
+ c.Assert(r, IsNil)
}
// Test we can correctly convert a xlsxSST into a reference table
@@ -57,9 +80,26 @@ func (s *RefTableSuite) TestMakeSharedStringRefTable(c *C) {
err := xml.NewDecoder(s.SharedStringsXML).Decode(sst)
c.Assert(err, IsNil)
reftable := MakeSharedStringRefTable(sst)
- c.Assert(reftable.Length(), Equals, 4)
- c.Assert(reftable.ResolveSharedString(0), Equals, "Foo")
- c.Assert(reftable.ResolveSharedString(1), Equals, "Bar")
+ c.Assert(reftable.Length(), Equals, 5)
+ p, r := reftable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
+ p, r = reftable.ResolveSharedString(1)
+ c.Assert(p, Equals, "Bar")
+ c.Assert(r, IsNil)
+ p, r = reftable.ResolveSharedString(2)
+ c.Assert(p, Equals, "Baz \n")
+ c.Assert(r, IsNil)
+ p, r = reftable.ResolveSharedString(3)
+ c.Assert(p, Equals, "Quuk")
+ c.Assert(r, IsNil)
+ p, r = reftable.ResolveSharedString(4)
+ c.Assert(p, Equals, "")
+ c.Assert(r, HasLen, 2)
+ c.Assert(r[0].Font.Size, Equals, 11.5)
+ c.Assert(r[0].Font.Name, Equals, "Font1")
+ c.Assert(r[1].Font.Size, Equals, 12.5)
+ c.Assert(r[1].Font.Name, Equals, "Font2")
}
// Test we can correctly resolve a numeric reference in the reference
@@ -69,7 +109,9 @@ func (s *RefTableSuite) TestResolveSharedString(c *C) {
err := xml.NewDecoder(s.SharedStringsXML).Decode(sst)
c.Assert(err, IsNil)
reftable := MakeSharedStringRefTable(sst)
- c.Assert(reftable.ResolveSharedString(0), Equals, "Foo")
+ p, r := reftable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
}
// Test we can correctly create the xlsx.xlsxSST struct from a RefTable
@@ -77,18 +119,52 @@ func (s *RefTableSuite) TestMakeXLSXSST(c *C) {
refTable := NewSharedStringRefTable()
refTable.AddString("Foo")
refTable.AddString("Bar")
+ refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ RichTextRun{
+ Text: "Text2",
+ },
+ })
sst := refTable.makeXLSXSST()
c.Assert(sst, NotNil)
- c.Assert(sst.Count, Equals, 2)
- c.Assert(sst.UniqueCount, Equals, 2)
- c.Assert(sst.SI, HasLen, 2)
+ c.Assert(sst.Count, Equals, 3)
+ c.Assert(sst.UniqueCount, Equals, 3)
+ c.Assert(sst.SI, HasLen, 3)
si := sst.SI[0]
- c.Assert(si.T, Equals, "Foo")
+ c.Assert(si.T.Text, Equals, "Foo")
+ c.Assert(si.R, IsNil)
+ si = sst.SI[2]
+ c.Assert(si.T, IsNil)
+ c.Assert(si.R, HasLen, 2)
+ c.Assert(si.R[0].RPr.B, NotNil)
+ c.Assert(si.R[0].T.Text, Equals, "Text1")
+ c.Assert(si.R[1].RPr, IsNil)
+ c.Assert(si.R[1].T.Text, Equals, "Text2")
}
func (s *RefTableSuite) TestMarshalSST(c *C) {
refTable := NewSharedStringRefTable()
refTable.AddString("Foo")
+ refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ RichTextRun{
+ Text: "Text2",
+ },
+ })
sst := refTable.makeXLSXSST()
output := bytes.NewBufferString(xml.Header)
@@ -99,7 +175,7 @@ func (s *RefTableSuite) TestMarshalSST(c *C) {
c.Assert(err, IsNil)
expectedXLSXSST := `
-Foo`
+FooText1Text2`
c.Assert(output.String(), Equals, expectedXLSXSST)
}
@@ -110,8 +186,12 @@ func (s *RefTableSuite) TestRefTableReadAddString(c *C) {
index2 := refTable.AddString("Foo")
c.Assert(index1, Equals, 0)
c.Assert(index2, Equals, 1)
- c.Assert(refTable.ResolveSharedString(0), Equals, "Foo")
- c.Assert(refTable.ResolveSharedString(1), Equals, "Foo")
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
+ p, r = refTable.ResolveSharedString(1)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
}
func (s *RefTableSuite) TestRefTableWriteAddString(c *C) {
@@ -121,5 +201,78 @@ func (s *RefTableSuite) TestRefTableWriteAddString(c *C) {
index2 := refTable.AddString("Foo")
c.Assert(index1, Equals, 0)
c.Assert(index2, Equals, 0)
- c.Assert(refTable.ResolveSharedString(0), Equals, "Foo")
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "Foo")
+ c.Assert(r, IsNil)
+}
+
+func (s *RefTableSuite) TestRefTableReadAddRichText(c *C) {
+ refTable := NewSharedStringRefTable()
+ refTable.isWrite = false
+ index1 := refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ })
+ index2 := refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ })
+
+ c.Assert(index1, Equals, 0)
+ c.Assert(index2, Equals, 1)
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "")
+ c.Assert(r, HasLen, 1)
+ c.Assert(r[0].Font.Bold, NotNil)
+ c.Assert(r[0].Text, Equals, "Text1")
+ p, r = refTable.ResolveSharedString(1)
+ c.Assert(p, Equals, "")
+ c.Assert(r, HasLen, 1)
+ c.Assert(r[0].Font.Bold, NotNil)
+ c.Assert(r[0].Text, Equals, "Text1")
+}
+
+func (s *RefTableSuite) TestRefTableWriteAddRichText(c *C) {
+ refTable := NewSharedStringRefTable()
+ refTable.isWrite = true
+ index1 := refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ })
+ index2 := refTable.AddRichText([]RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Bold: true,
+ },
+ Text: "Text1",
+ },
+ })
+
+ c.Assert(index1, Equals, 0)
+ c.Assert(index2, Equals, 0)
+ p, r := refTable.ResolveSharedString(0)
+ c.Assert(p, Equals, "")
+ c.Assert(r, HasLen, 1)
+ c.Assert(r[0].Font.Bold, NotNil)
+ c.Assert(r[0].Text, Equals, "Text1")
}
diff --git a/richtext.go b/richtext.go
new file mode 100644
index 00000000..95c06049
--- /dev/null
+++ b/richtext.go
@@ -0,0 +1,207 @@
+package xlsx
+
+import (
+ "fmt"
+ "reflect"
+)
+
+type RichTextFontFamily int
+type RichTextCharset int
+type RichTextVertAlign string
+type RichTextUnderline string
+
+const (
+ // RichTextFontFamilyUnspecified indicates that the font family was not specified
+ RichTextFontFamilyUnspecified RichTextFontFamily = -1
+ RichTextFontFamilyNotApplicable RichTextFontFamily = 0
+ RichTextFontFamilyRoman RichTextFontFamily = 1
+ RichTextFontFamilySwiss RichTextFontFamily = 2
+ RichTextFontFamilyModern RichTextFontFamily = 3
+ RichTextFontFamilyScript RichTextFontFamily = 4
+ RichTextFontFamilyDecorative RichTextFontFamily = 5
+
+ // RichTextCharsetUnspecified indicates that the font charset was not specified
+ RichTextCharsetUnspecified RichTextCharset = -1
+ RichTextCharsetANSI RichTextCharset = 0
+ RichTextCharsetDefault RichTextCharset = 1
+ RichTextCharsetSymbol RichTextCharset = 2
+ RichTextCharsetMac RichTextCharset = 77
+ RichTextCharsetShiftJIS RichTextCharset = 128
+ RichTextCharsetHangul RichTextCharset = 129
+ RichTextCharsetJohab RichTextCharset = 130
+ RichTextCharsetGB2312 RichTextCharset = 134
+ RichTextCharsetBIG5 RichTextCharset = 136
+ RichTextCharsetGreek RichTextCharset = 161
+ RichTextCharsetTurkish RichTextCharset = 162
+ RichTextCharsetVietnamese RichTextCharset = 163
+ RichTextCharsetHebrew RichTextCharset = 177
+ RichTextCharsetArabic RichTextCharset = 178
+ RichTextCharsetBaltic RichTextCharset = 186
+ RichTextCharsetRussian RichTextCharset = 204
+ RichTextCharsetThai RichTextCharset = 222
+ RichTextCharsetEastEurope RichTextCharset = 238
+ RichTextCharsetOEM RichTextCharset = 255
+
+ RichTextVertAlignSuperscript RichTextVertAlign = "superscript"
+ RichTextVertAlignSubscript RichTextVertAlign = "subscript"
+
+ RichTextUnderlineSingle RichTextUnderline = "single"
+ RichTextUnderlineDouble RichTextUnderline = "double"
+
+ // These underline styles doesn't work on the RichTextRun,
+ // and should be set as a part of cell style.
+ // "singleAccounting"
+ // "doubleAccounting"
+)
+
+// RichTextColor is the color of the RichTextRun.
+type RichTextColor struct {
+ coreColor xlsxColor
+}
+
+// NewRichTextColorFromARGB creates a new RichTextColor from ARGB component values.
+// Each component must have a value in range of 0 to 255.
+func NewRichTextColorFromARGB(alpha, red, green, blue int) *RichTextColor {
+ argb := fmt.Sprintf("%02X%02X%02X%02X", alpha, red, green, blue)
+ return &RichTextColor{coreColor: xlsxColor{RGB: argb}}
+}
+
+// NewRichTextColorFromThemeColor creates a new RichTextColor from the theme color.
+// The argument `themeColor` is a zero-based index of the theme color.
+func NewRichTextColorFromThemeColor(themeColor int) *RichTextColor {
+ return &RichTextColor{coreColor: xlsxColor{Theme: &themeColor}}
+}
+
+// RichTextFont is the font spec of the RichTextRun.
+type RichTextFont struct {
+ // Name is the font name. If Name is empty, Size, Family and Charset will be ignored.
+ Name string
+ // Size is the font size.
+ Size float64
+ // Family is a value of the font family. Use one of the RichTextFontFamily constants.
+ Family RichTextFontFamily
+ // Charset is a value of the charset of the font. Use one of the RichTextCharset constants.
+ Charset RichTextCharset
+ // Color is the text color.
+ Color *RichTextColor
+ // Bold specifies the bold face font style.
+ Bold bool
+ // Italic specifies the italic font style.
+ Italic bool
+ // Strike specifies a strikethrough line.
+ Strike bool
+ // VertAlign specifies the vertical position of the text. Use one of the RichTextVertAlign constants, or empty.
+ VertAlign RichTextVertAlign
+ // Underline specifies the underline style. Use one of the RichTextUnderline constants, or empty.
+ Underline RichTextUnderline
+}
+
+// RichTextRun is a run of the decorated text.
+type RichTextRun struct {
+ Font *RichTextFont
+ Text string
+}
+
+func (rt *RichTextRun) Equals(other *RichTextRun) bool {
+ return reflect.DeepEqual(rt, other)
+}
+
+func richTextToXml(r []RichTextRun) []xlsxR {
+ var xrs []xlsxR
+ for _, rt := range r {
+ xr := xlsxR{}
+ xr.T = xlsxT{Text: rt.Text}
+ if rt.Font != nil {
+ rpr := xlsxRunProperties{}
+ if len(rt.Font.Name) > 0 {
+ rpr.RFont = &xlsxVal{Val: rt.Font.Name}
+ }
+ if rt.Font.Size > 0.0 {
+ rpr.Sz = &xlsxFloatVal{Val: rt.Font.Size}
+ }
+ if rt.Font.Family != RichTextFontFamilyUnspecified {
+ rpr.Family = &xlsxIntVal{Val: int(rt.Font.Family)}
+ }
+ if rt.Font.Charset != RichTextCharsetUnspecified {
+ rpr.Charset = &xlsxIntVal{Val: int(rt.Font.Charset)}
+ }
+ if rt.Font.Color != nil {
+ xcolor := rt.Font.Color.coreColor
+ rpr.Color = &xcolor
+ }
+ if rt.Font.Bold {
+ rpr.B.Val = true
+ }
+ if rt.Font.Italic {
+ rpr.I.Val = true
+ }
+ if rt.Font.Strike {
+ rpr.Strike.Val = true
+ }
+ if len(rt.Font.VertAlign) > 0 {
+ rpr.VertAlign = &xlsxVal{Val: string(rt.Font.VertAlign)}
+ }
+ if len(rt.Font.Underline) > 0 {
+ rpr.U = &xlsxVal{Val: string(rt.Font.Underline)}
+ }
+ xr.RPr = &rpr
+ }
+ xrs = append(xrs, xr)
+ }
+ return xrs
+}
+
+func xmlToRichText(r []xlsxR) []RichTextRun {
+ richiText := []RichTextRun(nil)
+ for _, rr := range r {
+ rtr := RichTextRun{Text: rr.T.Text}
+ rpr := rr.RPr
+ if rpr != nil {
+ rtr.Font = &RichTextFont{}
+ if rpr.RFont != nil {
+ rtr.Font.Name = rpr.RFont.Val
+ }
+ if rpr.Sz != nil {
+ rtr.Font.Size = rpr.Sz.Val
+ }
+ if rpr.Family != nil {
+ rtr.Font.Family = RichTextFontFamily(rpr.Family.Val)
+ } else {
+ rtr.Font.Family = RichTextFontFamilyUnspecified
+ }
+ if rpr.Charset != nil {
+ rtr.Font.Charset = RichTextCharset(rpr.Charset.Val)
+ } else {
+ rtr.Font.Charset = RichTextCharsetUnspecified
+ }
+ if rpr.Color != nil {
+ rtr.Font.Color = &RichTextColor{coreColor: *rpr.Color}
+ }
+ if rpr.B.Val {
+ rtr.Font.Bold = true
+ }
+ if rpr.I.Val {
+ rtr.Font.Italic = true
+ }
+ if rpr.Strike.Val {
+ rtr.Font.Strike = true
+ }
+ if rpr.VertAlign != nil {
+ rtr.Font.VertAlign = RichTextVertAlign(rpr.VertAlign.Val)
+ }
+ if rpr.U != nil {
+ rtr.Font.Underline = RichTextUnderline(rpr.U.Val)
+ }
+ }
+ richiText = append(richiText, rtr)
+ }
+ return richiText
+}
+
+func richTextToPlainText(richText []RichTextRun) string {
+ var s string
+ for _, r := range richText {
+ s += r.Text
+ }
+ return s
+}
diff --git a/richtext_test.go b/richtext_test.go
new file mode 100644
index 00000000..977f7ab9
--- /dev/null
+++ b/richtext_test.go
@@ -0,0 +1,388 @@
+package xlsx
+
+import (
+ . "gopkg.in/check.v1"
+)
+
+type RichTextSuite struct{}
+
+var _ = Suite(&RichTextSuite{})
+
+func (s *RichTextSuite) TestNewRichTextColorFromARGB(c *C) {
+ rtColor := NewRichTextColorFromARGB(127, 128, 129, 130)
+ c.Assert(rtColor.coreColor.RGB, Equals, "7F808182")
+}
+
+func (s *RichTextSuite) TestNewRichTextColorFromThemeColor(c *C) {
+ rtColor := NewRichTextColorFromThemeColor(123)
+ c.Assert(*rtColor.coreColor.Theme, Equals, 123)
+}
+
+func (s *RichTextSuite) TestRichTextRunEquals(c *C) {
+ r1color := 1
+ r1 := &RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Color: &RichTextColor{coreColor: xlsxColor{Theme: &r1color}},
+ Bold: true,
+ Italic: true,
+ },
+ Text: "X",
+ }
+
+ r2color := r1color
+ r2 := &RichTextRun{ // same with r1
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Color: &RichTextColor{coreColor: xlsxColor{Theme: &r2color}},
+ Bold: true,
+ Italic: true,
+ },
+ Text: "X",
+ }
+
+ r3color := r1color
+ r3 := &RichTextRun{ // different font setting from r1
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Color: &RichTextColor{coreColor: xlsxColor{Theme: &r3color}},
+ Bold: true,
+ Italic: false,
+ },
+ Text: "X",
+ }
+
+ r4color := 2
+ r4 := &RichTextRun{ // different color setting from r1
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Color: &RichTextColor{coreColor: xlsxColor{Theme: &r4color}},
+ Bold: true,
+ Italic: true,
+ },
+ Text: "X",
+ }
+
+ r5 := &RichTextRun{ // no font setting
+ Text: "X",
+ }
+
+ r6color := r1color
+ r6 := &RichTextRun{ // different text from r1
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Color: &RichTextColor{coreColor: xlsxColor{Theme: &r6color}},
+ Bold: true,
+ Italic: true,
+ },
+ Text: "Y",
+ }
+
+ var r7 *RichTextRun = nil
+
+ c.Assert(r1.Equals(r2), Equals, true)
+ c.Assert(r1.Equals(r3), Equals, false)
+ c.Assert(r1.Equals(r4), Equals, false)
+ c.Assert(r1.Equals(r5), Equals, false)
+ c.Assert(r1.Equals(r6), Equals, false)
+ c.Assert(r1.Equals(r7), Equals, false)
+
+ c.Assert(r2.Equals(r1), Equals, true)
+ c.Assert(r3.Equals(r1), Equals, false)
+ c.Assert(r4.Equals(r1), Equals, false)
+ c.Assert(r5.Equals(r1), Equals, false)
+ c.Assert(r6.Equals(r1), Equals, false)
+ c.Assert(r7.Equals(r1), Equals, false)
+
+ c.Assert(r7.Equals(nil), Equals, true)
+}
+
+func (s *RichTextSuite) TestRichTextToXml(c *C) {
+ rtr := []RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Name: "Font",
+ Size: 12.345,
+ Family: RichTextFontFamilyScript,
+ Charset: RichTextCharsetHebrew,
+ Color: &RichTextColor{coreColor: xlsxColor{RGB: "DEADBEEF"}},
+ Bold: true,
+ Italic: false,
+ Strike: false,
+ VertAlign: RichTextVertAlignSuperscript,
+ Underline: RichTextUnderlineSingle,
+ },
+ Text: "Bold",
+ },
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Italic: true,
+ },
+ Text: "Italic",
+ },
+ RichTextRun{
+ Font: &RichTextFont{
+ Family: RichTextFontFamilyUnspecified,
+ Charset: RichTextCharsetUnspecified,
+ Strike: true,
+ },
+ Text: "Strike",
+ },
+ RichTextRun{
+ Font: &RichTextFont{},
+ Text: "Empty",
+ },
+ RichTextRun{
+ Text: "No Font",
+ },
+ }
+
+ xmlr := richTextToXml(rtr)
+ c.Assert(xmlr, HasLen, 5)
+
+ r := xmlr[0]
+ c.Assert(r.RPr.RFont.Val, Equals, "Font")
+ c.Assert(r.RPr.Charset.Val, Equals, int(RichTextCharsetHebrew))
+ c.Assert(r.RPr.Family.Val, Equals, int(RichTextFontFamilyScript))
+ c.Assert(r.RPr.B.Val, Equals, true)
+ c.Assert(r.RPr.I.Val, Equals, false)
+ c.Assert(r.RPr.Strike.Val, Equals, false)
+ c.Assert(r.RPr.Outline.Val, Equals, false)
+ c.Assert(r.RPr.Shadow.Val, Equals, false)
+ c.Assert(r.RPr.Condense.Val, Equals, false)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ c.Assert(r.RPr.Color.RGB, Equals, "DEADBEEF")
+ c.Assert(r.RPr.Sz.Val, Equals, 12.345)
+ c.Assert(r.RPr.U.Val, Equals, string(RichTextUnderlineSingle))
+ c.Assert(r.RPr.VertAlign.Val, Equals, string(RichTextVertAlignSuperscript))
+ c.Assert(r.RPr.Scheme, IsNil)
+ c.Assert(r.T.Text, Equals, "Bold")
+
+ r = xmlr[1]
+ c.Assert(r.RPr.RFont, IsNil)
+ c.Assert(r.RPr.Charset, IsNil)
+ c.Assert(r.RPr.Family, IsNil)
+ c.Assert(r.RPr.B.Val, Equals, false)
+ c.Assert(r.RPr.I.Val, Equals, true)
+ c.Assert(r.RPr.Strike.Val, Equals, false)
+ c.Assert(r.RPr.Outline.Val, Equals, false)
+ c.Assert(r.RPr.Shadow.Val, Equals, false)
+ c.Assert(r.RPr.Condense.Val, Equals, false)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ c.Assert(r.RPr.Color, IsNil)
+ c.Assert(r.RPr.Sz, IsNil)
+ c.Assert(r.RPr.U, IsNil)
+ c.Assert(r.RPr.VertAlign, IsNil)
+ c.Assert(r.RPr.Scheme, IsNil)
+ c.Assert(r.T.Text, Equals, "Italic")
+
+ r = xmlr[2]
+ c.Assert(r.RPr.RFont, IsNil)
+ c.Assert(r.RPr.Charset, IsNil)
+ c.Assert(r.RPr.Family, IsNil)
+ c.Assert(r.RPr.B.Val, Equals, false)
+ c.Assert(r.RPr.I.Val, Equals, false)
+ c.Assert(r.RPr.Strike.Val, Equals, true)
+ c.Assert(r.RPr.Outline.Val, Equals, false)
+ c.Assert(r.RPr.Shadow.Val, Equals, false)
+ c.Assert(r.RPr.Condense.Val, Equals, false)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ c.Assert(r.RPr.Color, IsNil)
+ c.Assert(r.RPr.Sz, IsNil)
+ c.Assert(r.RPr.U, IsNil)
+ c.Assert(r.RPr.VertAlign, IsNil)
+ c.Assert(r.RPr.Scheme, IsNil)
+ c.Assert(r.T.Text, Equals, "Strike")
+
+ r = xmlr[3]
+ c.Assert(r.RPr.RFont, IsNil)
+ c.Assert(r.RPr.Charset.Val, Equals, int(RichTextCharsetANSI))
+ c.Assert(r.RPr.Family.Val, Equals, int(RichTextFontFamilyNotApplicable))
+ c.Assert(r.RPr.B.Val, Equals, false)
+ c.Assert(r.RPr.I.Val, Equals, false)
+ c.Assert(r.RPr.Strike.Val, Equals, false)
+ c.Assert(r.RPr.Outline.Val, Equals, false)
+ c.Assert(r.RPr.Shadow.Val, Equals, false)
+ c.Assert(r.RPr.Condense.Val, Equals, false)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ c.Assert(r.RPr.Color, IsNil)
+ c.Assert(r.RPr.Sz, IsNil)
+ c.Assert(r.RPr.U, IsNil)
+ c.Assert(r.RPr.VertAlign, IsNil)
+ c.Assert(r.RPr.Scheme, IsNil)
+ c.Assert(r.T.Text, Equals, "Empty")
+
+ r = xmlr[4]
+ c.Assert(r.RPr, IsNil)
+ c.Assert(r.T.Text, Equals, "No Font")
+}
+
+func (s *RichTextSuite) TestXmlToRichText(c *C) {
+ xmlr := []xlsxR{
+ xlsxR{
+ RPr: &xlsxRunProperties{
+ RFont: &xlsxVal{Val: "Font"},
+ Charset: &xlsxIntVal{Val: int(RichTextCharsetGreek)},
+ Family: &xlsxIntVal{Val: int(RichTextFontFamilySwiss)},
+ B: xlsxBoolProp{Val: true},
+ I: xlsxBoolProp{Val: false},
+ Strike: xlsxBoolProp{Val: false},
+ Outline: xlsxBoolProp{Val: false},
+ Shadow: xlsxBoolProp{Val: false},
+ Condense: xlsxBoolProp{Val: false},
+ Extend: xlsxBoolProp{Val: false},
+ Color: &xlsxColor{RGB: "DEADBEEF"},
+ Sz: &xlsxFloatVal{Val: 12.345},
+ U: &xlsxVal{Val: string(RichTextUnderlineDouble)},
+ VertAlign: &xlsxVal{Val: string(RichTextVertAlignSuperscript)},
+ Scheme: nil,
+ },
+ T: xlsxT{Text: "Bold"},
+ },
+ xlsxR{
+ RPr: &xlsxRunProperties{
+ RFont: nil,
+ Charset: nil,
+ Family: nil,
+ B: xlsxBoolProp{Val: false},
+ I: xlsxBoolProp{Val: true},
+ Strike: xlsxBoolProp{Val: false},
+ Outline: xlsxBoolProp{Val: false},
+ Shadow: xlsxBoolProp{Val: false},
+ Condense: xlsxBoolProp{Val: false},
+ Extend: xlsxBoolProp{Val: false},
+ Color: nil,
+ Sz: nil,
+ U: nil,
+ VertAlign: nil,
+ Scheme: nil,
+ },
+ T: xlsxT{Text: "Italic"},
+ },
+ xlsxR{
+ RPr: &xlsxRunProperties{
+ RFont: nil,
+ Charset: nil,
+ Family: nil,
+ B: xlsxBoolProp{Val: false},
+ I: xlsxBoolProp{Val: false},
+ Strike: xlsxBoolProp{Val: true},
+ Outline: xlsxBoolProp{Val: false},
+ Shadow: xlsxBoolProp{Val: false},
+ Condense: xlsxBoolProp{Val: false},
+ Extend: xlsxBoolProp{Val: false},
+ Color: nil,
+ Sz: nil,
+ U: nil,
+ VertAlign: nil,
+ Scheme: nil,
+ },
+ T: xlsxT{Text: "Strike"},
+ },
+ xlsxR{
+ RPr: &xlsxRunProperties{},
+ T: xlsxT{Text: "Empty"},
+ },
+ xlsxR{
+ RPr: nil,
+ T: xlsxT{Text: "No Font"},
+ },
+ }
+
+ rtr := xmlToRichText(xmlr)
+ c.Assert(rtr, HasLen, 5)
+
+ r := rtr[0]
+ c.Assert(r.Font.Name, Equals, "Font")
+ c.Assert(r.Font.Size, Equals, 12.345)
+ c.Assert(r.Font.Family, Equals, RichTextFontFamilySwiss)
+ c.Assert(r.Font.Charset, Equals, RichTextCharsetGreek)
+ c.Assert(r.Font.Color.coreColor.RGB, Equals, "DEADBEEF")
+ c.Assert(r.Font.Bold, Equals, true)
+ c.Assert(r.Font.Italic, Equals, false)
+ c.Assert(r.Font.Strike, Equals, false)
+ c.Assert(r.Font.VertAlign, Equals, RichTextVertAlignSuperscript)
+ c.Assert(r.Font.Underline, Equals, RichTextUnderlineDouble)
+ c.Assert(r.Text, Equals, "Bold")
+
+ r = rtr[1]
+ c.Assert(r.Font.Name, Equals, "")
+ c.Assert(r.Font.Size, Equals, 0.0)
+ c.Assert(r.Font.Family, Equals, RichTextFontFamilyUnspecified)
+ c.Assert(r.Font.Charset, Equals, RichTextCharsetUnspecified)
+ c.Assert(r.Font.Color, IsNil)
+ c.Assert(r.Font.Bold, Equals, false)
+ c.Assert(r.Font.Italic, Equals, true)
+ c.Assert(r.Font.Strike, Equals, false)
+ c.Assert(r.Font.VertAlign, Equals, RichTextVertAlign(""))
+ c.Assert(r.Font.Underline, Equals, RichTextUnderline(""))
+ c.Assert(r.Text, Equals, "Italic")
+
+ r = rtr[2]
+ c.Assert(r.Font.Name, Equals, "")
+ c.Assert(r.Font.Size, Equals, 0.0)
+ c.Assert(r.Font.Family, Equals, RichTextFontFamilyUnspecified)
+ c.Assert(r.Font.Charset, Equals, RichTextCharsetUnspecified)
+ c.Assert(r.Font.Color, IsNil)
+ c.Assert(r.Font.Bold, Equals, false)
+ c.Assert(r.Font.Italic, Equals, false)
+ c.Assert(r.Font.Strike, Equals, true)
+ c.Assert(r.Font.VertAlign, Equals, RichTextVertAlign(""))
+ c.Assert(r.Font.Underline, Equals, RichTextUnderline(""))
+ c.Assert(r.Text, Equals, "Strike")
+
+ r = rtr[3]
+ c.Assert(r.Font.Name, Equals, "")
+ c.Assert(r.Font.Size, Equals, 0.0)
+ c.Assert(r.Font.Family, Equals, RichTextFontFamilyUnspecified)
+ c.Assert(r.Font.Charset, Equals, RichTextCharsetUnspecified)
+ c.Assert(r.Font.Color, IsNil)
+ c.Assert(r.Font.Bold, Equals, false)
+ c.Assert(r.Font.Italic, Equals, false)
+ c.Assert(r.Font.Strike, Equals, false)
+ c.Assert(r.Font.VertAlign, Equals, RichTextVertAlign(""))
+ c.Assert(r.Font.Underline, Equals, RichTextUnderline(""))
+ c.Assert(r.Text, Equals, "Empty")
+
+ r = rtr[4]
+ c.Assert(r.Font, IsNil)
+ c.Assert(r.Text, Equals, "No Font")
+}
+
+func (s *RichTextSuite) TestRichTextToPlainText(c *C) {
+ rt := []RichTextRun{
+ RichTextRun{
+ Font: &RichTextFont{
+ Bold: true,
+ },
+ Text: "Bold",
+ },
+ RichTextRun{
+ Font: &RichTextFont{
+ Italic: true,
+ },
+ Text: "Italic",
+ },
+ RichTextRun{
+ Font: &RichTextFont{
+ Strike: true,
+ },
+ Text: "Strike",
+ },
+ }
+ plainText := richTextToPlainText(rt)
+ c.Assert(plainText, Equals, "BoldItalicStrike")
+}
+
+func (s *RichTextSuite) TestRichTextToPlainTextEmpty(c *C) {
+ rt := []RichTextRun{}
+ plainText := richTextToPlainText(rt)
+ c.Assert(plainText, Equals, "")
+}
diff --git a/sheet.go b/sheet.go
index bbdc350c..9293aed4 100644
--- a/sheet.go
+++ b/sheet.go
@@ -546,6 +546,8 @@ func (s *Sheet) makeRows(worksheet *xlsxWorksheet, styles *xlsxStyleSheet, refTa
case CellTypeString:
if len(cell.Value) > 0 {
xC.V = strconv.Itoa(refTable.AddString(cell.Value))
+ } else if len(cell.RichText) > 0 {
+ xC.V = strconv.Itoa(refTable.AddRichText(cell.RichText))
}
xC.T = "s"
case CellTypeNumeric:
diff --git a/stream_file.go b/stream_file.go
index 9ad701fb..ea542105 100644
--- a/stream_file.go
+++ b/stream_file.go
@@ -318,12 +318,12 @@ func makeXlsxCell(cellType CellType, cellCoordinate string, cellStyleId int, cel
case CellTypeError:
return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "e", V: cellData}, nil
case CellTypeInline:
- return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "inlineStr", Is: &xlsxSI{T: cellData}}, nil
+ return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "inlineStr", Is: &xlsxSI{T: &xlsxT{Text: cellData}}}, nil
case CellTypeNumeric:
return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "n", V: cellData}, nil
case CellTypeString:
// TODO Currently shared strings are types as inline strings
- return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "inlineStr", Is: &xlsxSI{T: cellData}}, nil
+ return xlsxC{XMLName: xml.Name{Local: "c"}, R: cellCoordinate, S: cellStyleId, T: "inlineStr", Is: &xlsxSI{T: &xlsxT{Text: cellData}}}, nil
// TODO currently not supported
// case CellTypeStringFormula:
// return xlsxC{}, UnsupportedCellTypeError
diff --git a/xmlSharedStrings.go b/xmlSharedStrings.go
index 306a8020..9a614da2 100644
--- a/xmlSharedStrings.go
+++ b/xmlSharedStrings.go
@@ -2,6 +2,8 @@ package xlsx
import (
"encoding/xml"
+ "errors"
+ "strings"
)
// xlsxSST directly maps the sst element from the namespace
@@ -19,7 +21,7 @@ type xlsxSST struct {
// currently I have not checked this for completeness - it does as
// much as I need.
type xlsxSI struct {
- T string `xml:"t"`
+ T *xlsxT `xml:"t"`
R []xlsxR `xml:"r"`
}
@@ -28,5 +30,135 @@ type xlsxSI struct {
// currently I have not checked this for completeness - it does as
// much as I need.
type xlsxR struct {
- T string `xml:"t"`
+ RPr *xlsxRunProperties `xml:"rPr"`
+ T xlsxT `xml:"t"`
+}
+
+// xlsxRunProperties directly maps the rPr element from the namespace
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main
+type xlsxRunProperties struct {
+ RFont *xlsxVal `xml:"rFont"`
+ Charset *xlsxIntVal `xml:"charset"`
+ Family *xlsxIntVal `xml:"family"`
+ B xlsxBoolProp `xml:"b"`
+ I xlsxBoolProp `xml:"i"`
+ Strike xlsxBoolProp `xml:"strike"`
+ Outline xlsxBoolProp `xml:"outline"`
+ Shadow xlsxBoolProp `xml:"shadow"`
+ Condense xlsxBoolProp `xml:"condense"`
+ Extend xlsxBoolProp `xml:"extend"`
+ Color *xlsxColor `xml:"color"`
+ Sz *xlsxFloatVal `xml:"sz"`
+ U *xlsxVal `xml:"u"`
+ VertAlign *xlsxVal `xml:"vertAlign"`
+ Scheme *xlsxVal `xml:"scheme"`
+}
+
+// xlsxBoolProp handles "CT_BooleanProperty" type which is declared in the XML Schema of Office Open XML.
+// XML attribute "val" is optional. If "val" was omitted, the property value becomes "true".
+// On the serialization, the struct which has "true" will be serialized an empty XML tag without "val" attributes,
+// and the struct which has "false" will not be serialized.
+type xlsxBoolProp struct {
+ Val bool `xml:"val,addr"`
+}
+
+// MarshalXML implements xml.Marshaler interface for xlsxBoolProp
+func (b *xlsxBoolProp) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if b.Val {
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// UnmarshalXML implements xml.Unmarshaler interface for xlsxBoolProp
+func (b *xlsxBoolProp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+ boolVal := true
+ for _, attr := range start.Attr {
+ if attr.Name.Space == "" && attr.Name.Local == "val" {
+ // supports xsd:boolean
+ switch attr.Value {
+ case "true", "1":
+ boolVal = true
+ case "false", "0":
+ boolVal = false
+ default:
+ return errors.New(
+ "Cannot unmarshal into xlsxBoolProp: \"" +
+ attr.Value + "\" is not a valid boolean value")
+ }
+ }
+ }
+ b.Val = boolVal
+ return d.Skip()
+}
+
+// xlsxIntVal is like xlsxVal, except it has an int value
+type xlsxIntVal struct {
+ Val int `xml:"val,attr"`
+}
+
+// xlsxFloatVal is like xlsxVal, except it has a float value
+type xlsxFloatVal struct {
+ Val float64 `xml:"val,attr"`
+}
+
+// xlsxT represents a text. It will be serialized as a XML tag which has character data.
+// Attribute xml:space="preserve" will be added to the XML tag if needed.
+type xlsxT struct {
+ Text string `xml:",chardata"`
+}
+
+// MarshalXML implements xml.Marshaler interface for xlsxT
+func (t *xlsxT) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ if needPreserve(t.Text) {
+ attr := xml.Attr{
+ Name: xml.Name{Local: "xml:space"},
+ Value: "preserve",
+ }
+ start.Attr = append(start.Attr, attr)
+ }
+
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ if err := e.EncodeToken(xml.CharData(t.Text)); err != nil {
+ return err
+ }
+ if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
+ return err
+ }
+ return nil
+}
+
+// getText is a nil-safe utility function that gets a string from xlsxT.
+// If the pointer of xlsxT was nil, returns an empty string.
+func (t *xlsxT) getText() string {
+ if t == nil {
+ return ""
+ }
+ return t.Text
+}
+
+// needPreserve determines whether xml:space="preserve" is needed.
+func needPreserve(s string) bool {
+ if len(s) == 0 {
+ return false
+ }
+ // Note:
+ // xml:space="preserve" is not needed for CR and TAB
+ // because they are serialized as "
" and " ".
+ c := s[0]
+ if c <= 32 && c != 9 && c != 13 {
+ return true
+ }
+ c = s[len(s)-1]
+ if c <= 32 && c != 9 && c != 13 {
+ return true
+ }
+ return strings.ContainsRune(s, '\u000a')
}
diff --git a/xmlSharedStrings_test.go b/xmlSharedStrings_test.go
index b9fa8b4c..42bbbfd5 100644
--- a/xmlSharedStrings_test.go
+++ b/xmlSharedStrings_test.go
@@ -17,8 +17,8 @@ func (s *SharedStringsSuite) SetUpTest(c *C) {
s.SharedStringsXML = bytes.NewBufferString(
`
+ count="5"
+ uniqueCount="5">
Foo
@@ -31,6 +31,39 @@ func (s *SharedStringsSuite) SetUpTest(c *C) {
Quuk
+
+
+ Normal
+
+
+
+
+ Normal2
+
+
+
+
+
+
+
+
+
+ Bools
+
+
+
+
+
+ Font Spec
+
+
+
+
+
+
+ Misc
+
+
`)
}
@@ -40,9 +73,138 @@ func (s *SharedStringsSuite) TestUnmarshallSharedStrings(c *C) {
sst := new(xlsxSST)
err := xml.NewDecoder(s.SharedStringsXML).Decode(sst)
c.Assert(err, IsNil)
- c.Assert(sst.Count, Equals, 4)
- c.Assert(sst.UniqueCount, Equals, 4)
- c.Assert(sst.SI, HasLen, 4)
+ c.Assert(sst.Count, Equals, 5)
+ c.Assert(sst.UniqueCount, Equals, 5)
+ c.Assert(sst.SI, HasLen, 5)
+
si := sst.SI[0]
- c.Assert(si.T, Equals, "Foo")
+ c.Assert(si.T.Text, Equals, "Foo")
+ c.Assert(si.R, IsNil)
+ si = sst.SI[1]
+ c.Assert(si.T.Text, Equals, "Bar")
+ c.Assert(si.R, IsNil)
+ si = sst.SI[2]
+ c.Assert(si.T.Text, Equals, "Baz ")
+ c.Assert(si.R, IsNil)
+ si = sst.SI[3]
+ c.Assert(si.T.Text, Equals, "Quuk")
+ c.Assert(si.R, IsNil)
+ si = sst.SI[4]
+ c.Assert(si.T, IsNil)
+ c.Assert(len(si.R), Equals, 5)
+ r := si.R[0]
+ c.Assert(r.T.Text, Equals, "Normal")
+ c.Assert(r.RPr, IsNil)
+ r = si.R[1]
+ c.Assert(r.T.Text, Equals, "Normal2")
+ c.Assert(r.RPr.RFont, IsNil)
+ c.Assert(r.RPr.Sz, IsNil)
+ c.Assert(r.RPr.Color, IsNil)
+ c.Assert(r.RPr.Family, IsNil)
+ c.Assert(r.RPr.Charset, IsNil)
+ c.Assert(r.RPr.Scheme, IsNil)
+ c.Assert(r.RPr.B.Val, Equals, false)
+ c.Assert(r.RPr.I.Val, Equals, false)
+ c.Assert(r.RPr.Strike.Val, Equals, false)
+ c.Assert(r.RPr.Outline.Val, Equals, false)
+ c.Assert(r.RPr.Shadow.Val, Equals, false)
+ c.Assert(r.RPr.Condense.Val, Equals, false)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ c.Assert(r.RPr.U, IsNil)
+ c.Assert(r.RPr.VertAlign, IsNil)
+ r = si.R[2]
+ c.Assert(r.T.Text, Equals, "Bools")
+ c.Assert(r.RPr.RFont, IsNil)
+ c.Assert(r.RPr.B.Val, Equals, true)
+ c.Assert(r.RPr.I.Val, Equals, false)
+ c.Assert(r.RPr.Strike.Val, Equals, true)
+ c.Assert(r.RPr.Condense.Val, Equals, true)
+ c.Assert(r.RPr.Extend.Val, Equals, false)
+ r = si.R[3]
+ c.Assert(r.T.Text, Equals, "Font Spec")
+ c.Assert(r.RPr.RFont.Val, Equals, "FontZ")
+ c.Assert(r.RPr.Sz.Val, Equals, 13.5)
+ c.Assert(*r.RPr.Color.Theme, Equals, 1)
+ c.Assert(r.RPr.Family.Val, Equals, 2)
+ c.Assert(r.RPr.Charset.Val, Equals, 128)
+ c.Assert(r.RPr.Scheme.Val, Equals, "minor")
+ r = si.R[4]
+ c.Assert(r.T.Text, Equals, "Misc")
+ c.Assert(r.RPr.U.Val, Equals, "single")
+ c.Assert(r.RPr.VertAlign.Val, Equals, "superscript")
+}
+
+// TestMarshalSI_T tests that xlsxT is marshaled as it is expected.
+func (s *SharedStringsSuite) TestMarshalSI_T(c *C) {
+ testMarshalSIT(c, "", "")
+ testMarshalSIT(c, "a b c", "a b c")
+ testMarshalSIT(c, " abc", " abc")
+ testMarshalSIT(c, "abc ", "abc ")
+ testMarshalSIT(c, "\nabc", "\nabc")
+ testMarshalSIT(c, "abc\n", "abc\n")
+ testMarshalSIT(c, "ab\nc", "ab\nc")
+}
+
+func testMarshalSIT(c *C, t string, expected string) {
+ si := xlsxSI{T: &xlsxT{Text: t}}
+ bytes, err := xml.Marshal(&si)
+ c.Assert(err, IsNil)
+ c.Assert(string(bytes), Equals, expected)
+}
+
+// TestMarshalSI_R tests that xlsxR is marshaled as it is expected.
+func (s *SharedStringsSuite) TestMarshalSI_R(c *C) {
+ testMarshalSIR(c, xlsxR{}, "")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a b c"}}, "a b c")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: " abc"}}, " abc")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "abc "}}, "abc ")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "\nabc"}}, "\nabc")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "abc\n"}}, "abc\n")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "ab\nc"}}, "ab\nc")
+
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{RFont: &xlsxVal{Val: "Times New Roman"}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Charset: &xlsxIntVal{Val: 1}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Family: &xlsxIntVal{Val: 1}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{B: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{I: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Strike: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Outline: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Shadow: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Condense: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Extend: xlsxBoolProp{Val: true}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Color: &xlsxColor{RGB: "FF123456"}}},
+ "a")
+ colorIndex := 11
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Color: &xlsxColor{Indexed: &colorIndex}}},
+ "a")
+ colorTheme := 5
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Color: &xlsxColor{Theme: &colorTheme}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Color: &xlsxColor{Theme: &colorTheme, Tint: 0.1}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Sz: &xlsxFloatVal{Val: 12.5}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{U: &xlsxVal{Val: "single"}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{VertAlign: &xlsxVal{Val: "superscript"}}},
+ "a")
+ testMarshalSIR(c, xlsxR{T: xlsxT{Text: "a"}, RPr: &xlsxRunProperties{Scheme: &xlsxVal{Val: "major"}}},
+ "a")
+}
+
+func testMarshalSIR(c *C, r xlsxR, expected string) {
+ si := xlsxSI{R: []xlsxR{r}}
+ bytes, err := xml.Marshal(&si)
+ c.Assert(err, IsNil)
+ c.Assert(string(bytes), Equals, expected)
}