Skip to content

Commit 66d0272

Browse files
committed
Resolve qax-os#172, init rich text support
1 parent 0f2a905 commit 66d0272

File tree

9 files changed

+331
-32
lines changed

9 files changed

+331
-32
lines changed

cell.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,172 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error {
457457
return nil
458458
}
459459

460+
// SetCellRichText provides a function to set cell with rich text by given
461+
// worksheet. For example:
462+
//
463+
// package main
464+
//
465+
// import (
466+
// "fmt"
467+
//
468+
// "github.com/360EntSecGroup-Skylar/excelize"
469+
// )
470+
//
471+
// func main() {
472+
// f := excelize.NewFile()
473+
// if err := f.SetRowHeight("Sheet1", 1, 35); err != nil {
474+
// fmt.Println(err)
475+
// return
476+
// }
477+
// if err := f.SetColWidth("Sheet1", "A", "A", 44); err != nil {
478+
// fmt.Println(err)
479+
// return
480+
// }
481+
// if err := f.SetCellRichText("Sheet1", "A1", []excelize.RichTextRun{
482+
// {
483+
// Text: "blod",
484+
// Font: &excelize.Font{
485+
// Bold: true,
486+
// Color: "2354e8",
487+
// Family: "Times New Roman",
488+
// },
489+
// },
490+
// {
491+
// Text: " and ",
492+
// Font: &excelize.Font{
493+
// Family: "Times New Roman",
494+
// },
495+
// },
496+
// {
497+
// Text: " italic",
498+
// Font: &excelize.Font{
499+
// Bold: true,
500+
// Color: "e83723",
501+
// Italic: true,
502+
// Family: "Times New Roman",
503+
// },
504+
// },
505+
// {
506+
// Text: "text with color and font-family,",
507+
// Font: &excelize.Font{
508+
// Bold: true,
509+
// Color: "2354e8",
510+
// Family: "Times New Roman",
511+
// },
512+
// },
513+
// {
514+
// Text: "\r\nlarge text with ",
515+
// Font: &excelize.Font{
516+
// Size: 14,
517+
// Color: "ad23e8",
518+
// },
519+
// },
520+
// {
521+
// Text: "strike",
522+
// Font: &excelize.Font{
523+
// Color: "e89923",
524+
// Strike: true,
525+
// },
526+
// },
527+
// {
528+
// Text: " and ",
529+
// Font: &excelize.Font{
530+
// Size: 14,
531+
// Color: "ad23e8",
532+
// },
533+
// },
534+
// {
535+
// Text: "underline.",
536+
// Font: &excelize.Font{
537+
// Color: "23e833",
538+
// Underline: "single",
539+
// },
540+
// },
541+
// }); err != nil {
542+
// fmt.Println(err)
543+
// return
544+
// }
545+
// style, err := f.NewStyle(&excelize.Style{
546+
// Alignment: &excelize.Alignment{
547+
// WrapText: true,
548+
// },
549+
// })
550+
// if err != nil {
551+
// fmt.Println(err)
552+
// return
553+
// }
554+
// if err := f.SetCellStyle("Sheet1", "A1", "A1", style); err != nil {
555+
// fmt.Println(err)
556+
// return
557+
// }
558+
// if err := f.SaveAs("Book1.xlsx"); err != nil {
559+
// fmt.Println(err)
560+
// }
561+
// }
562+
//
563+
func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
564+
ws, err := f.workSheetReader(sheet)
565+
if err != nil {
566+
return err
567+
}
568+
cellData, col, _, err := f.prepareCell(ws, sheet, cell)
569+
if err != nil {
570+
return err
571+
}
572+
cellData.S = f.prepareCellStyle(ws, col, cellData.S)
573+
si := xlsxSI{}
574+
sst := f.sharedStringsReader()
575+
textRuns := []xlsxR{}
576+
for _, textRun := range runs {
577+
run := xlsxR{T: &xlsxT{Val: textRun.Text}}
578+
if strings.ContainsAny(textRun.Text, "\r\n ") {
579+
run.T.Space = "preserve"
580+
}
581+
fnt := textRun.Font
582+
if fnt != nil {
583+
rpr := xlsxRPr{}
584+
if fnt.Bold {
585+
rpr.B = " "
586+
}
587+
if fnt.Italic {
588+
rpr.I = " "
589+
}
590+
if fnt.Strike {
591+
rpr.Strike = " "
592+
}
593+
if fnt.Underline != "" {
594+
rpr.U = &attrValString{Val: &fnt.Underline}
595+
}
596+
if fnt.Family != "" {
597+
rpr.RFont = &attrValString{Val: &fnt.Family}
598+
}
599+
if fnt.Size > 0.0 {
600+
rpr.Sz = &attrValFloat{Val: &fnt.Size}
601+
}
602+
if fnt.Color != "" {
603+
rpr.Color = &xlsxColor{RGB: getPaletteColor(fnt.Color)}
604+
}
605+
run.RPr = &rpr
606+
}
607+
textRuns = append(textRuns, run)
608+
}
609+
si.R = textRuns
610+
sst.SI = append(sst.SI, si)
611+
sst.Count++
612+
sst.UniqueCount++
613+
cellData.T, cellData.V = "s", strconv.Itoa(len(sst.SI)-1)
614+
f.addContentTypePart(0, "sharedStrings")
615+
rels := f.relsReader("xl/_rels/workbook.xml.rels")
616+
for _, rel := range rels.Relationships {
617+
if rel.Target == "sharedStrings.xml" {
618+
return err
619+
}
620+
}
621+
// Update xl/_rels/workbook.xml.rels
622+
f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipSharedStrings, "sharedStrings.xml", "")
623+
return err
624+
}
625+
460626
// SetSheetRow writes an array to row by given worksheet name, starting
461627
// coordinate and a pointer to array type 'slice'. For example, writes an
462628
// array to row 6 start with the cell B6 on Sheet1:

cell_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,84 @@ func TestOverflowNumericCell(t *testing.T) {
141141
// GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648"
142142
assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225")
143143
}
144+
145+
func TestSetCellRichText(t *testing.T) {
146+
f := NewFile()
147+
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
148+
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 44))
149+
richTextRun := []RichTextRun{
150+
{
151+
Text: "blod",
152+
Font: &Font{
153+
Bold: true,
154+
Color: "2354e8",
155+
Family: "Times New Roman",
156+
},
157+
},
158+
{
159+
Text: " and ",
160+
Font: &Font{
161+
Family: "Times New Roman",
162+
},
163+
},
164+
{
165+
Text: "italic ",
166+
Font: &Font{
167+
Bold: true,
168+
Color: "e83723",
169+
Italic: true,
170+
Family: "Times New Roman",
171+
},
172+
},
173+
{
174+
Text: "text with color and font-family,",
175+
Font: &Font{
176+
Bold: true,
177+
Color: "2354e8",
178+
Family: "Times New Roman",
179+
},
180+
},
181+
{
182+
Text: "\r\nlarge text with ",
183+
Font: &Font{
184+
Size: 14,
185+
Color: "ad23e8",
186+
},
187+
},
188+
{
189+
Text: "strike",
190+
Font: &Font{
191+
Color: "e89923",
192+
Strike: true,
193+
},
194+
},
195+
{
196+
Text: " and ",
197+
Font: &Font{
198+
Size: 14,
199+
Color: "ad23e8",
200+
},
201+
},
202+
{
203+
Text: "underline.",
204+
Font: &Font{
205+
Color: "23e833",
206+
Underline: "single",
207+
},
208+
},
209+
}
210+
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", richTextRun))
211+
assert.NoError(t, f.SetCellRichText("Sheet1", "A2", richTextRun))
212+
style, err := f.NewStyle(&Style{
213+
Alignment: &Alignment{
214+
WrapText: true,
215+
},
216+
})
217+
assert.NoError(t, err)
218+
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
219+
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx")))
220+
// Test set cell rich text on not exists worksheet
221+
assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN is not exist")
222+
// Test set cell rich text with illegal cell coordinates
223+
assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
224+
}

comment.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ func (f *File) GetComments() (comments map[string][]Comment) {
5050
sheetComment.Text += *comment.Text.T
5151
}
5252
for _, text := range comment.Text.R {
53-
sheetComment.Text += text.T
53+
if text.T != nil {
54+
sheetComment.Text += text.T.Val
55+
}
5456
}
5557
sheetComments = append(sheetComments, sheetComment)
5658
}
@@ -263,7 +265,7 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
263265
RFont: &attrValString{Val: stringPtr(defaultFont)},
264266
Family: &attrValInt{Val: intPtr(2)},
265267
},
266-
T: a,
268+
T: &xlsxT{Val: a},
267269
},
268270
{
269271
RPr: &xlsxRPr{
@@ -274,7 +276,7 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
274276
RFont: &attrValString{Val: stringPtr(defaultFont)},
275277
Family: &attrValInt{Val: intPtr(2)},
276278
},
277-
T: t,
279+
T: &xlsxT{Val: t},
278280
},
279281
},
280282
},

file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
9797
f.workBookWriter()
9898
f.workSheetWriter()
9999
f.relsWriter()
100+
f.sharedStringsWriter()
100101
f.styleSheetWriter()
101102

102103
for path, content := range f.XLSX {

picture.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -367,22 +367,24 @@ func (f *File) addContentTypePart(index int, contentType string) {
367367
"drawings": f.setContentTypePartImageExtensions,
368368
}
369369
partNames := map[string]string{
370-
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
371-
"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
372-
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
373-
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
374-
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
375-
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
376-
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
370+
"chart": "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
371+
"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
372+
"comments": "/xl/comments" + strconv.Itoa(index) + ".xml",
373+
"drawings": "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
374+
"table": "/xl/tables/table" + strconv.Itoa(index) + ".xml",
375+
"pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml",
376+
"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
377+
"sharedStrings": "/xl/sharedStrings.xml",
377378
}
378379
contentTypes := map[string]string{
379-
"chart": ContentTypeDrawingML,
380-
"chartsheet": ContentTypeSpreadSheetMLChartsheet,
381-
"comments": ContentTypeSpreadSheetMLComments,
382-
"drawings": ContentTypeDrawing,
383-
"table": ContentTypeSpreadSheetMLTable,
384-
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
385-
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
380+
"chart": ContentTypeDrawingML,
381+
"chartsheet": ContentTypeSpreadSheetMLChartsheet,
382+
"comments": ContentTypeSpreadSheetMLComments,
383+
"drawings": ContentTypeDrawing,
384+
"table": ContentTypeSpreadSheetMLTable,
385+
"pivotTable": ContentTypeSpreadSheetMLPivotTable,
386+
"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
387+
"sharedStrings": ContentTypeSpreadSheetMLSharedStrings,
386388
}
387389
s, ok := setContentType[contentType]
388390
if ok {

styles.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"bytes"
1414
"encoding/json"
1515
"encoding/xml"
16+
"errors"
1617
"fmt"
1718
"io"
1819
"log"
@@ -1022,6 +1023,15 @@ func (f *File) styleSheetWriter() {
10221023
}
10231024
}
10241025

1026+
// sharedStringsWriter provides a function to save xl/sharedStrings.xml after
1027+
// serialize structure.
1028+
func (f *File) sharedStringsWriter() {
1029+
if f.SharedStrings != nil {
1030+
output, _ := xml.Marshal(f.SharedStrings)
1031+
f.saveFileList("xl/sharedStrings.xml", replaceRelationshipsNameSpaceBytes(output))
1032+
}
1033+
}
1034+
10251035
// parseFormatStyleSet provides a function to parse the format settings of the
10261036
// cells and conditional formats.
10271037
func parseFormatStyleSet(style string) (*Style, error) {
@@ -1033,7 +1043,7 @@ func parseFormatStyleSet(style string) (*Style, error) {
10331043
}
10341044

10351045
// NewStyle provides a function to create the style for cells by given JSON or
1036-
// structure. Note that the color field uses RGB color code.
1046+
// structure pointer. Note that the color field uses RGB color code.
10371047
//
10381048
// The following shows the border styles sorted by excelize index number:
10391049
//
@@ -1906,6 +1916,8 @@ func (f *File) NewStyle(style interface{}) (int, error) {
19061916
}
19071917
case *Style:
19081918
fs = v
1919+
default:
1920+
return cellXfsID, errors.New("invalid parameter type")
19091921
}
19101922
s := f.stylesReader()
19111923
numFmtID := setNumFmt(s, fs)

styles_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ func TestNewStyle(t *testing.T) {
193193
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
194194
_, err = f.NewStyle(&Style{})
195195
assert.NoError(t, err)
196+
_, err = f.NewStyle(Style{})
197+
assert.EqualError(t, err, "invalid parameter type")
196198
}
197199

198200
func TestGetDefaultFont(t *testing.T) {

0 commit comments

Comments
 (0)