2018-01-19 17:32:54 +08:00
|
|
|
package excelize
|
|
|
|
|
2018-12-27 18:51:44 +08:00
|
|
|
import (
|
2019-03-21 11:41:46 +08:00
|
|
|
"fmt"
|
2019-09-01 12:30:14 +08:00
|
|
|
"path/filepath"
|
2020-04-10 00:04:23 +08:00
|
|
|
"strconv"
|
2020-08-15 00:09:50 +08:00
|
|
|
"sync"
|
2018-12-27 18:51:44 +08:00
|
|
|
"testing"
|
2019-10-26 20:55:24 +08:00
|
|
|
"time"
|
2018-12-27 18:51:44 +08:00
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
2018-01-19 17:32:54 +08:00
|
|
|
|
2020-08-15 00:09:50 +08:00
|
|
|
func TestConcurrency(t *testing.T) {
|
|
|
|
f := NewFile()
|
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
for i := 1; i <= 5; i++ {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(val int) {
|
|
|
|
f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val)
|
|
|
|
f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val))
|
|
|
|
f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
|
|
|
|
wg.Done()
|
|
|
|
}(i)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
val, err := f.GetCellValue("Sheet1", "A1")
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
assert.Equal(t, "1", val)
|
|
|
|
}
|
|
|
|
|
2018-01-19 17:32:54 +08:00
|
|
|
func TestCheckCellInArea(t *testing.T) {
|
2019-12-14 19:57:37 +08:00
|
|
|
f := NewFile()
|
2018-01-19 17:32:54 +08:00
|
|
|
expectedTrueCellInAreaList := [][2]string{
|
2018-01-31 11:12:43 +08:00
|
|
|
{"c2", "A1:AAZ32"},
|
|
|
|
{"B9", "A1:B9"},
|
|
|
|
{"C2", "C2:C2"},
|
2018-01-19 17:32:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, expectedTrueCellInArea := range expectedTrueCellInAreaList {
|
|
|
|
cell := expectedTrueCellInArea[0]
|
|
|
|
area := expectedTrueCellInArea[1]
|
2019-12-14 19:57:37 +08:00
|
|
|
ok, err := f.checkCellInArea(cell, area)
|
2019-03-23 20:08:06 +08:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Truef(t, ok,
|
2018-12-27 18:51:44 +08:00
|
|
|
"Expected cell %v to be in area %v, got false\n", cell, area)
|
2018-01-19 17:32:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
expectedFalseCellInAreaList := [][2]string{
|
2018-01-31 11:12:43 +08:00
|
|
|
{"c2", "A4:AAZ32"},
|
|
|
|
{"C4", "D6:A1"}, // weird case, but you never know
|
|
|
|
{"AEF42", "BZ40:AEF41"},
|
2018-01-19 17:32:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, expectedFalseCellInArea := range expectedFalseCellInAreaList {
|
|
|
|
cell := expectedFalseCellInArea[0]
|
|
|
|
area := expectedFalseCellInArea[1]
|
2019-12-14 19:57:37 +08:00
|
|
|
ok, err := f.checkCellInArea(cell, area)
|
2019-03-23 20:08:06 +08:00
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Falsef(t, ok,
|
2018-12-27 18:51:44 +08:00
|
|
|
"Expected cell %v not to be inside of area %v, but got true\n", cell, area)
|
2018-01-19 17:32:54 +08:00
|
|
|
}
|
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering
Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes.
But have to go deeper, do fixes, after do related fixes and again and again.
Major improvements:
1. Tests made stronger again (But still be weak).
2. "Empty" returns for incorrect input replaces with panic.
3. Check for correct col/row/cell naming & addressing by default.
4. Removed huge amount of duplicated code.
5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all,
and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName().
6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc).
* Formatting fixes
2019-03-20 00:14:41 +08:00
|
|
|
|
2019-12-14 19:57:37 +08:00
|
|
|
ok, err := f.checkCellInArea("A1", "A:B")
|
|
|
|
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
assert.False(t, ok)
|
|
|
|
|
|
|
|
ok, err = f.checkCellInArea("AA0", "Z0:AB1")
|
2019-03-23 20:08:06 +08:00
|
|
|
assert.EqualError(t, err, `cannot convert cell "AA0" to coordinates: invalid cell name "AA0"`)
|
|
|
|
assert.False(t, ok)
|
2018-01-19 17:32:54 +08:00
|
|
|
}
|
2019-03-21 11:41:46 +08:00
|
|
|
|
|
|
|
func TestSetCellFloat(t *testing.T) {
|
|
|
|
sheet := "Sheet1"
|
|
|
|
t.Run("with no decimal", func(t *testing.T) {
|
|
|
|
f := NewFile()
|
2019-12-24 01:09:28 +08:00
|
|
|
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.0, -1, 64))
|
|
|
|
assert.NoError(t, f.SetCellFloat(sheet, "A2", 123.0, 1, 64))
|
2019-03-23 20:08:06 +08:00
|
|
|
val, err := f.GetCellValue(sheet, "A1")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "123", val, "A1 should be 123")
|
|
|
|
val, err = f.GetCellValue(sheet, "A2")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "123.0", val, "A2 should be 123.0")
|
2019-03-21 11:41:46 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("with a decimal and precision limit", func(t *testing.T) {
|
|
|
|
f := NewFile()
|
2019-12-24 01:09:28 +08:00
|
|
|
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, 1, 64))
|
2019-03-23 20:08:06 +08:00
|
|
|
val, err := f.GetCellValue(sheet, "A1")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "123.4", val, "A1 should be 123.4")
|
2019-03-21 11:41:46 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("with a decimal and no limit", func(t *testing.T) {
|
|
|
|
f := NewFile()
|
2019-12-24 01:09:28 +08:00
|
|
|
assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, -1, 64))
|
2019-03-23 20:08:06 +08:00
|
|
|
val, err := f.GetCellValue(sheet, "A1")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "123.42", val, "A1 should be 123.42")
|
2019-03-21 11:41:46 +08:00
|
|
|
})
|
2019-10-26 20:55:24 +08:00
|
|
|
f := NewFile()
|
|
|
|
assert.EqualError(t, f.SetCellFloat(sheet, "A", 123.42, -1, 64), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetCellValue(t *testing.T) {
|
|
|
|
f := NewFile()
|
|
|
|
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
}
|
|
|
|
|
2020-10-04 21:07:39 +08:00
|
|
|
func TestSetCellValues(t *testing.T) {
|
|
|
|
f := NewFile()
|
|
|
|
err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
v, err := f.GetCellValue("Sheet1", "A1")
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, v, "12/31/10 12:00")
|
|
|
|
|
|
|
|
// test date value lower than min date supported by Excel
|
|
|
|
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = f.GetCellValue("Sheet1", "A1")
|
|
|
|
assert.EqualError(t, err, `strconv.ParseFloat: parsing "1600-12-31T00:00:00Z": invalid syntax`)
|
|
|
|
}
|
|
|
|
|
2019-10-26 20:55:24 +08:00
|
|
|
func TestSetCellBool(t *testing.T) {
|
|
|
|
f := NewFile()
|
|
|
|
assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
}
|
|
|
|
|
2020-06-27 00:02:47 +08:00
|
|
|
func TestGetCellValue(t *testing.T) {
|
|
|
|
// Test get cell value without r attribute of the row.
|
|
|
|
f := NewFile()
|
2020-11-18 22:08:40 +08:00
|
|
|
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
|
2020-06-27 00:02:47 +08:00
|
|
|
delete(f.Sheet, "xl/worksheets/sheet1.xml")
|
2020-11-18 22:08:40 +08:00
|
|
|
f.XLSX["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>`))
|
2020-06-27 00:02:47 +08:00
|
|
|
f.checked = nil
|
|
|
|
cells := []string{"A3", "A4", "B4", "A7", "B7"}
|
|
|
|
rows, err := f.GetRows("Sheet1")
|
|
|
|
assert.Equal(t, [][]string{nil, nil, {"A3"}, {"A4", "B4"}, nil, nil, {"A7", "B7"}, {"A8", "B8"}}, rows)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
for _, cell := range cells {
|
|
|
|
value, err := f.GetCellValue("Sheet1", cell)
|
|
|
|
assert.Equal(t, cell, value)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
2020-06-28 00:02:32 +08:00
|
|
|
cols, err := f.GetCols("Sheet1")
|
|
|
|
assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols)
|
|
|
|
assert.NoError(t, err)
|
2020-11-18 22:08:40 +08:00
|
|
|
delete(f.Sheet, "xl/worksheets/sheet1.xml")
|
|
|
|
f.XLSX["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.checked = nil
|
|
|
|
cell, err := f.GetCellValue("Sheet1", "A2")
|
|
|
|
assert.Equal(t, "A2", cell)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
delete(f.Sheet, "xl/worksheets/sheet1.xml")
|
|
|
|
f.XLSX["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.checked = nil
|
|
|
|
rows, err = f.GetRows("Sheet1")
|
|
|
|
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
delete(f.Sheet, "xl/worksheets/sheet1.xml")
|
|
|
|
f.XLSX["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.checked = nil
|
|
|
|
rows, err = f.GetRows("Sheet1")
|
|
|
|
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
|
|
|
|
assert.NoError(t, err)
|
2020-06-27 00:02:47 +08:00
|
|
|
}
|
|
|
|
|
2019-10-26 20:55:24 +08:00
|
|
|
func TestGetCellFormula(t *testing.T) {
|
2019-12-22 00:02:09 +08:00
|
|
|
// Test get cell formula on not exist worksheet.
|
2019-10-26 20:55:24 +08:00
|
|
|
f := NewFile()
|
2019-12-22 00:02:09 +08:00
|
|
|
_, err := f.GetCellFormula("SheetN", "A1")
|
|
|
|
assert.EqualError(t, err, "sheet SheetN is not exist")
|
|
|
|
|
|
|
|
// Test get cell formula on no formula cell.
|
2019-12-24 01:09:28 +08:00
|
|
|
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
|
2019-12-22 00:02:09 +08:00
|
|
|
_, err = f.GetCellFormula("Sheet1", "A1")
|
|
|
|
assert.NoError(t, err)
|
2019-10-26 20:55:24 +08:00
|
|
|
}
|
|
|
|
|
2019-03-21 11:41:46 +08:00
|
|
|
func ExampleFile_SetCellFloat() {
|
|
|
|
f := NewFile()
|
2019-03-21 14:09:25 +08:00
|
|
|
var x = 3.14159265
|
2019-12-24 01:09:28 +08:00
|
|
|
if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil {
|
2020-02-19 00:08:10 +08:00
|
|
|
fmt.Println(err)
|
2019-12-24 01:09:28 +08:00
|
|
|
}
|
2019-03-23 20:08:06 +08:00
|
|
|
val, _ := f.GetCellValue("Sheet1", "A1")
|
|
|
|
fmt.Println(val)
|
2019-03-21 11:41:46 +08:00
|
|
|
// Output: 3.14
|
|
|
|
}
|
2019-04-16 14:50:16 +08:00
|
|
|
|
|
|
|
func BenchmarkSetCellValue(b *testing.B) {
|
|
|
|
values := []string{"First", "Second", "Third", "Fourth", "Fifth", "Sixth"}
|
|
|
|
cols := []string{"A", "B", "C", "D", "E", "F"}
|
|
|
|
f := NewFile()
|
|
|
|
b.ResetTimer()
|
2020-04-10 00:04:23 +08:00
|
|
|
for i := 1; i <= b.N; i++ {
|
2019-04-16 14:50:16 +08:00
|
|
|
for j := 0; j < len(values); j++ {
|
2020-04-10 00:04:23 +08:00
|
|
|
if err := f.SetCellValue("Sheet1", cols[j]+strconv.Itoa(i), values[j]); err != nil {
|
2019-12-24 01:09:28 +08:00
|
|
|
b.Error(err)
|
|
|
|
}
|
2019-04-16 14:50:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-01 12:30:14 +08:00
|
|
|
|
|
|
|
func TestOverflowNumericCell(t *testing.T) {
|
|
|
|
f, err := OpenFile(filepath.Join("test", "OverflowNumericCell.xlsx"))
|
|
|
|
if !assert.NoError(t, err) {
|
|
|
|
t.FailNow()
|
|
|
|
}
|
2019-09-02 21:52:55 +08:00
|
|
|
val, err := f.GetCellValue("Sheet1", "A1")
|
2019-09-01 12:30:14 +08:00
|
|
|
assert.NoError(t, err)
|
2019-09-02 21:52:55 +08:00
|
|
|
// GOARCH=amd64 - all ok; GOARCH=386 - actual: "-2147483648"
|
2019-09-01 12:30:14 +08:00
|
|
|
assert.Equal(t, "8595602512225", val, "A1 should be 8595602512225")
|
|
|
|
}
|
2020-04-06 00:23:27 +08:00
|
|
|
|
|
|
|
func TestSetCellRichText(t *testing.T) {
|
|
|
|
f := NewFile()
|
|
|
|
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
|
|
|
|
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 44))
|
|
|
|
richTextRun := []RichTextRun{
|
|
|
|
{
|
2020-08-22 18:58:43 +08:00
|
|
|
Text: "bold",
|
2020-04-06 00:23:27 +08:00
|
|
|
Font: &Font{
|
|
|
|
Bold: true,
|
|
|
|
Color: "2354e8",
|
|
|
|
Family: "Times New Roman",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: " and ",
|
|
|
|
Font: &Font{
|
|
|
|
Family: "Times New Roman",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: "italic ",
|
|
|
|
Font: &Font{
|
|
|
|
Bold: true,
|
|
|
|
Color: "e83723",
|
|
|
|
Italic: true,
|
|
|
|
Family: "Times New Roman",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: "text with color and font-family,",
|
|
|
|
Font: &Font{
|
|
|
|
Bold: true,
|
|
|
|
Color: "2354e8",
|
|
|
|
Family: "Times New Roman",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: "\r\nlarge text with ",
|
|
|
|
Font: &Font{
|
|
|
|
Size: 14,
|
|
|
|
Color: "ad23e8",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: "strike",
|
|
|
|
Font: &Font{
|
|
|
|
Color: "e89923",
|
|
|
|
Strike: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: " and ",
|
|
|
|
Font: &Font{
|
|
|
|
Size: 14,
|
|
|
|
Color: "ad23e8",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Text: "underline.",
|
|
|
|
Font: &Font{
|
|
|
|
Color: "23e833",
|
|
|
|
Underline: "single",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", richTextRun))
|
|
|
|
assert.NoError(t, f.SetCellRichText("Sheet1", "A2", richTextRun))
|
|
|
|
style, err := f.NewStyle(&Style{
|
|
|
|
Alignment: &Alignment{
|
|
|
|
WrapText: true,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
|
|
|
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx")))
|
|
|
|
// Test set cell rich text on not exists worksheet
|
|
|
|
assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN is not exist")
|
|
|
|
// Test set cell rich text with illegal cell coordinates
|
|
|
|
assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
|
|
|
}
|
2020-10-04 21:07:39 +08:00
|
|
|
|
2020-12-12 16:17:00 +08:00
|
|
|
func TestFormattedValue2(t *testing.T) {
|
2020-10-04 21:07:39 +08:00
|
|
|
f := NewFile()
|
|
|
|
v := f.formattedValue(0, "43528")
|
|
|
|
assert.Equal(t, "43528", v)
|
|
|
|
|
|
|
|
v = f.formattedValue(15, "43528")
|
|
|
|
assert.Equal(t, "43528", v)
|
|
|
|
|
|
|
|
v = f.formattedValue(1, "43528")
|
|
|
|
assert.Equal(t, "43528", v)
|
|
|
|
customNumFmt := "[$-409]MM/DD/YYYY"
|
|
|
|
_, err := f.NewStyle(&Style{
|
|
|
|
CustomNumFmt: &customNumFmt,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
v = f.formattedValue(1, "43528")
|
|
|
|
assert.Equal(t, "03/04/2019", v)
|
2020-10-19 23:55:54 +08:00
|
|
|
|
|
|
|
// formatted value with no built-in number format ID
|
|
|
|
numFmtID := 5
|
|
|
|
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
|
|
|
|
NumFmtID: &numFmtID,
|
|
|
|
})
|
2020-12-12 16:17:00 +08:00
|
|
|
v = f.formattedValue(2, "43528")
|
|
|
|
assert.Equal(t, "43528", v)
|
|
|
|
|
|
|
|
// formatted value with invalid number format ID
|
|
|
|
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
|
|
|
|
NumFmtID: nil,
|
|
|
|
})
|
2021-01-08 23:57:13 +08:00
|
|
|
_ = f.formattedValue(3, "43528")
|
2020-12-12 16:17:00 +08:00
|
|
|
|
|
|
|
// formatted value with empty number format
|
|
|
|
f.Styles.NumFmts = nil
|
|
|
|
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
|
|
|
|
NumFmtID: &numFmtID,
|
|
|
|
})
|
2020-10-19 23:55:54 +08:00
|
|
|
v = f.formattedValue(1, "43528")
|
|
|
|
assert.Equal(t, "43528", v)
|
2020-10-04 21:07:39 +08:00
|
|
|
}
|