Skip to content

Commit

Permalink
extend cell value load to support custom datetime format (qax-os#703)
Browse files Browse the repository at this point in the history
* extend cell value load to support custom datetime format

* cleanup incorrect imports

* fix numeric values conversion as done in legacy Excel

* fix tests coverage

* revert temporary package name fix

* remove personal info from test XLSX files

* remove unused dependencies

* update format conversion in parseTime

* new UT to increase code coverage

* Resolve code review issue for PR qax-os#703

* Rename broken file name generated by unit test

Co-authored-by: xuri <[email protected]>
  • Loading branch information
artiz and xuri authored Oct 4, 2020
1 parent 9055a83 commit f2b8798
Show file tree
Hide file tree
Showing 14 changed files with 452 additions and 248 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
~$*.xlsx
test/Test*.xlsx
test/Test*.xlsm
*.out
*.test
.idea
18 changes: 16 additions & 2 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,9 +762,23 @@ func (f *File) formattedValue(s int, v string) string {
return v
}
styleSheet := f.stylesReader()
ok := builtInNumFmtFunc[*styleSheet.CellXfs.Xf[s].NumFmtID]
if s >= len(styleSheet.CellXfs.Xf) {
return v
}
numFmtId := *styleSheet.CellXfs.Xf[s].NumFmtID
ok := builtInNumFmtFunc[numFmtId]
if ok != nil {
return ok(*styleSheet.CellXfs.Xf[s].NumFmtID, v)
return ok(v, builtInNumFmt[numFmtId])
}
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtId {
format := strings.ToLower(xlsxFmt.FormatCode)
if strings.Contains(format, "y") || strings.Contains(format, "m") || strings.Contains(format, "d") || strings.Contains(format, "h") {
return parseTime(v, format)
}

return v
}
}
return v
}
Expand Down
36 changes: 36 additions & 0 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ func TestSetCellValue(t *testing.T) {
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

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`)
}

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"`)
Expand Down Expand Up @@ -264,3 +281,22 @@ func TestSetCellRichText(t *testing.T) {
// 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"`)
}

func TestFormattedValue(t *testing.T) {
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)
}
2 changes: 1 addition & 1 deletion crypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ import (
func TestEncrypt(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
assert.NoError(t, err)
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
assert.EqualError(t, f.SaveAs(filepath.Join("test", "BadEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
}
2 changes: 1 addition & 1 deletion excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
}
if s == 0 {
style, _ := f.NewStyle(&Style{NumFmt: format})
_ = f.SetCellStyle(sheet, axis, axis, style)
err = f.SetCellStyle(sheet, axis, axis, style)
}
return err
}
Expand Down
5 changes: 4 additions & 1 deletion excelize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func TestBrokenFile(t *testing.T) {

t.Run("SaveAsEmptyStruct", func(t *testing.T) {
// Test write file with broken file struct with given path.
assert.NoError(t, f.SaveAs(filepath.Join("test", "BrokenFile.SaveAsEmptyStruct.xlsx")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx")))
})

t.Run("OpenBadWorkbook", func(t *testing.T) {
Expand Down Expand Up @@ -1175,6 +1175,9 @@ func TestSetDefaultTimeStyle(t *testing.T) {
f := NewFile()
// Test set default time style on not exists worksheet.
assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN is not exist")

// Test set default time style on invalid cell
assert.EqualError(t, f.setDefaultTimeStyle("Sheet1", "", 42), "cannot convert cell \"\" to coordinates: invalid cell name \"\"")
}

func TestAddVBAProject(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
}
}

if f.options != nil {
if f.options != nil && f.options.Password != "" {
if err := zw.Close(); err != nil {
return buf, err
}
Expand Down
19 changes: 19 additions & 0 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,25 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
}
return f.formattedValue(xlsx.S, xlsx.V), nil
default:
// correct numeric values as legacy Excel app
// https://en.wikipedia.org/wiki/Numeric_precision_in_Microsoft_Excel
// In the top figure the fraction 1/9000 in Excel is displayed.
// Although this number has a decimal representation that is an infinite string of ones,
// Excel displays only the leading 15 figures. In the second line, the number one is added to the fraction, and again Excel displays only 15 figures.
const precision = 1000000000000000
if len(xlsx.V) > 16 {
num, err := strconv.ParseFloat(xlsx.V, 64)
if err != nil {
return "", err
}

num = math.Round(num*precision) / precision
val := fmt.Sprintf("%g", num)
if val != xlsx.V {
return f.formattedValue(xlsx.S, val), nil
}
}

return f.formattedValue(xlsx.S, xlsx.V), nil
}
}
Expand Down
37 changes: 36 additions & 1 deletion rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ func TestDuplicateMergeCells(t *testing.T) {
assert.EqualError(t, f.duplicateMergeCells("SheetN", xlsx, 1, 2), "sheet SheetN is not exist")
}

func TestGetValueFrom(t *testing.T) {
func TestGetValueFromInlineStr(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
Expand All @@ -826,6 +826,20 @@ func TestGetValueFrom(t *testing.T) {
assert.Equal(t, "", val)
}

func TestGetValueFromNumber(t *testing.T) {
c := &xlsxC{T: "n", V: "2.2200000000000002"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d)
assert.NoError(t, err)
assert.Equal(t, "2.22", val)

c = &xlsxC{T: "n", V: "2.220000ddsf0000000002-r"}
val, err = c.getValueFrom(f, d)
assert.NotNil(t, err)
assert.Equal(t, "strconv.ParseFloat: parsing \"2.220000ddsf0000000002-r\": invalid syntax", err.Error())
}

func TestErrSheetNotExistError(t *testing.T) {
err := ErrSheetNotExist{SheetName: "Sheet1"}
assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")
Expand All @@ -842,6 +856,27 @@ func TestCheckRow(t *testing.T) {
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
}

func TestNumberFormats(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
cells := make([][]string, 0)
cols, err := f.Cols("Sheet2")
if !assert.NoError(t, err) {
t.FailNow()
}
for cols.Next() {
col, err := cols.Rows()
assert.NoError(t, err)
if err != nil {
break
}
cells = append(cells, col)
}
assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
}

func BenchmarkRows(b *testing.B) {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
for i := 0; i < b.N; i++ {
Expand Down
Loading

0 comments on commit f2b8798

Please sign in to comment.