Skip to content

Commit f44153e

Browse files
authored
This closes #1377, stream writer writes inline string type for string cell value
- Add `CellTypeFormula`, `CellTypeInlineString`, `CellTypeSharedString` and remove `CellTypeString` in `CellType` enumeration - Unit tests updated
1 parent 14c6a19 commit f44153e

8 files changed

+172
-137
lines changed

calc_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -5223,8 +5223,8 @@ func TestCalcXLOOKUP(t *testing.T) {
52235223
"=XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3",
52245224
}
52255225
for formula, expected := range formulaList {
5226-
assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
5227-
result, err := f.CalcCellValue("Sheet1", "D3")
5226+
assert.NoError(t, f.SetCellFormula("Sheet1", "D4", formula))
5227+
result, err := f.CalcCellValue("Sheet1", "D4")
52285228
assert.NoError(t, err, formula)
52295229
assert.Equal(t, expected, result, formula)
52305230
}

cell.go

+124-27
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ const (
3030
CellTypeBool
3131
CellTypeDate
3232
CellTypeError
33+
CellTypeFormula
34+
CellTypeInlineString
3335
CellTypeNumber
34-
CellTypeString
36+
CellTypeSharedString
3537
)
3638

3739
const (
@@ -51,9 +53,9 @@ var cellTypes = map[string]CellType{
5153
"d": CellTypeDate,
5254
"n": CellTypeNumber,
5355
"e": CellTypeError,
54-
"s": CellTypeString,
55-
"str": CellTypeString,
56-
"inlineStr": CellTypeString,
56+
"s": CellTypeSharedString,
57+
"str": CellTypeFormula,
58+
"inlineStr": CellTypeInlineString,
5759
}
5860

5961
// GetCellValue provides a function to get formatted value from cell by given
@@ -235,8 +237,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
235237
date1904 = wb.WorkbookPr.Date1904
236238
}
237239
var isNum bool
238-
c.T, c.V, isNum, err = setCellTime(value, date1904)
239-
if err != nil {
240+
if isNum, err = c.setCellTime(value, date1904); err != nil {
240241
return err
241242
}
242243
if isNum {
@@ -247,7 +248,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
247248

248249
// setCellTime prepares cell type and Excel time by given Go time.Time type
249250
// timestamp.
250-
func setCellTime(value time.Time, date1904 bool) (t string, b string, isNum bool, err error) {
251+
func (c *xlsxC) setCellTime(value time.Time, date1904 bool) (isNum bool, err error) {
251252
var excelTime float64
252253
_, offset := value.In(value.Location()).Zone()
253254
value = value.Add(time.Duration(offset) * time.Second)
@@ -256,9 +257,9 @@ func setCellTime(value time.Time, date1904 bool) (t string, b string, isNum bool
256257
}
257258
isNum = excelTime > 0
258259
if isNum {
259-
t, b = setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
260+
c.setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
260261
} else {
261-
t, b = setCellDefault(value.Format(time.RFC3339Nano))
262+
c.setCellDefault(value.Format(time.RFC3339Nano))
262263
}
263264
return
264265
}
@@ -435,14 +436,14 @@ func (f *File) setSharedString(val string) (int, error) {
435436
sst.Count++
436437
sst.UniqueCount++
437438
t := xlsxT{Val: val}
438-
_, val, t.Space = setCellStr(val)
439+
val, t.Space = trimCellValue(val)
439440
sst.SI = append(sst.SI, xlsxSI{T: &t})
440441
f.sharedStringsMap[val] = sst.UniqueCount - 1
441442
return sst.UniqueCount - 1, nil
442443
}
443444

444-
// setCellStr provides a function to set string type to cell.
445-
func setCellStr(value string) (t string, v string, ns xml.Attr) {
445+
// trimCellValue provides a function to set string type to cell.
446+
func trimCellValue(value string) (v string, ns xml.Attr) {
446447
if len(value) > TotalCellChars {
447448
value = value[:TotalCellChars]
448449
}
@@ -458,10 +459,117 @@ func setCellStr(value string) (t string, v string, ns xml.Attr) {
458459
}
459460
}
460461
}
461-
t, v = "str", bstrMarshal(value)
462+
v = bstrMarshal(value)
462463
return
463464
}
464465

466+
// setCellValue set cell data type and value for (inline) rich string cell or
467+
// formula cell.
468+
func (c *xlsxC) setCellValue(val string) {
469+
if c.F != nil {
470+
c.setStr(val)
471+
return
472+
}
473+
c.setInlineStr(val)
474+
}
475+
476+
// setInlineStr set cell data type and value which containing an (inline) rich
477+
// string.
478+
func (c *xlsxC) setInlineStr(val string) {
479+
c.T, c.V, c.IS = "inlineStr", "", &xlsxSI{T: &xlsxT{}}
480+
c.IS.T.Val, c.IS.T.Space = trimCellValue(val)
481+
}
482+
483+
// setStr set cell data type and value which containing a formula string.
484+
func (c *xlsxC) setStr(val string) {
485+
c.T, c.IS = "str", nil
486+
c.V, c.XMLSpace = trimCellValue(val)
487+
}
488+
489+
// getCellDate parse cell value which containing a boolean.
490+
func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
491+
if !raw {
492+
if c.V == "1" {
493+
return "TRUE", nil
494+
}
495+
if c.V == "0" {
496+
return "FALSE", nil
497+
}
498+
}
499+
return f.formattedValue(c.S, c.V, raw), nil
500+
}
501+
502+
// setCellDefault prepares cell type and string type cell value by a given
503+
// string.
504+
func (c *xlsxC) setCellDefault(value string) {
505+
if ok, _, _ := isNumeric(value); !ok {
506+
c.setInlineStr(value)
507+
c.IS.T.Val = value
508+
return
509+
}
510+
c.V = value
511+
}
512+
513+
// getCellDate parse cell value which contains a date in the ISO 8601 format.
514+
func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
515+
if !raw {
516+
layout := "20060102T150405.999"
517+
if strings.HasSuffix(c.V, "Z") {
518+
layout = "20060102T150405Z"
519+
if strings.Contains(c.V, "-") {
520+
layout = "2006-01-02T15:04:05Z"
521+
}
522+
} else if strings.Contains(c.V, "-") {
523+
layout = "2006-01-02 15:04:05Z"
524+
}
525+
if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil {
526+
excelTime, _ := timeToExcelTime(timestamp, false)
527+
c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
528+
}
529+
}
530+
return f.formattedValue(c.S, c.V, raw), nil
531+
}
532+
533+
// getValueFrom return a value from a column/row cell, this function is
534+
// intended to be used with for range on rows an argument with the spreadsheet
535+
// opened file.
536+
func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
537+
f.Lock()
538+
defer f.Unlock()
539+
switch c.T {
540+
case "b":
541+
return c.getCellBool(f, raw)
542+
case "d":
543+
return c.getCellDate(f, raw)
544+
case "s":
545+
if c.V != "" {
546+
xlsxSI := 0
547+
xlsxSI, _ = strconv.Atoi(c.V)
548+
if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
549+
return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil
550+
}
551+
if len(d.SI) > xlsxSI {
552+
return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil
553+
}
554+
}
555+
return f.formattedValue(c.S, c.V, raw), nil
556+
case "inlineStr":
557+
if c.IS != nil {
558+
return f.formattedValue(c.S, c.IS.String(), raw), nil
559+
}
560+
return f.formattedValue(c.S, c.V, raw), nil
561+
default:
562+
if isNum, precision, decimal := isNumeric(c.V); isNum && !raw {
563+
if precision > 15 {
564+
c.V = strconv.FormatFloat(decimal, 'G', 15, 64)
565+
} else {
566+
c.V = strconv.FormatFloat(decimal, 'f', -1, 64)
567+
}
568+
}
569+
return f.formattedValue(c.S, c.V, raw), nil
570+
}
571+
}
572+
465573
// SetCellDefault provides a function to set string type value of a cell as
466574
// default format without escaping the cell.
467575
func (f *File) SetCellDefault(sheet, cell, value string) error {
@@ -476,22 +584,11 @@ func (f *File) SetCellDefault(sheet, cell, value string) error {
476584
ws.Lock()
477585
defer ws.Unlock()
478586
c.S = f.prepareCellStyle(ws, col, row, c.S)
479-
c.T, c.V = setCellDefault(value)
480-
c.IS = nil
587+
c.setCellDefault(value)
481588
f.removeFormula(c, ws, sheet)
482589
return err
483590
}
484591

485-
// setCellDefault prepares cell type and string type cell value by a given
486-
// string.
487-
func setCellDefault(value string) (t string, v string) {
488-
if ok, _, _ := isNumeric(value); !ok {
489-
t = "str"
490-
}
491-
v = value
492-
return
493-
}
494-
495592
// GetCellFormula provides a function to get formula from cell by given
496593
// worksheet name and cell reference in spreadsheet.
497594
func (f *File) GetCellFormula(sheet, cell string) (string, error) {
@@ -625,7 +722,7 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts)
625722
c.F.Ref = *opt.Ref
626723
}
627724
}
628-
c.IS = nil
725+
c.T, c.IS = "str", nil
629726
return err
630727
}
631728

@@ -900,7 +997,7 @@ func setRichText(runs []RichTextRun) ([]xlsxR, error) {
900997
return textRuns, ErrCellCharsLength
901998
}
902999
run := xlsxR{T: &xlsxT{}}
903-
_, run.T.Val, run.T.Space = setCellStr(textRun.Text)
1000+
run.T.Val, run.T.Space = trimCellValue(textRun.Text)
9041001
fnt := textRun.Font
9051002
if fnt != nil {
9061003
run.RPr = newRpr(fnt)

cell_test.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,11 @@ func TestSetCellTime(t *testing.T) {
224224
} {
225225
timezone, err := time.LoadLocation(location)
226226
assert.NoError(t, err)
227-
_, b, isNum, err := setCellTime(date.In(timezone), false)
227+
c := &xlsxC{}
228+
isNum, err := c.setCellTime(date.In(timezone), false)
228229
assert.NoError(t, err)
229230
assert.Equal(t, true, isNum)
230-
assert.Equal(t, expected, b)
231+
assert.Equal(t, expected, c.V)
231232
}
232233
}
233234

@@ -237,7 +238,7 @@ func TestGetCellValue(t *testing.T) {
237238
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
238239

239240
f.Sheet.Delete("xl/worksheets/sheet1.xml")
240-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="3"><c t="str"><v>A3</v></c></row><row><c t="str"><v>A4</v></c><c t="str"><v>B4</v></c></row><row r="7"><c t="str"><v>A7</v></c><c t="str"><v>B7</v></c></row><row><c t="str"><v>A8</v></c><c t="str"><v>B8</v></c></row>`)))
241+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="3"><c t="inlineStr"><is><t>A3</t></is></c></row><row><c t="inlineStr"><is><t>A4</t></is></c><c t="inlineStr"><is><t>B4</t></is></c></row><row r="7"><c t="inlineStr"><is><t>A7</t></is></c><c t="inlineStr"><is><t>B7</t></is></c></row><row><c t="inlineStr"><is><t>A8</t></is></c><c t="inlineStr"><is><t>B8</t></is></c></row>`)))
241242
f.checked = nil
242243
cells := []string{"A3", "A4", "B4", "A7", "B7"}
243244
rows, err := f.GetRows("Sheet1")
@@ -253,35 +254,35 @@ func TestGetCellValue(t *testing.T) {
253254
assert.NoError(t, err)
254255

255256
f.Sheet.Delete("xl/worksheets/sheet1.xml")
256-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)))
257+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="inlineStr"><is><t>A2</t></is></c></row><row r="2"><c r="B2" t="inlineStr"><is><t>B2</t></is></c></row>`)))
257258
f.checked = nil
258259
cell, err := f.GetCellValue("Sheet1", "A2")
259260
assert.Equal(t, "A2", cell)
260261
assert.NoError(t, err)
261262

262263
f.Sheet.Delete("xl/worksheets/sheet1.xml")
263-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)))
264+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="inlineStr"><is><t>A2</t></is></c></row><row r="2"><c r="B2" t="inlineStr"><is><t>B2</t></is></c></row>`)))
264265
f.checked = nil
265266
rows, err = f.GetRows("Sheet1")
266267
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
267268
assert.NoError(t, err)
268269

269270
f.Sheet.Delete("xl/worksheets/sheet1.xml")
270-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="1"><c r="A1" t="str"><v>A1</v></c></row><row r="1"><c r="B1" t="str"><v>B1</v></c></row>`)))
271+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="1"><c r="A1" t="inlineStr"><is><t>A1</t></is></c></row><row r="1"><c r="B1" t="inlineStr"><is><t>B1</t></is></c></row>`)))
271272
f.checked = nil
272273
rows, err = f.GetRows("Sheet1")
273274
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
274275
assert.NoError(t, err)
275276

276277
f.Sheet.Delete("xl/worksheets/sheet1.xml")
277-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row><c t="str"><v>A3</v></c></row><row><c t="str"><v>A4</v></c><c t="str"><v>B4</v></c></row><row r="7"><c t="str"><v>A7</v></c><c t="str"><v>B7</v></c></row><row><c t="str"><v>A8</v></c><c t="str"><v>B8</v></c></row>`)))
278+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row><c t="inlineStr"><is><t>A3</t></is></c></row><row><c t="inlineStr"><is><t>A4</t></is></c><c t="inlineStr"><is><t>B4</t></is></c></row><row r="7"><c t="inlineStr"><is><t>A7</t></is></c><c t="inlineStr"><is><t>B7</t></is></c></row><row><c t="inlineStr"><is><t>A8</t></is></c><c t="inlineStr"><is><t>B8</t></is></c></row>`)))
278279
f.checked = nil
279280
rows, err = f.GetRows("Sheet1")
280281
assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
281282
assert.NoError(t, err)
282283

283284
f.Sheet.Delete("xl/worksheets/sheet1.xml")
284-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="0"><c r="H6" t="str"><v>H6</v></c><c r="A1" t="str"><v>r0A6</v></c><c r="F4" t="str"><v>F4</v></c></row><row><c r="A1" t="str"><v>A6</v></c><c r="B1" t="str"><v>B6</v></c><c r="C1" t="str"><v>C6</v></c></row><row r="3"><c r="A3"><v>100</v></c><c r="B3" t="str"><v>B3</v></c></row>`)))
285+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="0"><c r="H6" t="inlineStr"><is><t>H6</t></is></c><c r="A1" t="inlineStr"><is><t>r0A6</t></is></c><c r="F4" t="inlineStr"><is><t>F4</t></is></c></row><row><c r="A1" t="inlineStr"><is><t>A6</t></is></c><c r="B1" t="inlineStr"><is><t>B6</t></is></c><c r="C1" t="inlineStr"><is><t>C6</t></is></c></row><row r="3"><c r="A3"><v>100</v></c><c r="B3" t="inlineStr"><is><t>B3</t></is></c></row>`)))
285286
f.checked = nil
286287
cell, err = f.GetCellValue("Sheet1", "H6")
287288
assert.Equal(t, "H6", cell)
@@ -326,8 +327,8 @@ func TestGetCellValue(t *testing.T) {
326327
<row r="25"><c r="A25"><v>275.39999999999998</v></c></row>
327328
<row r="26"><c r="A26"><v>68.900000000000006</v></c></row>
328329
<row r="27"><c r="A27"><v>1.1000000000000001</v></c></row>
329-
<row r="28"><c r="A28" t="str"><v>1234567890123_4</v></c></row>
330-
<row r="29"><c r="A29" t="str"><v>123456789_0123_4</v></c></row>
330+
<row r="28"><c r="A28" t="inlineStr"><is><t>1234567890123_4</t></is></c></row>
331+
<row r="29"><c r="A29" t="inlineStr"><is><t>123456789_0123_4</t></is></c></row>
331332
<row r="30"><c r="A30"><v>+0.0000000000000000002399999999999992E-4</v></c></row>
332333
<row r="31"><c r="A31"><v>7.2399999999999992E-2</v></c></row>
333334
<row r="32"><c r="A32" t="d"><v>20200208T080910.123</v></c></row>
@@ -386,7 +387,7 @@ func TestGetCellType(t *testing.T) {
386387
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
387388
cellType, err = f.GetCellType("Sheet1", "A1")
388389
assert.NoError(t, err)
389-
assert.Equal(t, CellTypeString, cellType)
390+
assert.Equal(t, CellTypeSharedString, cellType)
390391
_, err = f.GetCellType("Sheet1", "A")
391392
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
392393
}

col_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ func TestGetColsError(t *testing.T) {
109109

110110
f = NewFile()
111111
f.Sheet.Delete("xl/worksheets/sheet1.xml")
112-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`))
112+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="A"><c r="2" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`))
113113
f.checked = nil
114114
_, err = f.GetCols("Sheet1")
115115
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
116116

117-
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`))
117+
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`))
118118
_, err = f.GetCols("Sheet1")
119119
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
120120

@@ -124,7 +124,7 @@ func TestGetColsError(t *testing.T) {
124124
cols.totalRows = 2
125125
cols.totalCols = 2
126126
cols.curCol = 1
127-
cols.sheetXML = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="str"><v>A</v></c></row></sheetData></worksheet>`)
127+
cols.sheetXML = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`)
128128
_, err = cols.Rows()
129129
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
130130

0 commit comments

Comments
 (0)