This close #1373, fixes the incorrect build-in number format apply the result

- An error will be returned when setting the stream row without ascending row numbers, to avoid potential mistakes as mentioned in #1139
- Updated unit tests
This commit is contained in:
xuri 2022-10-20 00:02:30 +08:00
parent 3ece904b00
commit 2df615fa28
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 158 additions and 14 deletions

View File

@ -87,6 +87,12 @@ func newDecodeXMLError(err error) error {
return fmt.Errorf("xml decode error: %s", err)
}
// newStreamSetRowError defined the error message on the stream writer
// receiving the non-ascending row number.
func newStreamSetRowError(row int) error {
return fmt.Errorf("row %d has already been written", row)
}
var (
// ErrStreamSetColWidth defined the error message on set column width in
// stream writing mode.

View File

@ -721,10 +721,10 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
expected := [][]string{
{"37947.7500001", "37948", "37947.75", "37948", "37947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 pm", "6:00:00 pm", "18:00", "18:00:00", "11/22/03 18:00", "37947", "37947", "37947.75", "37947.75", "37947.7500001", "37947.7500001", "37947.7500001", "37947.7500001", "00:00", "910746:00:00", "37947.7500001", "3.79E+04", "37947.7500001"},
{"-37947.7500001", "-37948", "-37947.75", "-37948", "-37947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37947)", "(37947)", "(-37947.75)", "(-37947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-3.79E+04", "-37947.7500001"},
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "0:10 am", "0:10:04 am", "00:10", "00:10:04", "12/30/99 00:10", "0", "0", "0.01", "0.01", "0.007", "0.007", "0.007", "0.007", "10:04", "0:10:04", "0.007", "7.00E-03", "0.007"},
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 am", "2:24:00 am", "02:24", "02:24:00", "1/1/00 02:24", "2", "2", "2.10", "2.10", "2.1", "2.1", "2.1", "2.1", "24:00", "50:24:00", "2.1", "2.10E+00", "2.1"},
{"37947.7500001", "37948", "37947.75", "37,948", "37947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 pm", "6:00:00 pm", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37947.7500001", "37947.7500001", "37947.7500001", "37947.7500001", "00:00", "910746:00:00", "37947.7500001", "3.79E+04", "37947.7500001"},
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-3.79E+04", "-37947.7500001"},
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "0:10 am", "0:10:04 am", "00:10", "00:10:04", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:04", "0:10:04", "0.007", "7.00E-03", "0.007"},
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 am", "2:24:00 am", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2.1", "2.1", "2.1", "2.1", "24:00", "50:24:00", "2.1", "2.10E+00", "2.1"},
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String"},
}
@ -744,7 +744,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
}
assert.NoError(t, f.SetCellStyle("Sheet2", c, c, style))
cellValue, err := f.GetCellValue("Sheet2", c)
assert.Equal(t, expected[i][k], cellValue)
assert.Equal(t, expected[i][k], cellValue, "Sheet2!"+c, i, k)
assert.NoError(t, err)
}
}

View File

@ -993,6 +993,68 @@ func TestNumberFormats(t *testing.T) {
}
assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
assert.NoError(t, f.Close())
f = NewFile()
numFmt1, err := f.NewStyle(&Style{NumFmt: 1})
assert.NoError(t, err)
numFmt2, err := f.NewStyle(&Style{NumFmt: 2})
assert.NoError(t, err)
numFmt3, err := f.NewStyle(&Style{NumFmt: 3})
assert.NoError(t, err)
numFmt9, err := f.NewStyle(&Style{NumFmt: 9})
assert.NoError(t, err)
numFmt10, err := f.NewStyle(&Style{NumFmt: 10})
assert.NoError(t, err)
numFmt37, err := f.NewStyle(&Style{NumFmt: 37})
assert.NoError(t, err)
numFmt38, err := f.NewStyle(&Style{NumFmt: 38})
assert.NoError(t, err)
numFmt39, err := f.NewStyle(&Style{NumFmt: 39})
assert.NoError(t, err)
numFmt40, err := f.NewStyle(&Style{NumFmt: 40})
assert.NoError(t, err)
for _, cases := range [][]interface{}{
{"A1", numFmt1, 8.8888666665555493e+19, "88888666665555500000"},
{"A2", numFmt1, 8.8888666665555487, "9"},
{"A3", numFmt2, 8.8888666665555493e+19, "88888666665555500000.00"},
{"A4", numFmt2, 8.8888666665555487, "8.89"},
{"A5", numFmt3, 8.8888666665555493e+19, "88,888,666,665,555,500,000"},
{"A6", numFmt3, 8.8888666665555487, "9"},
{"A7", numFmt3, 123, "123"},
{"A8", numFmt3, -1234, "-1,234"},
{"A9", numFmt9, 8.8888666665555493e+19, "8888866666555550000000%"},
{"A10", numFmt9, -8.8888666665555493e+19, "-8888866666555550000000%"},
{"A11", numFmt9, 8.8888666665555487, "889%"},
{"A12", numFmt9, -8.8888666665555487, "-889%"},
{"A13", numFmt10, 8.8888666665555493e+19, "8888866666555550000000.00%"},
{"A14", numFmt10, -8.8888666665555493e+19, "-8888866666555550000000.00%"},
{"A15", numFmt10, 8.8888666665555487, "888.89%"},
{"A16", numFmt10, -8.8888666665555487, "-888.89%"},
{"A17", numFmt37, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
{"A18", numFmt37, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
{"A19", numFmt37, 8.8888666665555487, "9 "},
{"A20", numFmt37, -8.8888666665555487, "(9)"},
{"A21", numFmt38, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
{"A22", numFmt38, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
{"A23", numFmt38, 8.8888666665555487, "9 "},
{"A24", numFmt38, -8.8888666665555487, "(9)"},
{"A25", numFmt39, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
{"A26", numFmt39, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
{"A27", numFmt39, 8.8888666665555487, "8.89 "},
{"A28", numFmt39, -8.8888666665555487, "(8.89)"},
{"A29", numFmt40, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
{"A30", numFmt40, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
{"A31", numFmt40, 8.8888666665555487, "8.89 "},
{"A32", numFmt40, -8.8888666665555487, "(8.89)"},
} {
cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string)
f.SetCellStyle("Sheet1", cell, cell, styleID)
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
result, err := f.GetCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
}
func BenchmarkRows(b *testing.B) {
@ -1016,6 +1078,7 @@ func BenchmarkRows(b *testing.B) {
}
}
// trimSliceSpace trim continually blank element in the tail of slice.
func trimSliceSpace(s []string) []string {
for {
if len(s) > 0 && s[len(s)-1] == "" {

View File

@ -32,6 +32,7 @@ type StreamWriter struct {
cols strings.Builder
worksheet *xlsxWorksheet
rawData bufferedWriter
rows int
mergeCellsCount int
mergeCells strings.Builder
tableParts string
@ -40,7 +41,7 @@ type StreamWriter struct {
// NewStreamWriter return stream writer struct by given worksheet name for
// generate new worksheet with large amounts of data. Note that after set
// rows, you must call the 'Flush' method to end the streaming writing process
// and ensure that the order of line numbers is ascending, the normal mode
// and ensure that the order of row numbers is ascending, the normal mode
// functions and stream mode functions can't be work mixed to writing data on
// the worksheets, you can't get cell value when in-memory chunks data over
// 16MB. For example, set data for worksheet of size 102400 rows x 50 columns
@ -358,6 +359,10 @@ func (sw *StreamWriter) SetRow(cell string, values []interface{}, opts ...RowOpt
if err != nil {
return err
}
if row <= sw.rows {
return newStreamSetRowError(row)
}
sw.rows = row
sw.writeSheetData()
options := parseRowOpts(opts...)
attrs, err := options.marshalAttrs()

View File

@ -61,7 +61,7 @@ func TestStreamWriter(t *testing.T) {
}}))
assert.NoError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}))
assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID}))
assert.EqualError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: MaxRowHeight + 1}), ErrMaxRowHeight.Error())
assert.EqualError(t, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1}), ErrMaxRowHeight.Error())
for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
@ -77,7 +77,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
// Test set cell column overflow.
assert.ErrorIs(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber)
assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber)
// Test close temporary file error.
file = NewFile()
@ -226,6 +226,9 @@ func TestStreamSetRow(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set row with non-ascending row number
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error())
}
func TestStreamSetRowNilValues(t *testing.T) {

View File

@ -758,7 +758,7 @@ var builtInNumFmtFunc = map[int]func(v, format string, date1904 bool) string{
0: format,
1: formatToInt,
2: formatToFloat,
3: formatToInt,
3: formatToIntSeparator,
4: formatToFloat,
9: formatToC,
10: formatToD,
@ -869,6 +869,26 @@ var operatorType = map[string]string{
"greaterThanOrEqual": "greater than or equal to",
}
// printCommaSep format number with thousands separator.
func printCommaSep(text string) string {
var (
target strings.Builder
subStr = strings.Split(text, ".")
length = len(subStr[0])
)
for i := 0; i < length; i++ {
if i > 0 && (length-i)%3 == 0 {
target.WriteString(",")
}
target.WriteString(string(text[i]))
}
if len(subStr) == 2 {
target.WriteString(".")
target.WriteString(subStr[1])
}
return target.String()
}
// formatToInt provides a function to convert original string to integer
// format as string type by given built-in number formats code and cell
// string.
@ -880,7 +900,7 @@ func formatToInt(v, format string, date1904 bool) string {
if err != nil {
return v
}
return fmt.Sprintf("%d", int64(math.Round(f)))
return strconv.FormatFloat(math.Round(f), 'f', -1, 64)
}
// formatToFloat provides a function to convert original string to float
@ -894,9 +914,27 @@ func formatToFloat(v, format string, date1904 bool) string {
if err != nil {
return v
}
source := strconv.FormatFloat(f, 'f', -1, 64)
if !strings.Contains(source, ".") {
return source + ".00"
}
return fmt.Sprintf("%.2f", f)
}
// formatToIntSeparator provides a function to convert original string to
// integer format as string type by given built-in number formats code and cell
// string.
func formatToIntSeparator(v, format string, date1904 bool) string {
if strings.Contains(v, "_") {
return v
}
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return v
}
return printCommaSep(strconv.FormatFloat(math.Round(f), 'f', -1, 64))
}
// formatToA provides a function to convert original string to special format
// as string type by given built-in number formats code and cell string.
func formatToA(v, format string, date1904 bool) string {
@ -907,10 +945,17 @@ func formatToA(v, format string, date1904 bool) string {
if err != nil {
return v
}
var target strings.Builder
if f < 0 {
return fmt.Sprintf("(%d)", int(math.Abs(f)))
target.WriteString("(")
}
return fmt.Sprintf("%d", int(f))
target.WriteString(printCommaSep(strconv.FormatFloat(math.Abs(math.Round(f)), 'f', -1, 64)))
if f < 0 {
target.WriteString(")")
} else {
target.WriteString(" ")
}
return target.String()
}
// formatToB provides a function to convert original string to special format
@ -923,10 +968,24 @@ func formatToB(v, format string, date1904 bool) string {
if err != nil {
return v
}
var target strings.Builder
if f < 0 {
return fmt.Sprintf("(%.2f)", f)
target.WriteString("(")
}
return fmt.Sprintf("%.2f", f)
source := strconv.FormatFloat(math.Abs(f), 'f', -1, 64)
var text string
if !strings.Contains(source, ".") {
text = printCommaSep(source + ".00")
} else {
text = printCommaSep(fmt.Sprintf("%.2f", math.Abs(f)))
}
target.WriteString(text)
if f < 0 {
target.WriteString(")")
} else {
target.WriteString(" ")
}
return target.String()
}
// formatToC provides a function to convert original string to special format
@ -939,6 +998,10 @@ func formatToC(v, format string, date1904 bool) string {
if err != nil {
return v
}
source := strconv.FormatFloat(f, 'f', -1, 64)
if !strings.Contains(source, ".") {
return source + "00%"
}
return fmt.Sprintf("%.f%%", f*100)
}
@ -952,6 +1015,10 @@ func formatToD(v, format string, date1904 bool) string {
if err != nil {
return v
}
source := strconv.FormatFloat(f, 'f', -1, 64)
if !strings.Contains(source, ".") {
return source + "00.00%"
}
return fmt.Sprintf("%.2f%%", f*100)
}