diff --git a/calc_test.go b/calc_test.go
index 1a8b8c62..5d61712f 100644
--- a/calc_test.go
+++ b/calc_test.go
@@ -5223,8 +5223,8 @@ func TestCalcXLOOKUP(t *testing.T) {
"=XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3",
}
for formula, expected := range formulaList {
- assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
- result, err := f.CalcCellValue("Sheet1", "D3")
+ assert.NoError(t, f.SetCellFormula("Sheet1", "D4", formula))
+ result, err := f.CalcCellValue("Sheet1", "D4")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
diff --git a/cell.go b/cell.go
index 3fcbb7b3..6ed7f485 100644
--- a/cell.go
+++ b/cell.go
@@ -30,8 +30,10 @@ const (
CellTypeBool
CellTypeDate
CellTypeError
+ CellTypeFormula
+ CellTypeInlineString
CellTypeNumber
- CellTypeString
+ CellTypeSharedString
)
const (
@@ -51,9 +53,9 @@ var cellTypes = map[string]CellType{
"d": CellTypeDate,
"n": CellTypeNumber,
"e": CellTypeError,
- "s": CellTypeString,
- "str": CellTypeString,
- "inlineStr": CellTypeString,
+ "s": CellTypeSharedString,
+ "str": CellTypeFormula,
+ "inlineStr": CellTypeInlineString,
}
// 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 {
date1904 = wb.WorkbookPr.Date1904
}
var isNum bool
- c.T, c.V, isNum, err = setCellTime(value, date1904)
- if err != nil {
+ if isNum, err = c.setCellTime(value, date1904); err != nil {
return err
}
if isNum {
@@ -247,7 +248,7 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
// setCellTime prepares cell type and Excel time by given Go time.Time type
// timestamp.
-func setCellTime(value time.Time, date1904 bool) (t string, b string, isNum bool, err error) {
+func (c *xlsxC) setCellTime(value time.Time, date1904 bool) (isNum bool, err error) {
var excelTime float64
_, offset := value.In(value.Location()).Zone()
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
}
isNum = excelTime > 0
if isNum {
- t, b = setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
+ c.setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
} else {
- t, b = setCellDefault(value.Format(time.RFC3339Nano))
+ c.setCellDefault(value.Format(time.RFC3339Nano))
}
return
}
@@ -435,14 +436,14 @@ func (f *File) setSharedString(val string) (int, error) {
sst.Count++
sst.UniqueCount++
t := xlsxT{Val: val}
- _, val, t.Space = setCellStr(val)
+ val, t.Space = trimCellValue(val)
sst.SI = append(sst.SI, xlsxSI{T: &t})
f.sharedStringsMap[val] = sst.UniqueCount - 1
return sst.UniqueCount - 1, nil
}
-// setCellStr provides a function to set string type to cell.
-func setCellStr(value string) (t string, v string, ns xml.Attr) {
+// trimCellValue provides a function to set string type to cell.
+func trimCellValue(value string) (v string, ns xml.Attr) {
if len(value) > TotalCellChars {
value = value[:TotalCellChars]
}
@@ -458,10 +459,117 @@ func setCellStr(value string) (t string, v string, ns xml.Attr) {
}
}
}
- t, v = "str", bstrMarshal(value)
+ v = bstrMarshal(value)
return
}
+// setCellValue set cell data type and value for (inline) rich string cell or
+// formula cell.
+func (c *xlsxC) setCellValue(val string) {
+ if c.F != nil {
+ c.setStr(val)
+ return
+ }
+ c.setInlineStr(val)
+}
+
+// setInlineStr set cell data type and value which containing an (inline) rich
+// string.
+func (c *xlsxC) setInlineStr(val string) {
+ c.T, c.V, c.IS = "inlineStr", "", &xlsxSI{T: &xlsxT{}}
+ c.IS.T.Val, c.IS.T.Space = trimCellValue(val)
+}
+
+// setStr set cell data type and value which containing a formula string.
+func (c *xlsxC) setStr(val string) {
+ c.T, c.IS = "str", nil
+ c.V, c.XMLSpace = trimCellValue(val)
+}
+
+// getCellDate parse cell value which containing a boolean.
+func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
+ if !raw {
+ if c.V == "1" {
+ return "TRUE", nil
+ }
+ if c.V == "0" {
+ return "FALSE", nil
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+}
+
+// setCellDefault prepares cell type and string type cell value by a given
+// string.
+func (c *xlsxC) setCellDefault(value string) {
+ if ok, _, _ := isNumeric(value); !ok {
+ c.setInlineStr(value)
+ c.IS.T.Val = value
+ return
+ }
+ c.V = value
+}
+
+// getCellDate parse cell value which contains a date in the ISO 8601 format.
+func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
+ if !raw {
+ layout := "20060102T150405.999"
+ if strings.HasSuffix(c.V, "Z") {
+ layout = "20060102T150405Z"
+ if strings.Contains(c.V, "-") {
+ layout = "2006-01-02T15:04:05Z"
+ }
+ } else if strings.Contains(c.V, "-") {
+ layout = "2006-01-02 15:04:05Z"
+ }
+ if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil {
+ excelTime, _ := timeToExcelTime(timestamp, false)
+ c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+}
+
+// getValueFrom return a value from a column/row cell, this function is
+// intended to be used with for range on rows an argument with the spreadsheet
+// opened file.
+func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
+ f.Lock()
+ defer f.Unlock()
+ switch c.T {
+ case "b":
+ return c.getCellBool(f, raw)
+ case "d":
+ return c.getCellDate(f, raw)
+ case "s":
+ if c.V != "" {
+ xlsxSI := 0
+ xlsxSI, _ = strconv.Atoi(c.V)
+ if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
+ return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil
+ }
+ if len(d.SI) > xlsxSI {
+ return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+ case "inlineStr":
+ if c.IS != nil {
+ return f.formattedValue(c.S, c.IS.String(), raw), nil
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+ default:
+ if isNum, precision, decimal := isNumeric(c.V); isNum && !raw {
+ if precision > 15 {
+ c.V = strconv.FormatFloat(decimal, 'G', 15, 64)
+ } else {
+ c.V = strconv.FormatFloat(decimal, 'f', -1, 64)
+ }
+ }
+ return f.formattedValue(c.S, c.V, raw), nil
+ }
+}
+
// SetCellDefault provides a function to set string type value of a cell as
// default format without escaping the cell.
func (f *File) SetCellDefault(sheet, cell, value string) error {
@@ -476,22 +584,11 @@ func (f *File) SetCellDefault(sheet, cell, value string) error {
ws.Lock()
defer ws.Unlock()
c.S = f.prepareCellStyle(ws, col, row, c.S)
- c.T, c.V = setCellDefault(value)
- c.IS = nil
+ c.setCellDefault(value)
f.removeFormula(c, ws, sheet)
return err
}
-// setCellDefault prepares cell type and string type cell value by a given
-// string.
-func setCellDefault(value string) (t string, v string) {
- if ok, _, _ := isNumeric(value); !ok {
- t = "str"
- }
- v = value
- return
-}
-
// GetCellFormula provides a function to get formula from cell by given
// worksheet name and cell reference in spreadsheet.
func (f *File) GetCellFormula(sheet, cell string) (string, error) {
@@ -625,7 +722,7 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts)
c.F.Ref = *opt.Ref
}
}
- c.IS = nil
+ c.T, c.IS = "str", nil
return err
}
@@ -900,7 +997,7 @@ func setRichText(runs []RichTextRun) ([]xlsxR, error) {
return textRuns, ErrCellCharsLength
}
run := xlsxR{T: &xlsxT{}}
- _, run.T.Val, run.T.Space = setCellStr(textRun.Text)
+ run.T.Val, run.T.Space = trimCellValue(textRun.Text)
fnt := textRun.Font
if fnt != nil {
run.RPr = newRpr(fnt)
diff --git a/cell_test.go b/cell_test.go
index 980058a3..f7412111 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -224,10 +224,11 @@ func TestSetCellTime(t *testing.T) {
} {
timezone, err := time.LoadLocation(location)
assert.NoError(t, err)
- _, b, isNum, err := setCellTime(date.In(timezone), false)
+ c := &xlsxC{}
+ isNum, err := c.setCellTime(date.In(timezone), false)
assert.NoError(t, err)
assert.Equal(t, true, isNum)
- assert.Equal(t, expected, b)
+ assert.Equal(t, expected, c.V)
}
}
@@ -237,7 +238,7 @@ func TestGetCellValue(t *testing.T) {
sheetData := `%s`
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
f.checked = nil
cells := []string{"A3", "A4", "B4", "A7", "B7"}
rows, err := f.GetRows("Sheet1")
@@ -253,35 +254,35 @@ func TestGetCellValue(t *testing.T) {
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
f.checked = nil
cell, err := f.GetCellValue("Sheet1", "A2")
assert.Equal(t, "A2", cell)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2
B2
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1
B1
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1
B1
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3
A4B4
A7B7
A8B8
`)))
f.checked = nil
rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
assert.NoError(t, err)
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `H6r0A6F4
A6B6C6
100B3
`)))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `H6r0A6F4
A6B6C6
100B3
`)))
f.checked = nil
cell, err = f.GetCellValue("Sheet1", "H6")
assert.Equal(t, "H6", cell)
@@ -326,8 +327,8 @@ func TestGetCellValue(t *testing.T) {
275.39999999999998
68.900000000000006
1.1000000000000001
- 1234567890123_4
- 123456789_0123_4
+ 1234567890123_4
+ 123456789_0123_4
+0.0000000000000000002399999999999992E-4
7.2399999999999992E-2
20200208T080910.123
@@ -386,7 +387,7 @@ func TestGetCellType(t *testing.T) {
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
cellType, err = f.GetCellType("Sheet1", "A1")
assert.NoError(t, err)
- assert.Equal(t, CellTypeString, cellType)
+ assert.Equal(t, CellTypeSharedString, cellType)
_, err = f.GetCellType("Sheet1", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
diff --git a/col_test.go b/col_test.go
index 75c191b9..f7863357 100644
--- a/col_test.go
+++ b/col_test.go
@@ -109,12 +109,12 @@ func TestGetColsError(t *testing.T) {
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
f.checked = nil
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B
`))
_, err = f.GetCols("Sheet1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
@@ -124,7 +124,7 @@ func TestGetColsError(t *testing.T) {
cols.totalRows = 2
cols.totalCols = 2
cols.curCol = 1
- cols.sheetXML = []byte(`A
`)
+ cols.sheetXML = []byte(`A
`)
_, err = cols.Rows()
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
diff --git a/rows.go b/rows.go
index 9f791cb8..4f05f243 100644
--- a/rows.go
+++ b/rows.go
@@ -20,8 +20,6 @@ import (
"math"
"os"
"strconv"
- "strings"
- "time"
"github.com/mohae/deepcopy"
)
@@ -449,81 +447,6 @@ func (f *File) sharedStringsReader() *xlsxSST {
return f.SharedStrings
}
-// getCellDate parse cell value which containing a boolean.
-func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
- if !raw {
- if c.V == "1" {
- return "TRUE", nil
- }
- if c.V == "0" {
- return "FALSE", nil
- }
- }
- return f.formattedValue(c.S, c.V, raw), nil
-}
-
-// getCellDate parse cell value which contains a date in the ISO 8601 format.
-func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
- if !raw {
- layout := "20060102T150405.999"
- if strings.HasSuffix(c.V, "Z") {
- layout = "20060102T150405Z"
- if strings.Contains(c.V, "-") {
- layout = "2006-01-02T15:04:05Z"
- }
- } else if strings.Contains(c.V, "-") {
- layout = "2006-01-02 15:04:05Z"
- }
- if timestamp, err := time.Parse(layout, strings.ReplaceAll(c.V, ",", ".")); err == nil {
- excelTime, _ := timeToExcelTime(timestamp, false)
- c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
- }
- }
- return f.formattedValue(c.S, c.V, raw), nil
-}
-
-// getValueFrom return a value from a column/row cell, this function is
-// intended to be used with for range on rows an argument with the spreadsheet
-// opened file.
-func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
- f.Lock()
- defer f.Unlock()
- switch c.T {
- case "b":
- return c.getCellBool(f, raw)
- case "d":
- return c.getCellDate(f, raw)
- case "s":
- if c.V != "" {
- xlsxSI := 0
- xlsxSI, _ = strconv.Atoi(c.V)
- if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
- return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil
- }
- if len(d.SI) > xlsxSI {
- return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil
- }
- }
- return f.formattedValue(c.S, c.V, raw), nil
- case "str":
- return f.formattedValue(c.S, c.V, raw), nil
- case "inlineStr":
- if c.IS != nil {
- return f.formattedValue(c.S, c.IS.String(), raw), nil
- }
- return f.formattedValue(c.S, c.V, raw), nil
- default:
- if isNum, precision, decimal := isNumeric(c.V); isNum && !raw {
- if precision > 15 {
- c.V = strconv.FormatFloat(decimal, 'G', 15, 64)
- } else {
- c.V = strconv.FormatFloat(decimal, 'f', -1, 64)
- }
- }
- return f.formattedValue(c.S, c.V, raw), nil
- }
-}
-
// SetRowVisible provides a function to set visible of a single row by given
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
//
diff --git a/rows_test.go b/rows_test.go
index 423932f8..81572e18 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -203,12 +203,12 @@ func TestColumns(t *testing.T) {
_, err = rows.Columns()
assert.NoError(t, err)
- rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
+ rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
assert.True(t, rows.Next())
_, err = rows.Columns()
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
- rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
+ rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1
B
`)))
_, err = rows.Columns()
assert.NoError(t, err)
diff --git a/sheet_test.go b/sheet_test.go
index 6e87de9c..4e1e4481 100644
--- a/sheet_test.go
+++ b/sheet_test.go
@@ -76,18 +76,18 @@ func TestSearchSheet(t *testing.T) {
f = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml")
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
f.checked = nil
result, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
assert.Equal(t, []string(nil), result)
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
result, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.Equal(t, []string(nil), result)
- f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
+ f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A
`))
result, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "invalid cell reference [1, 0]")
assert.Equal(t, []string(nil), result)
diff --git a/stream.go b/stream.go
index aaa45893..fa78d8bb 100644
--- a/stream.go
+++ b/stream.go
@@ -263,7 +263,7 @@ func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err er
if col < hCol || col > vCol {
continue
}
- res[col-hCol] = c.V
+ res[col-hCol], _ = c.getValueFrom(sw.File, nil, false)
}
return res, nil
}
@@ -462,7 +462,7 @@ func (sw *StreamWriter) MergeCell(hCell, vCell string) error {
// setCellFormula provides a function to set formula of a cell.
func setCellFormula(c *xlsxC, formula string) {
if formula != "" {
- c.F = &xlsxF{Content: formula}
+ c.T, c.F = "str", &xlsxF{Content: formula}
}
}
@@ -477,9 +477,9 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
case float64:
c.T, c.V = setCellFloat(val, -1, 64)
case string:
- c.T, c.V, c.XMLSpace = setCellStr(val)
+ c.setCellValue(val)
case []byte:
- c.T, c.V, c.XMLSpace = setCellStr(string(val))
+ c.setCellValue(string(val))
case time.Duration:
c.T, c.V = setCellDuration(val)
case time.Time:
@@ -488,20 +488,19 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
- c.T, c.V, isNum, err = setCellTime(val, date1904)
- if isNum && c.S == 0 {
+ if isNum, err = c.setCellTime(val, date1904); isNum && c.S == 0 {
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
c.S = style
}
case bool:
c.T, c.V = setCellBool(val)
case nil:
- c.T, c.V, c.XMLSpace = setCellStr("")
+ c.setCellValue("")
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))
+ c.setCellValue(fmt.Sprint(val))
}
return err
}
@@ -569,10 +568,25 @@ func writeCell(buf *bufferedWriter, c xlsxC) {
_, _ = buf.WriteString(``)
}
if c.IS != nil {
- is, _ := xml.Marshal(c.IS.R)
- _, _ = buf.WriteString(``)
- _, _ = buf.Write(is)
- _, _ = buf.WriteString(``)
+ if len(c.IS.R) > 0 {
+ is, _ := xml.Marshal(c.IS.R)
+ _, _ = buf.WriteString(``)
+ _, _ = buf.Write(is)
+ _, _ = buf.WriteString(``)
+ }
+ if c.IS.T != nil {
+ _, _ = buf.WriteString(``)
+ _, _ = buf.Write([]byte(c.IS.T.Val))
+ _, _ = buf.WriteString(``)
+ }
}
_, _ = buf.WriteString(``)
}