Skip to content

Commit

Permalink
add support of rich text
Browse files Browse the repository at this point in the history
* Change xlsxSI so that it can contain "r" element.
* Add xlsxT for representing "t" element. xlsxT also supports xml:space="preseve" in the serialization.
* Change Cell so that it can have the rich text. It is possible to be a breaking change.
* RefTable can handle rich text
  • Loading branch information
kzmi committed Mar 24, 2020
1 parent 166ec63 commit b350962
Show file tree
Hide file tree
Showing 11 changed files with 1,165 additions and 54 deletions.
10 changes: 10 additions & 0 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 9 additions & 3 deletions format_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,27 @@ 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"
// for strings, and all values that are not numbers would show up as "Error". In that case, this code would
// 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
Expand Down
11 changes: 5 additions & 6 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
88 changes: 70 additions & 18 deletions reftable.go
Original file line number Diff line number Diff line change
@@ -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).
Expand All @@ -22,38 +30,45 @@ 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{}
sst.Count = len(rt.indexedStrings)
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
Expand All @@ -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)
}
Loading

0 comments on commit b350962

Please sign in to comment.