From 2f5704b114d033e81725f18459f9293a9adfee1e Mon Sep 17 00:00:00 2001 From: "charles.deng" Date: Mon, 10 Oct 2022 00:11:18 +0800 Subject: [PATCH] Stream writer support to set inline rich text cell (#1121) Co-authored-by: zhengchao.deng --- cell.go | 42 +++++++++++++++++++++++++----------------- excelize.go | 12 ++++++------ stream.go | 21 +++++++++++++++++++-- stream_test.go | 9 ++++++--- xmlSharedStrings.go | 5 +++-- xmlWorksheet.go | 17 ++++++++--------- 6 files changed, 67 insertions(+), 39 deletions(-) diff --git a/cell.go b/cell.go index 6beb3b27..80eb0352 100644 --- a/cell.go +++ b/cell.go @@ -885,6 +885,28 @@ func newRpr(fnt *Font) *xlsxRPr { return &rpr } +// setRichText provides a function to set rich text of a cell. +func setRichText(runs []RichTextRun) ([]xlsxR, error) { + var ( + textRuns []xlsxR + totalCellChars int + ) + for _, textRun := range runs { + totalCellChars += len(textRun.Text) + if totalCellChars > TotalCellChars { + return textRuns, ErrCellCharsLength + } + run := xlsxR{T: &xlsxT{}} + _, run.T.Val, run.T.Space = setCellStr(textRun.Text) + fnt := textRun.Font + if fnt != nil { + run.RPr = newRpr(fnt) + } + textRuns = append(textRuns, run) + } + return textRuns, nil +} + // SetCellRichText provides a function to set cell with rich text by given // worksheet. For example, set rich text on the A1 cell of the worksheet named // Sheet1: @@ -1016,24 +1038,10 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { return err } c.S = f.prepareCellStyle(ws, col, row, c.S) - si := xlsxSI{} - sst := f.sharedStringsReader() - var textRuns []xlsxR - totalCellChars := 0 - for _, textRun := range runs { - totalCellChars += len(textRun.Text) - if totalCellChars > TotalCellChars { - return ErrCellCharsLength - } - run := xlsxR{T: &xlsxT{}} - _, run.T.Val, run.T.Space = setCellStr(textRun.Text) - fnt := textRun.Font - if fnt != nil { - run.RPr = newRpr(fnt) - } - textRuns = append(textRuns, run) + si, sst := xlsxSI{}, f.sharedStringsReader() + if si.R, err = setRichText(runs); err != nil { + return err } - si.R = textRuns for idx, strItem := range sst.SI { if reflect.DeepEqual(strItem, si) { c.T, c.V = "s", strconv.Itoa(idx) diff --git a/excelize.go b/excelize.go index ec7485bd..94d10885 100644 --- a/excelize.go +++ b/excelize.go @@ -444,12 +444,12 @@ func (f *File) UpdateLinkedValue() error { // AddVBAProject provides the method to add vbaProject.bin file which contains // functions and/or macros. The file extension should be .xlsm. For example: // -// codeName := "Sheet1" -// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ -// CodeName: &codeName, -// }); err != nil { -// fmt.Println(err) -// } +// codeName := "Sheet1" +// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ +// CodeName: &codeName, +// }); err != nil { +// fmt.Println(err) +// } // if err := f.AddVBAProject("vbaProject.bin"); err != nil { // fmt.Println(err) // } diff --git a/stream.go b/stream.go index b99730dc..66c0fda7 100644 --- a/stream.go +++ b/stream.go @@ -56,7 +56,14 @@ type StreamWriter struct { // if err != nil { // fmt.Println(err) // } -// if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}}, +// if err := streamWriter.SetRow("A1", +// []interface{}{ +// excelize.Cell{StyleID: styleID, Value: "Data"}, +// []excelize.RichTextRun{ +// {Text: "Rich ", Font: &excelize.Font{Color: "2354e8"}}, +// {Text: "Text", Font: &excelize.Font{Color: "e83723"}}, +// }, +// }, // excelize.RowOpts{Height: 45, Hidden: false}); err != nil { // fmt.Println(err) // } @@ -433,7 +440,8 @@ func setCellFormula(c *xlsxC, formula string) { } // setCellValFunc provides a function to set value of a cell. -func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) { +func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error { + var err error switch val := val.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: err = setCellIntFunc(c, val) @@ -462,6 +470,9 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) { c.T, c.V = setCellBool(val) case nil: c.T, c.V, c.XMLSpace = setCellStr("") + case []RichTextRun: + c.T, c.IS = "inlineStr", &xlsxSI{} + c.IS.R, err = setRichText(val) default: c.T, c.V, c.XMLSpace = setCellStr(fmt.Sprint(val)) } @@ -519,6 +530,12 @@ func writeCell(buf *bufferedWriter, c xlsxC) { _ = xml.EscapeText(buf, []byte(c.V)) _, _ = buf.WriteString(``) } + if c.IS != nil { + is, _ := xml.Marshal(c.IS.R) + _, _ = buf.WriteString(``) + _, _ = buf.Write(is) + _, _ = buf.WriteString(``) + } _, _ = buf.WriteString(``) } diff --git a/stream_test.go b/stream_test.go index 80875c79..3c2cc691 100644 --- a/stream_test.go +++ b/stream_test.go @@ -52,11 +52,14 @@ func TestStreamWriter(t *testing.T) { row[0] = []byte("Word") assert.NoError(t, streamWriter.SetRow("A3", row)) - // Test set cell with style. + // Test set cell with style and rich text. styleID, err := file.NewStyle(&Style{Font: &Font{Color: "#777777"}}) assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A4", []interface{}{Cell{StyleID: styleID}, Cell{Formula: "SUM(A10,B10)"}}, RowOpts{Height: 45, StyleID: styleID})) - assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}, &Cell{Formula: "SUM(A10,B10)"}})) + assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}, &Cell{Formula: "SUM(A10,B10)"}, []RichTextRun{ + {Text: "Rich ", Font: &Font{Color: "2354e8"}}, + {Text: "Text", Font: &Font{Color: "e83723"}}, + }})) assert.NoError(t, streamWriter.SetRow("A6", []interface{}{time.Now()})) assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID})) assert.EqualError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: MaxRowHeight + 1}), ErrMaxRowHeight.Error()) @@ -128,7 +131,7 @@ func TestStreamWriter(t *testing.T) { cells += len(row) } assert.NoError(t, rows.Close()) - assert.Equal(t, 2559558, cells) + assert.Equal(t, 2559559, cells) // Save spreadsheet with password. assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"})) assert.NoError(t, file.Close()) diff --git a/xmlSharedStrings.go b/xmlSharedStrings.go index 683105e3..3249ecac 100644 --- a/xmlSharedStrings.go +++ b/xmlSharedStrings.go @@ -46,8 +46,9 @@ type xlsxSI struct { // properties are defined in the rPr element, and the text displayed to the // user is defined in the Text (t) element. type xlsxR struct { - RPr *xlsxRPr `xml:"rPr"` - T *xlsxT `xml:"t"` + XMLName xml.Name `xml:"r"` + RPr *xlsxRPr `xml:"rPr"` + T *xlsxT `xml:"t"` } // xlsxT directly maps the t element in the run properties. diff --git a/xmlWorksheet.go b/xmlWorksheet.go index e55406c2..24f5e4e5 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -466,15 +466,14 @@ type xlsxC struct { XMLName xml.Name `xml:"c"` XMLSpace xml.Attr `xml:"space,attr,omitempty"` R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1 - S int `xml:"s,attr,omitempty"` // Style reference. - // Str string `xml:"str,attr,omitempty"` // Style reference. - T string `xml:"t,attr,omitempty"` // Type. - Cm *uint `xml:"cm,attr,omitempty"` // - Vm *uint `xml:"vm,attr,omitempty"` // - Ph *bool `xml:"ph,attr,omitempty"` // - F *xlsxF `xml:"f,omitempty"` // Formula - V string `xml:"v,omitempty"` // Value - IS *xlsxSI `xml:"is"` + S int `xml:"s,attr,omitempty"` // Style reference + T string `xml:"t,attr,omitempty"` // Type + Cm *uint `xml:"cm,attr"` + Vm *uint `xml:"vm,attr"` + Ph *bool `xml:"ph,attr"` + F *xlsxF `xml:"f"` // Formula + V string `xml:"v,omitempty"` // Value + IS *xlsxSI `xml:"is"` } // xlsxF represents a formula for the cell. The formula expression is