Skip to content

Commit 2df615f

Browse files
committed
This close qax-os#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 qax-os#1139 - Updated unit tests
1 parent 3ece904 commit 2df615f

File tree

6 files changed

+158
-14
lines changed

6 files changed

+158
-14
lines changed

errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ func newDecodeXMLError(err error) error {
8787
return fmt.Errorf("xml decode error: %s", err)
8888
}
8989

90+
// newStreamSetRowError defined the error message on the stream writer
91+
// receiving the non-ascending row number.
92+
func newStreamSetRowError(row int) error {
93+
return fmt.Errorf("row %d has already been written", row)
94+
}
95+
9096
var (
9197
// ErrStreamSetColWidth defined the error message on set column width in
9298
// stream writing mode.

excelize_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,10 +721,10 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
721721
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}
722722
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
723723
expected := [][]string{
724-
{"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"},
725-
{"-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"},
726-
{"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"},
727-
{"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"},
724+
{"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"},
725+
{"-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"},
726+
{"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"},
727+
{"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"},
728728
{"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"},
729729
}
730730

@@ -744,7 +744,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
744744
}
745745
assert.NoError(t, f.SetCellStyle("Sheet2", c, c, style))
746746
cellValue, err := f.GetCellValue("Sheet2", c)
747-
assert.Equal(t, expected[i][k], cellValue)
747+
assert.Equal(t, expected[i][k], cellValue, "Sheet2!"+c, i, k)
748748
assert.NoError(t, err)
749749
}
750750
}

rows_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,68 @@ func TestNumberFormats(t *testing.T) {
993993
}
994994
assert.Equal(t, []string{"", "200", "450", "200", "510", "315", "127", "89", "348", "53", "37"}, cells[3])
995995
assert.NoError(t, f.Close())
996+
997+
f = NewFile()
998+
numFmt1, err := f.NewStyle(&Style{NumFmt: 1})
999+
assert.NoError(t, err)
1000+
numFmt2, err := f.NewStyle(&Style{NumFmt: 2})
1001+
assert.NoError(t, err)
1002+
numFmt3, err := f.NewStyle(&Style{NumFmt: 3})
1003+
assert.NoError(t, err)
1004+
numFmt9, err := f.NewStyle(&Style{NumFmt: 9})
1005+
assert.NoError(t, err)
1006+
numFmt10, err := f.NewStyle(&Style{NumFmt: 10})
1007+
assert.NoError(t, err)
1008+
numFmt37, err := f.NewStyle(&Style{NumFmt: 37})
1009+
assert.NoError(t, err)
1010+
numFmt38, err := f.NewStyle(&Style{NumFmt: 38})
1011+
assert.NoError(t, err)
1012+
numFmt39, err := f.NewStyle(&Style{NumFmt: 39})
1013+
assert.NoError(t, err)
1014+
numFmt40, err := f.NewStyle(&Style{NumFmt: 40})
1015+
assert.NoError(t, err)
1016+
for _, cases := range [][]interface{}{
1017+
{"A1", numFmt1, 8.8888666665555493e+19, "88888666665555500000"},
1018+
{"A2", numFmt1, 8.8888666665555487, "9"},
1019+
{"A3", numFmt2, 8.8888666665555493e+19, "88888666665555500000.00"},
1020+
{"A4", numFmt2, 8.8888666665555487, "8.89"},
1021+
{"A5", numFmt3, 8.8888666665555493e+19, "88,888,666,665,555,500,000"},
1022+
{"A6", numFmt3, 8.8888666665555487, "9"},
1023+
{"A7", numFmt3, 123, "123"},
1024+
{"A8", numFmt3, -1234, "-1,234"},
1025+
{"A9", numFmt9, 8.8888666665555493e+19, "8888866666555550000000%"},
1026+
{"A10", numFmt9, -8.8888666665555493e+19, "-8888866666555550000000%"},
1027+
{"A11", numFmt9, 8.8888666665555487, "889%"},
1028+
{"A12", numFmt9, -8.8888666665555487, "-889%"},
1029+
{"A13", numFmt10, 8.8888666665555493e+19, "8888866666555550000000.00%"},
1030+
{"A14", numFmt10, -8.8888666665555493e+19, "-8888866666555550000000.00%"},
1031+
{"A15", numFmt10, 8.8888666665555487, "888.89%"},
1032+
{"A16", numFmt10, -8.8888666665555487, "-888.89%"},
1033+
{"A17", numFmt37, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
1034+
{"A18", numFmt37, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
1035+
{"A19", numFmt37, 8.8888666665555487, "9 "},
1036+
{"A20", numFmt37, -8.8888666665555487, "(9)"},
1037+
{"A21", numFmt38, 8.8888666665555493e+19, "88,888,666,665,555,500,000 "},
1038+
{"A22", numFmt38, -8.8888666665555493e+19, "(88,888,666,665,555,500,000)"},
1039+
{"A23", numFmt38, 8.8888666665555487, "9 "},
1040+
{"A24", numFmt38, -8.8888666665555487, "(9)"},
1041+
{"A25", numFmt39, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
1042+
{"A26", numFmt39, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
1043+
{"A27", numFmt39, 8.8888666665555487, "8.89 "},
1044+
{"A28", numFmt39, -8.8888666665555487, "(8.89)"},
1045+
{"A29", numFmt40, 8.8888666665555493e+19, "88,888,666,665,555,500,000.00 "},
1046+
{"A30", numFmt40, -8.8888666665555493e+19, "(88,888,666,665,555,500,000.00)"},
1047+
{"A31", numFmt40, 8.8888666665555487, "8.89 "},
1048+
{"A32", numFmt40, -8.8888666665555487, "(8.89)"},
1049+
} {
1050+
cell, styleID, value, expected := cases[0].(string), cases[1].(int), cases[2], cases[3].(string)
1051+
f.SetCellStyle("Sheet1", cell, cell, styleID)
1052+
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
1053+
result, err := f.GetCellValue("Sheet1", cell)
1054+
assert.NoError(t, err)
1055+
assert.Equal(t, expected, result)
1056+
}
1057+
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
9961058
}
9971059

9981060
func BenchmarkRows(b *testing.B) {
@@ -1016,6 +1078,7 @@ func BenchmarkRows(b *testing.B) {
10161078
}
10171079
}
10181080

1081+
// trimSliceSpace trim continually blank element in the tail of slice.
10191082
func trimSliceSpace(s []string) []string {
10201083
for {
10211084
if len(s) > 0 && s[len(s)-1] == "" {

stream.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type StreamWriter struct {
3232
cols strings.Builder
3333
worksheet *xlsxWorksheet
3434
rawData bufferedWriter
35+
rows int
3536
mergeCellsCount int
3637
mergeCells strings.Builder
3738
tableParts string
@@ -40,7 +41,7 @@ type StreamWriter struct {
4041
// NewStreamWriter return stream writer struct by given worksheet name for
4142
// generate new worksheet with large amounts of data. Note that after set
4243
// rows, you must call the 'Flush' method to end the streaming writing process
43-
// and ensure that the order of line numbers is ascending, the normal mode
44+
// and ensure that the order of row numbers is ascending, the normal mode
4445
// functions and stream mode functions can't be work mixed to writing data on
4546
// the worksheets, you can't get cell value when in-memory chunks data over
4647
// 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
358359
if err != nil {
359360
return err
360361
}
362+
if row <= sw.rows {
363+
return newStreamSetRowError(row)
364+
}
365+
sw.rows = row
361366
sw.writeSheetData()
362367
options := parseRowOpts(opts...)
363368
attrs, err := options.marshalAttrs()

stream_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestStreamWriter(t *testing.T) {
6161
}}))
6262
assert.NoError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}))
6363
assert.NoError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: 20, Hidden: true, StyleID: styleID}))
64-
assert.EqualError(t, streamWriter.SetRow("A7", nil, RowOpts{Height: MaxRowHeight + 1}), ErrMaxRowHeight.Error())
64+
assert.EqualError(t, streamWriter.SetRow("A8", nil, RowOpts{Height: MaxRowHeight + 1}), ErrMaxRowHeight.Error())
6565

6666
for rowID := 10; rowID <= 51200; rowID++ {
6767
row := make([]interface{}, 50)
@@ -77,7 +77,7 @@ func TestStreamWriter(t *testing.T) {
7777
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))
7878

7979
// Test set cell column overflow.
80-
assert.ErrorIs(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber)
80+
assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber)
8181

8282
// Test close temporary file error.
8383
file = NewFile()
@@ -226,6 +226,9 @@ func TestStreamSetRow(t *testing.T) {
226226
streamWriter, err := file.NewStreamWriter("Sheet1")
227227
assert.NoError(t, err)
228228
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
229+
// Test set row with non-ascending row number
230+
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
231+
assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error())
229232
}
230233

231234
func TestStreamSetRowNilValues(t *testing.T) {

styles.go

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ var builtInNumFmtFunc = map[int]func(v, format string, date1904 bool) string{
758758
0: format,
759759
1: formatToInt,
760760
2: formatToFloat,
761-
3: formatToInt,
761+
3: formatToIntSeparator,
762762
4: formatToFloat,
763763
9: formatToC,
764764
10: formatToD,
@@ -869,6 +869,26 @@ var operatorType = map[string]string{
869869
"greaterThanOrEqual": "greater than or equal to",
870870
}
871871

872+
// printCommaSep format number with thousands separator.
873+
func printCommaSep(text string) string {
874+
var (
875+
target strings.Builder
876+
subStr = strings.Split(text, ".")
877+
length = len(subStr[0])
878+
)
879+
for i := 0; i < length; i++ {
880+
if i > 0 && (length-i)%3 == 0 {
881+
target.WriteString(",")
882+
}
883+
target.WriteString(string(text[i]))
884+
}
885+
if len(subStr) == 2 {
886+
target.WriteString(".")
887+
target.WriteString(subStr[1])
888+
}
889+
return target.String()
890+
}
891+
872892
// formatToInt provides a function to convert original string to integer
873893
// format as string type by given built-in number formats code and cell
874894
// string.
@@ -880,7 +900,7 @@ func formatToInt(v, format string, date1904 bool) string {
880900
if err != nil {
881901
return v
882902
}
883-
return fmt.Sprintf("%d", int64(math.Round(f)))
903+
return strconv.FormatFloat(math.Round(f), 'f', -1, 64)
884904
}
885905

886906
// formatToFloat provides a function to convert original string to float
@@ -894,9 +914,27 @@ func formatToFloat(v, format string, date1904 bool) string {
894914
if err != nil {
895915
return v
896916
}
917+
source := strconv.FormatFloat(f, 'f', -1, 64)
918+
if !strings.Contains(source, ".") {
919+
return source + ".00"
920+
}
897921
return fmt.Sprintf("%.2f", f)
898922
}
899923

924+
// formatToIntSeparator provides a function to convert original string to
925+
// integer format as string type by given built-in number formats code and cell
926+
// string.
927+
func formatToIntSeparator(v, format string, date1904 bool) string {
928+
if strings.Contains(v, "_") {
929+
return v
930+
}
931+
f, err := strconv.ParseFloat(v, 64)
932+
if err != nil {
933+
return v
934+
}
935+
return printCommaSep(strconv.FormatFloat(math.Round(f), 'f', -1, 64))
936+
}
937+
900938
// formatToA provides a function to convert original string to special format
901939
// as string type by given built-in number formats code and cell string.
902940
func formatToA(v, format string, date1904 bool) string {
@@ -907,10 +945,17 @@ func formatToA(v, format string, date1904 bool) string {
907945
if err != nil {
908946
return v
909947
}
948+
var target strings.Builder
910949
if f < 0 {
911-
return fmt.Sprintf("(%d)", int(math.Abs(f)))
950+
target.WriteString("(")
912951
}
913-
return fmt.Sprintf("%d", int(f))
952+
target.WriteString(printCommaSep(strconv.FormatFloat(math.Abs(math.Round(f)), 'f', -1, 64)))
953+
if f < 0 {
954+
target.WriteString(")")
955+
} else {
956+
target.WriteString(" ")
957+
}
958+
return target.String()
914959
}
915960

916961
// formatToB provides a function to convert original string to special format
@@ -923,10 +968,24 @@ func formatToB(v, format string, date1904 bool) string {
923968
if err != nil {
924969
return v
925970
}
971+
var target strings.Builder
926972
if f < 0 {
927-
return fmt.Sprintf("(%.2f)", f)
973+
target.WriteString("(")
928974
}
929-
return fmt.Sprintf("%.2f", f)
975+
source := strconv.FormatFloat(math.Abs(f), 'f', -1, 64)
976+
var text string
977+
if !strings.Contains(source, ".") {
978+
text = printCommaSep(source + ".00")
979+
} else {
980+
text = printCommaSep(fmt.Sprintf("%.2f", math.Abs(f)))
981+
}
982+
target.WriteString(text)
983+
if f < 0 {
984+
target.WriteString(")")
985+
} else {
986+
target.WriteString(" ")
987+
}
988+
return target.String()
930989
}
931990

932991
// formatToC provides a function to convert original string to special format
@@ -939,6 +998,10 @@ func formatToC(v, format string, date1904 bool) string {
939998
if err != nil {
940999
return v
9411000
}
1001+
source := strconv.FormatFloat(f, 'f', -1, 64)
1002+
if !strings.Contains(source, ".") {
1003+
return source + "00%"
1004+
}
9421005
return fmt.Sprintf("%.f%%", f*100)
9431006
}
9441007

@@ -952,6 +1015,10 @@ func formatToD(v, format string, date1904 bool) string {
9521015
if err != nil {
9531016
return v
9541017
}
1018+
source := strconv.FormatFloat(f, 'f', -1, 64)
1019+
if !strings.Contains(source, ".") {
1020+
return source + "00.00%"
1021+
}
9551022
return fmt.Sprintf("%.2f%%", f*100)
9561023
}
9571024

0 commit comments

Comments
 (0)