forked from p30928647/excelize
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
This commit is contained in:
parent
14c6a198ce
commit
f44153ea46
|
@ -5223,8 +5223,8 @@ func TestCalcXLOOKUP(t *testing.T) {
|
||||||
"=XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3",
|
"=XLOOKUP(29,C2:H2,C3:H3,NA(),-1,1)": "D3",
|
||||||
}
|
}
|
||||||
for formula, expected := range formulaList {
|
for formula, expected := range formulaList {
|
||||||
assert.NoError(t, f.SetCellFormula("Sheet1", "D3", formula))
|
assert.NoError(t, f.SetCellFormula("Sheet1", "D4", formula))
|
||||||
result, err := f.CalcCellValue("Sheet1", "D3")
|
result, err := f.CalcCellValue("Sheet1", "D4")
|
||||||
assert.NoError(t, err, formula)
|
assert.NoError(t, err, formula)
|
||||||
assert.Equal(t, expected, result, formula)
|
assert.Equal(t, expected, result, formula)
|
||||||
}
|
}
|
||||||
|
|
151
cell.go
151
cell.go
|
@ -30,8 +30,10 @@ const (
|
||||||
CellTypeBool
|
CellTypeBool
|
||||||
CellTypeDate
|
CellTypeDate
|
||||||
CellTypeError
|
CellTypeError
|
||||||
|
CellTypeFormula
|
||||||
|
CellTypeInlineString
|
||||||
CellTypeNumber
|
CellTypeNumber
|
||||||
CellTypeString
|
CellTypeSharedString
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -51,9 +53,9 @@ var cellTypes = map[string]CellType{
|
||||||
"d": CellTypeDate,
|
"d": CellTypeDate,
|
||||||
"n": CellTypeNumber,
|
"n": CellTypeNumber,
|
||||||
"e": CellTypeError,
|
"e": CellTypeError,
|
||||||
"s": CellTypeString,
|
"s": CellTypeSharedString,
|
||||||
"str": CellTypeString,
|
"str": CellTypeFormula,
|
||||||
"inlineStr": CellTypeString,
|
"inlineStr": CellTypeInlineString,
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCellValue provides a function to get formatted value from cell by given
|
// 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
|
date1904 = wb.WorkbookPr.Date1904
|
||||||
}
|
}
|
||||||
var isNum bool
|
var isNum bool
|
||||||
c.T, c.V, isNum, err = setCellTime(value, date1904)
|
if isNum, err = c.setCellTime(value, date1904); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isNum {
|
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
|
// setCellTime prepares cell type and Excel time by given Go time.Time type
|
||||||
// timestamp.
|
// 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
|
var excelTime float64
|
||||||
_, offset := value.In(value.Location()).Zone()
|
_, offset := value.In(value.Location()).Zone()
|
||||||
value = value.Add(time.Duration(offset) * time.Second)
|
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
|
isNum = excelTime > 0
|
||||||
if isNum {
|
if isNum {
|
||||||
t, b = setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
|
c.setCellDefault(strconv.FormatFloat(excelTime, 'f', -1, 64))
|
||||||
} else {
|
} else {
|
||||||
t, b = setCellDefault(value.Format(time.RFC3339Nano))
|
c.setCellDefault(value.Format(time.RFC3339Nano))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -435,14 +436,14 @@ func (f *File) setSharedString(val string) (int, error) {
|
||||||
sst.Count++
|
sst.Count++
|
||||||
sst.UniqueCount++
|
sst.UniqueCount++
|
||||||
t := xlsxT{Val: val}
|
t := xlsxT{Val: val}
|
||||||
_, val, t.Space = setCellStr(val)
|
val, t.Space = trimCellValue(val)
|
||||||
sst.SI = append(sst.SI, xlsxSI{T: &t})
|
sst.SI = append(sst.SI, xlsxSI{T: &t})
|
||||||
f.sharedStringsMap[val] = sst.UniqueCount - 1
|
f.sharedStringsMap[val] = sst.UniqueCount - 1
|
||||||
return sst.UniqueCount - 1, nil
|
return sst.UniqueCount - 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCellStr provides a function to set string type to cell.
|
// trimCellValue provides a function to set string type to cell.
|
||||||
func setCellStr(value string) (t string, v string, ns xml.Attr) {
|
func trimCellValue(value string) (v string, ns xml.Attr) {
|
||||||
if len(value) > TotalCellChars {
|
if len(value) > TotalCellChars {
|
||||||
value = 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
|
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
|
// SetCellDefault provides a function to set string type value of a cell as
|
||||||
// default format without escaping the cell.
|
// default format without escaping the cell.
|
||||||
func (f *File) SetCellDefault(sheet, cell, value string) error {
|
func (f *File) SetCellDefault(sheet, cell, value string) error {
|
||||||
|
@ -476,22 +584,11 @@ func (f *File) SetCellDefault(sheet, cell, value string) error {
|
||||||
ws.Lock()
|
ws.Lock()
|
||||||
defer ws.Unlock()
|
defer ws.Unlock()
|
||||||
c.S = f.prepareCellStyle(ws, col, row, c.S)
|
c.S = f.prepareCellStyle(ws, col, row, c.S)
|
||||||
c.T, c.V = setCellDefault(value)
|
c.setCellDefault(value)
|
||||||
c.IS = nil
|
|
||||||
f.removeFormula(c, ws, sheet)
|
f.removeFormula(c, ws, sheet)
|
||||||
return err
|
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
|
// GetCellFormula provides a function to get formula from cell by given
|
||||||
// worksheet name and cell reference in spreadsheet.
|
// worksheet name and cell reference in spreadsheet.
|
||||||
func (f *File) GetCellFormula(sheet, cell string) (string, error) {
|
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.F.Ref = *opt.Ref
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.IS = nil
|
c.T, c.IS = "str", nil
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -900,7 +997,7 @@ func setRichText(runs []RichTextRun) ([]xlsxR, error) {
|
||||||
return textRuns, ErrCellCharsLength
|
return textRuns, ErrCellCharsLength
|
||||||
}
|
}
|
||||||
run := xlsxR{T: &xlsxT{}}
|
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
|
fnt := textRun.Font
|
||||||
if fnt != nil {
|
if fnt != nil {
|
||||||
run.RPr = newRpr(fnt)
|
run.RPr = newRpr(fnt)
|
||||||
|
|
23
cell_test.go
23
cell_test.go
|
@ -224,10 +224,11 @@ func TestSetCellTime(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
timezone, err := time.LoadLocation(location)
|
timezone, err := time.LoadLocation(location)
|
||||||
assert.NoError(t, err)
|
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.NoError(t, err)
|
||||||
assert.Equal(t, true, isNum)
|
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 := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
|
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
cells := []string{"A3", "A4", "B4", "A7", "B7"}
|
cells := []string{"A3", "A4", "B4", "A7", "B7"}
|
||||||
rows, err := f.GetRows("Sheet1")
|
rows, err := f.GetRows("Sheet1")
|
||||||
|
@ -253,35 +254,35 @@ func TestGetCellValue(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
cell, err := f.GetCellValue("Sheet1", "A2")
|
cell, err := f.GetCellValue("Sheet1", "A2")
|
||||||
assert.Equal(t, "A2", cell)
|
assert.Equal(t, "A2", cell)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
rows, err = f.GetRows("Sheet1")
|
rows, err = f.GetRows("Sheet1")
|
||||||
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
|
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
rows, err = f.GetRows("Sheet1")
|
rows, err = f.GetRows("Sheet1")
|
||||||
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
|
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
rows, err = f.GetRows("Sheet1")
|
rows, err = f.GetRows("Sheet1")
|
||||||
assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
|
assert.Equal(t, [][]string{{"A3"}, {"A4", "B4"}, nil, nil, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`)))
|
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>`)))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
cell, err = f.GetCellValue("Sheet1", "H6")
|
cell, err = f.GetCellValue("Sheet1", "H6")
|
||||||
assert.Equal(t, "H6", cell)
|
assert.Equal(t, "H6", cell)
|
||||||
|
@ -326,8 +327,8 @@ func TestGetCellValue(t *testing.T) {
|
||||||
<row r="25"><c r="A25"><v>275.39999999999998</v></c></row>
|
<row r="25"><c r="A25"><v>275.39999999999998</v></c></row>
|
||||||
<row r="26"><c r="A26"><v>68.900000000000006</v></c></row>
|
<row r="26"><c r="A26"><v>68.900000000000006</v></c></row>
|
||||||
<row r="27"><c r="A27"><v>1.1000000000000001</v></c></row>
|
<row r="27"><c r="A27"><v>1.1000000000000001</v></c></row>
|
||||||
<row r="28"><c r="A28" t="str"><v>1234567890123_4</v></c></row>
|
<row r="28"><c r="A28" t="inlineStr"><is><t>1234567890123_4</t></is></c></row>
|
||||||
<row r="29"><c r="A29" t="str"><v>123456789_0123_4</v></c></row>
|
<row r="29"><c r="A29" t="inlineStr"><is><t>123456789_0123_4</t></is></c></row>
|
||||||
<row r="30"><c r="A30"><v>+0.0000000000000000002399999999999992E-4</v></c></row>
|
<row r="30"><c r="A30"><v>+0.0000000000000000002399999999999992E-4</v></c></row>
|
||||||
<row r="31"><c r="A31"><v>7.2399999999999992E-2</v></c></row>
|
<row r="31"><c r="A31"><v>7.2399999999999992E-2</v></c></row>
|
||||||
<row r="32"><c r="A32" t="d"><v>20200208T080910.123</v></c></row>
|
<row r="32"><c r="A32" t="d"><v>20200208T080910.123</v></c></row>
|
||||||
|
@ -386,7 +387,7 @@ func TestGetCellType(t *testing.T) {
|
||||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
|
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A1"))
|
||||||
cellType, err = f.GetCellType("Sheet1", "A1")
|
cellType, err = f.GetCellType("Sheet1", "A1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, CellTypeString, cellType)
|
assert.Equal(t, CellTypeSharedString, cellType)
|
||||||
_, err = f.GetCellType("Sheet1", "A")
|
_, err = f.GetCellType("Sheet1", "A")
|
||||||
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,12 +109,12 @@ func TestGetColsError(t *testing.T) {
|
||||||
|
|
||||||
f = NewFile()
|
f = NewFile()
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
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>`))
|
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>`))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
_, err = f.GetCols("Sheet1")
|
_, err = f.GetCols("Sheet1")
|
||||||
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
|
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
|
||||||
|
|
||||||
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>`))
|
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>`))
|
||||||
_, err = f.GetCols("Sheet1")
|
_, err = f.GetCols("Sheet1")
|
||||||
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ func TestGetColsError(t *testing.T) {
|
||||||
cols.totalRows = 2
|
cols.totalRows = 2
|
||||||
cols.totalCols = 2
|
cols.totalCols = 2
|
||||||
cols.curCol = 1
|
cols.curCol = 1
|
||||||
cols.sheetXML = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="str"><v>A</v></c></row></sheetData></worksheet>`)
|
cols.sheetXML = []byte(`<worksheet><sheetData><row r="1"><c r="A" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`)
|
||||||
_, err = cols.Rows()
|
_, err = cols.Rows()
|
||||||
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
|
|
||||||
|
|
77
rows.go
77
rows.go
|
@ -20,8 +20,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mohae/deepcopy"
|
"github.com/mohae/deepcopy"
|
||||||
)
|
)
|
||||||
|
@ -449,81 +447,6 @@ func (f *File) sharedStringsReader() *xlsxSST {
|
||||||
return f.SharedStrings
|
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
|
// 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:
|
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
|
||||||
//
|
//
|
||||||
|
|
|
@ -203,12 +203,12 @@ func TestColumns(t *testing.T) {
|
||||||
_, err = rows.Columns()
|
_, err = rows.Columns()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="A"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
|
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="A"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`)))
|
||||||
assert.True(t, rows.Next())
|
assert.True(t, rows.Next())
|
||||||
_, err = rows.Columns()
|
_, err = rows.Columns()
|
||||||
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
|
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
|
||||||
|
|
||||||
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
|
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="inlineStr"><is><t>B</t></is></c></row></sheetData></worksheet>`)))
|
||||||
_, err = rows.Columns()
|
_, err = rows.Columns()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -76,18 +76,18 @@ func TestSearchSheet(t *testing.T) {
|
||||||
|
|
||||||
f = NewFile()
|
f = NewFile()
|
||||||
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
f.Sheet.Delete("xl/worksheets/sheet1.xml")
|
||||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>A</v></c></row></sheetData></worksheet>`))
|
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="A"><c r="2" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`))
|
||||||
f.checked = nil
|
f.checked = nil
|
||||||
result, err = f.SearchSheet("Sheet1", "A")
|
result, err = f.SearchSheet("Sheet1", "A")
|
||||||
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
|
assert.EqualError(t, err, "strconv.Atoi: parsing \"A\": invalid syntax")
|
||||||
assert.Equal(t, []string(nil), result)
|
assert.Equal(t, []string(nil), result)
|
||||||
|
|
||||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>A</v></c></row></sheetData></worksheet>`))
|
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`))
|
||||||
result, err = f.SearchSheet("Sheet1", "A")
|
result, err = f.SearchSheet("Sheet1", "A")
|
||||||
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
|
||||||
assert.Equal(t, []string(nil), result)
|
assert.Equal(t, []string(nil), result)
|
||||||
|
|
||||||
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="0"><c r="A1" t="str"><v>A</v></c></row></sheetData></worksheet>`))
|
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="0"><c r="A1" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`))
|
||||||
result, err = f.SearchSheet("Sheet1", "A")
|
result, err = f.SearchSheet("Sheet1", "A")
|
||||||
assert.EqualError(t, err, "invalid cell reference [1, 0]")
|
assert.EqualError(t, err, "invalid cell reference [1, 0]")
|
||||||
assert.Equal(t, []string(nil), result)
|
assert.Equal(t, []string(nil), result)
|
||||||
|
|
38
stream.go
38
stream.go
|
@ -263,7 +263,7 @@ func (sw *StreamWriter) getRowValues(hRow, hCol, vCol int) (res []string, err er
|
||||||
if col < hCol || col > vCol {
|
if col < hCol || col > vCol {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res[col-hCol] = c.V
|
res[col-hCol], _ = c.getValueFrom(sw.File, nil, false)
|
||||||
}
|
}
|
||||||
return res, nil
|
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.
|
// setCellFormula provides a function to set formula of a cell.
|
||||||
func setCellFormula(c *xlsxC, formula string) {
|
func setCellFormula(c *xlsxC, formula string) {
|
||||||
if formula != "" {
|
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:
|
case float64:
|
||||||
c.T, c.V = setCellFloat(val, -1, 64)
|
c.T, c.V = setCellFloat(val, -1, 64)
|
||||||
case string:
|
case string:
|
||||||
c.T, c.V, c.XMLSpace = setCellStr(val)
|
c.setCellValue(val)
|
||||||
case []byte:
|
case []byte:
|
||||||
c.T, c.V, c.XMLSpace = setCellStr(string(val))
|
c.setCellValue(string(val))
|
||||||
case time.Duration:
|
case time.Duration:
|
||||||
c.T, c.V = setCellDuration(val)
|
c.T, c.V = setCellDuration(val)
|
||||||
case time.Time:
|
case time.Time:
|
||||||
|
@ -488,20 +488,19 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
|
||||||
if wb != nil && wb.WorkbookPr != nil {
|
if wb != nil && wb.WorkbookPr != nil {
|
||||||
date1904 = wb.WorkbookPr.Date1904
|
date1904 = wb.WorkbookPr.Date1904
|
||||||
}
|
}
|
||||||
c.T, c.V, isNum, err = setCellTime(val, date1904)
|
if isNum, err = c.setCellTime(val, date1904); isNum && c.S == 0 {
|
||||||
if isNum && c.S == 0 {
|
|
||||||
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
|
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
|
||||||
c.S = style
|
c.S = style
|
||||||
}
|
}
|
||||||
case bool:
|
case bool:
|
||||||
c.T, c.V = setCellBool(val)
|
c.T, c.V = setCellBool(val)
|
||||||
case nil:
|
case nil:
|
||||||
c.T, c.V, c.XMLSpace = setCellStr("")
|
c.setCellValue("")
|
||||||
case []RichTextRun:
|
case []RichTextRun:
|
||||||
c.T, c.IS = "inlineStr", &xlsxSI{}
|
c.T, c.IS = "inlineStr", &xlsxSI{}
|
||||||
c.IS.R, err = setRichText(val)
|
c.IS.R, err = setRichText(val)
|
||||||
default:
|
default:
|
||||||
c.T, c.V, c.XMLSpace = setCellStr(fmt.Sprint(val))
|
c.setCellValue(fmt.Sprint(val))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -569,10 +568,25 @@ func writeCell(buf *bufferedWriter, c xlsxC) {
|
||||||
_, _ = buf.WriteString(`</v>`)
|
_, _ = buf.WriteString(`</v>`)
|
||||||
}
|
}
|
||||||
if c.IS != nil {
|
if c.IS != nil {
|
||||||
is, _ := xml.Marshal(c.IS.R)
|
if len(c.IS.R) > 0 {
|
||||||
_, _ = buf.WriteString(`<is>`)
|
is, _ := xml.Marshal(c.IS.R)
|
||||||
_, _ = buf.Write(is)
|
_, _ = buf.WriteString(`<is>`)
|
||||||
_, _ = buf.WriteString(`</is>`)
|
_, _ = buf.Write(is)
|
||||||
|
_, _ = buf.WriteString(`</is>`)
|
||||||
|
}
|
||||||
|
if c.IS.T != nil {
|
||||||
|
_, _ = buf.WriteString(`<is><t`)
|
||||||
|
if c.IS.T.Space.Value != "" {
|
||||||
|
_, _ = buf.WriteString(` xml:`)
|
||||||
|
_, _ = buf.WriteString(c.IS.T.Space.Name.Local)
|
||||||
|
_, _ = buf.WriteString(`="`)
|
||||||
|
_, _ = buf.WriteString(c.IS.T.Space.Value)
|
||||||
|
_, _ = buf.WriteString(`"`)
|
||||||
|
}
|
||||||
|
_, _ = buf.WriteString(`>`)
|
||||||
|
_, _ = buf.Write([]byte(c.IS.T.Val))
|
||||||
|
_, _ = buf.WriteString(`</t></is>`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, _ = buf.WriteString(`</c>`)
|
_, _ = buf.WriteString(`</c>`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue