diff options
-rw-r--r-- | adjust_test.go | 16 | ||||
-rw-r--r-- | calcchain.go | 20 | ||||
-rw-r--r-- | calcchain_test.go | 26 | ||||
-rw-r--r-- | cell.go | 92 | ||||
-rw-r--r-- | cell_test.go | 113 | ||||
-rw-r--r-- | chart_test.go | 18 | ||||
-rw-r--r-- | col.go | 78 | ||||
-rw-r--r-- | col_test.go | 13 | ||||
-rw-r--r-- | comment.go | 102 | ||||
-rw-r--r-- | comment_test.go | 48 | ||||
-rw-r--r-- | docProps.go | 4 | ||||
-rw-r--r-- | docProps_test.go | 12 | ||||
-rw-r--r-- | drawing.go | 25 | ||||
-rw-r--r-- | drawing_test.go | 17 | ||||
-rw-r--r-- | errors.go | 5 | ||||
-rw-r--r-- | excelize.go | 9 | ||||
-rw-r--r-- | excelize_test.go | 30 | ||||
-rw-r--r-- | file.go | 4 | ||||
-rw-r--r-- | merge_test.go | 6 | ||||
-rw-r--r-- | picture.go | 11 | ||||
-rw-r--r-- | picture_test.go | 16 | ||||
-rw-r--r-- | rows.go | 39 | ||||
-rw-r--r-- | rows_test.go | 17 | ||||
-rw-r--r-- | shape.go | 14 | ||||
-rw-r--r-- | shape_test.go | 11 | ||||
-rw-r--r-- | sheet.go | 8 | ||||
-rw-r--r-- | sheet_test.go | 6 | ||||
-rw-r--r-- | stream_test.go | 4 | ||||
-rw-r--r-- | styles.go | 112 | ||||
-rw-r--r-- | styles_test.go | 76 |
30 files changed, 655 insertions, 297 deletions
diff --git a/adjust_test.go b/adjust_test.go index 0325616..3ce1796 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -10,7 +10,7 @@ import ( func TestAdjustMergeCells(t *testing.T) { f := NewFile() - // testing adjustAutoFilter with illegal cell reference. + // Test adjustAutoFilter with illegal cell reference. assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ @@ -57,7 +57,7 @@ func TestAdjustMergeCells(t *testing.T) { }, }, columns, 1, -1)) - // testing adjustMergeCells + // Test adjustMergeCells. var cases []struct { label string ws *xlsxWorksheet @@ -68,7 +68,7 @@ func TestAdjustMergeCells(t *testing.T) { expectRect []int } - // testing insert + // Test insert. cases = []struct { label string ws *xlsxWorksheet @@ -139,7 +139,7 @@ func TestAdjustMergeCells(t *testing.T) { assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label) } - // testing delete + // Test delete, cases = []struct { label string ws *xlsxWorksheet @@ -227,7 +227,7 @@ func TestAdjustMergeCells(t *testing.T) { assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label) } - // testing delete one row/column + // Test delete one row or column cases = []struct { label string ws *xlsxWorksheet @@ -324,13 +324,13 @@ func TestAdjustTable(t *testing.T) { f = NewFile() assert.NoError(t, f.AddTable(sheetName, "A1", "D5", "")) - // Test adjust table with non-table part + // Test adjust table with non-table part. f.Pkg.Delete("xl/tables/table1.xml") assert.NoError(t, f.RemoveRow(sheetName, 1)) - // Test adjust table with unsupported charset + // Test adjust table with unsupported charset. f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) assert.NoError(t, f.RemoveRow(sheetName, 1)) - // Test adjust table with invalid table range reference + // Test adjust table with invalid table range reference. f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`)) assert.NoError(t, f.RemoveRow(sheetName, 1)) } diff --git a/calcchain.go b/calcchain.go index 80928c2..3aa5d81 100644 --- a/calcchain.go +++ b/calcchain.go @@ -15,23 +15,19 @@ import ( "bytes" "encoding/xml" "io" - "log" ) // calcChainReader provides a function to get the pointer to the structure // after deserialization of xl/calcChain.xml. -func (f *File) calcChainReader() *xlsxCalcChain { - var err error - +func (f *File) calcChainReader() (*xlsxCalcChain, error) { if f.CalcChain == nil { f.CalcChain = new(xlsxCalcChain) - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))). + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))). Decode(f.CalcChain); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return f.CalcChain, err } } - - return f.CalcChain + return f.CalcChain, nil } // calcChainWriter provides a function to save xl/calcChain.xml after @@ -45,8 +41,11 @@ func (f *File) calcChainWriter() { // deleteCalcChain provides a function to remove cell reference on the // calculation chain. -func (f *File) deleteCalcChain(index int, cell string) { - calc := f.calcChainReader() +func (f *File) deleteCalcChain(index int, cell string) error { + calc, err := f.calcChainReader() + if err != nil { + return err + } if calc != nil { calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool { return !((c.I == index && c.R == cell) || (c.I == index && cell == "") || (c.I == 0 && c.R == cell)) @@ -64,6 +63,7 @@ func (f *File) deleteCalcChain(index int, cell string) { } } } + return err } type xlsxCalcChainCollection []xlsxCalcChainC diff --git a/calcchain_test.go b/calcchain_test.go index c36655b..fae3a51 100644 --- a/calcchain_test.go +++ b/calcchain_test.go @@ -1,12 +1,18 @@ package excelize -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestCalcChainReader(t *testing.T) { f := NewFile() + // Test read calculation chain with unsupported charset. f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) - f.calcChainReader() + _, err := f.calcChainReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestDeleteCalcChain(t *testing.T) { @@ -15,5 +21,19 @@ func TestDeleteCalcChain(t *testing.T) { f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{ PartName: "/xl/calcChain.xml", }) - f.deleteCalcChain(1, "A1") + assert.NoError(t, f.deleteCalcChain(1, "A1")) + + f.CalcChain = nil + f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) + assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") + + f.CalcChain = nil + f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellFormula("Sheet1", "A1", ""), "XML syntax error on line 1: invalid UTF-8") + + formulaType, ref := STCellFormulaTypeShared, "C1:C5" + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) + f.CalcChain = nil + f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") } @@ -66,7 +66,11 @@ var cellTypes = map[string]CellType{ // values will be the same in a merged range. func (f *File) GetCellValue(sheet, cell string, opts ...Options) (string, error) { return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { - val, err := c.getValueFrom(f, f.sharedStringsReader(), parseOptions(opts...).RawCellValue) + sst, err := f.sharedStringsReader() + if err != nil { + return "", true, err + } + val, err := c.getValueFrom(f, sst, parseOptions(opts...).RawCellValue) return val, true, err }) } @@ -173,23 +177,26 @@ func (c *xlsxC) hasValue() bool { } // removeFormula delete formula for the cell. -func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) { +func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) error { if c.F != nil && c.Vm == nil { sheetID := f.getSheetID(sheet) - f.deleteCalcChain(sheetID, c.R) + if err := f.deleteCalcChain(sheetID, c.R); err != nil { + return err + } if c.F.T == STCellFormulaTypeShared && c.F.Ref != "" { si := c.F.Si for r, row := range ws.SheetData.Row { for col, cell := range row.C { if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si { ws.SheetData.Row[r].C[col].F = nil - f.deleteCalcChain(sheetID, cell.R) + _ = f.deleteCalcChain(sheetID, cell.R) } } } } c.F = nil } + return nil } // setCellIntFunc is a wrapper of SetCellInt. @@ -289,8 +296,7 @@ func (f *File) SetCellInt(sheet, cell string, value int) error { c.S = f.prepareCellStyle(ws, col, row, c.S) c.T, c.V = setCellInt(value) c.IS = nil - f.removeFormula(c, ws, sheet) - return err + return f.removeFormula(c, ws, sheet) } // setCellInt prepares cell type and string type cell value by a given @@ -316,8 +322,7 @@ func (f *File) SetCellBool(sheet, cell string, value bool) error { c.S = f.prepareCellStyle(ws, col, row, c.S) c.T, c.V = setCellBool(value) c.IS = nil - f.removeFormula(c, ws, sheet) - return err + return f.removeFormula(c, ws, sheet) } // setCellBool prepares cell type and string type cell value by a given @@ -354,8 +359,7 @@ func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSiz c.S = f.prepareCellStyle(ws, col, row, c.S) c.T, c.V = setCellFloat(value, precision, bitSize) c.IS = nil - f.removeFormula(c, ws, sheet) - return err + return f.removeFormula(c, ws, sheet) } // setCellFloat prepares cell type and string type cell value by a given @@ -379,10 +383,11 @@ func (f *File) SetCellStr(sheet, cell, value string) error { ws.Lock() defer ws.Unlock() c.S = f.prepareCellStyle(ws, col, row, c.S) - c.T, c.V, err = f.setCellString(value) + if c.T, c.V, err = f.setCellString(value); err != nil { + return err + } c.IS = nil - f.removeFormula(c, ws, sheet) - return err + return f.removeFormula(c, ws, sheet) } // setCellString provides a function to set string type to shared string @@ -429,7 +434,10 @@ func (f *File) setSharedString(val string) (int, error) { if err := f.sharedStringsLoader(); err != nil { return 0, err } - sst := f.sharedStringsReader() + sst, err := f.sharedStringsReader() + if err != nil { + return 0, err + } f.Lock() defer f.Unlock() if i, ok := f.sharedStringsMap[val]; ok { @@ -498,7 +506,7 @@ func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) { return "FALSE", nil } } - return f.formattedValue(c.S, c.V, raw), nil + return f.formattedValue(c.S, c.V, raw) } // setCellDefault prepares cell type and string type cell value by a given @@ -529,7 +537,7 @@ func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) { c.V = strconv.FormatFloat(excelTime, 'G', 15, 64) } } - return f.formattedValue(c.S, c.V, raw), nil + return f.formattedValue(c.S, c.V, raw) } // getValueFrom return a value from a column/row cell, this function is @@ -548,18 +556,18 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { xlsxSI := 0 xlsxSI, _ = strconv.Atoi(c.V) if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok { - return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil + return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw) } if len(d.SI) > xlsxSI { - return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil + return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw) } } - return f.formattedValue(c.S, c.V, raw), nil + return f.formattedValue(c.S, c.V, raw) case "inlineStr": if c.IS != nil { - return f.formattedValue(c.S, c.IS.String(), raw), nil + return f.formattedValue(c.S, c.IS.String(), raw) } - return f.formattedValue(c.S, c.V, raw), nil + return f.formattedValue(c.S, c.V, raw) default: if isNum, precision, decimal := isNumeric(c.V); isNum && !raw { if precision > 15 { @@ -568,7 +576,7 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { c.V = strconv.FormatFloat(decimal, 'f', -1, 64) } } - return f.formattedValue(c.S, c.V, raw), nil + return f.formattedValue(c.S, c.V, raw) } } @@ -587,8 +595,7 @@ func (f *File) SetCellDefault(sheet, cell, value string) error { defer ws.Unlock() c.S = f.prepareCellStyle(ws, col, row, c.S) c.setCellDefault(value) - f.removeFormula(c, ws, sheet) - return err + return f.removeFormula(c, ws, sheet) } // GetCellFormula provides a function to get formula from cell by given @@ -698,8 +705,7 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts) } if formula == "" { c.F = nil - f.deleteCalcChain(f.getSheetID(sheet), cell) - return err + return f.deleteCalcChain(f.getSheetID(sheet), cell) } if c.F != nil { @@ -926,7 +932,10 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro if err != nil || c.T != "s" { return } - sst := f.sharedStringsReader() + sst, err := f.sharedStringsReader() + if err != nil { + return + } if len(sst.SI) <= siIdx || siIdx < 0 { return } @@ -1145,7 +1154,11 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { return err } c.S = f.prepareCellStyle(ws, col, row, c.S) - si, sst := xlsxSI{}, f.sharedStringsReader() + si := xlsxSI{} + sst, err := f.sharedStringsReader() + if err != nil { + return err + } if si.R, err = setRichText(runs); err != nil { return err } @@ -1286,19 +1299,22 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c // formattedValue provides a function to returns a value after formatted. If // it is possible to apply a format to the cell value, it will do so, if not // then an error will be returned, along with the raw value of the cell. -func (f *File) formattedValue(s int, v string, raw bool) string { +func (f *File) formattedValue(s int, v string, raw bool) (string, error) { if raw { - return v + return v, nil } if s == 0 { - return v + return v, nil + } + styleSheet, err := f.stylesReader() + if err != nil { + return v, err } - styleSheet := f.stylesReader() if styleSheet.CellXfs == nil { - return v + return v, err } if s >= len(styleSheet.CellXfs.Xf) { - return v + return v, err } var numFmtID int if styleSheet.CellXfs.Xf[s].NumFmtID != nil { @@ -1309,17 +1325,17 @@ func (f *File) formattedValue(s int, v string, raw bool) string { date1904 = wb.WorkbookPr.Date1904 } if ok := builtInNumFmtFunc[numFmtID]; ok != nil { - return ok(v, builtInNumFmt[numFmtID], date1904) + return ok(v, builtInNumFmt[numFmtID], date1904), err } if styleSheet.NumFmts == nil { - return v + return v, err } for _, xlsxFmt := range styleSheet.NumFmts.NumFmt { if xlsxFmt.NumFmtID == numFmtID { - return format(v, xlsxFmt.FormatCode, date1904) + return format(v, xlsxFmt.FormatCode, date1904), err } } - return v + return v, err } // prepareCellStyle provides a function to prepare style index of cell in diff --git a/cell_test.go b/cell_test.go index 6689c36..18bc101 100644 --- a/cell_test.go +++ b/cell_test.go @@ -188,6 +188,11 @@ func TestSetCellValue(t *testing.T) { B2, err := f.GetCellValue("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, "0.50", B2) + + // Test set cell value with unsupported charset shared strings table + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8") } func TestSetCellValues(t *testing.T) { @@ -199,7 +204,7 @@ func TestSetCellValues(t *testing.T) { assert.NoError(t, err) assert.Equal(t, v, "12/31/10 00:00") - // test date value lower than min date supported by Excel + // 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) @@ -377,6 +382,12 @@ func TestGetCellValue(t *testing.T) { "2020-07-10 15:00:00.000", }, rows[0]) assert.NoError(t, err) + + // Test get cell value with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + _, value := f.GetCellValue("Sheet1", "A1") + assert.EqualError(t, value, "XML syntax error on line 1: invalid UTF-8") } func TestGetCellType(t *testing.T) { @@ -395,7 +406,9 @@ func TestGetCellType(t *testing.T) { func TestGetValueFrom(t *testing.T) { f := NewFile() c := xlsxC{T: "s"} - value, err := c.getValueFrom(f, f.sharedStringsReader(), false) + sst, err := f.sharedStringsReader() + assert.NoError(t, err) + value, err := c.getValueFrom(f, sst, false) assert.NoError(t, err) assert.Equal(t, "", value) } @@ -566,36 +579,46 @@ func TestGetCellRichText(t *testing.T) { runsSource[1].Font.Color = strings.ToUpper(runsSource[1].Font.Color) assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font") - // Test get cell rich text when string item index overflow + // Test get cell rich text when string item index overflow. ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "2" runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) - // Test get cell rich text when string item index is negative + // Test get cell rich text when string item index is negative. ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "-1" runs, err = f.GetCellRichText("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) - // Test get cell rich text on invalid string item index + // Test get cell rich text on invalid string item index. ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "x" _, err = f.GetCellRichText("Sheet1", "A1") assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax") - // Test set cell rich text on not exists worksheet + // Test set cell rich text on not exists worksheet. _, err = f.GetCellRichText("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN does not exist") - // Test set cell rich text with illegal cell reference + // Test set cell rich text with illegal cell reference. _, err = f.GetCellRichText("Sheet1", "A") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - // Test set rich text color theme without tint + // Test set rich text color theme without tint. assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTheme: &theme}}})) - // Test set rich text color tint without theme + // Test set rich text color tint without theme. assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTint: 0.5}}})) + + // Test set cell rich text with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", runsSource), "XML syntax error on line 1: invalid UTF-8") + // Test get cell rich text with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + _, err = f.GetCellRichText("Sheet1", "A1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetCellRichText(t *testing.T) { @@ -689,80 +712,108 @@ func TestSetCellRichText(t *testing.T) { 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 + // Test set cell rich text on not exists worksheet. assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN does not exist") - // Test set cell rich text with illegal cell reference + // Test set cell rich text with illegal cell reference. assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) richTextRun = []RichTextRun{{Text: strings.Repeat("s", TotalCellChars+1)}} - // Test set cell rich text with characters over the maximum limit + // Test set cell rich text with characters over the maximum limit. assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", richTextRun), ErrCellCharsLength.Error()) } -func TestFormattedValue2(t *testing.T) { +func TestFormattedValue(t *testing.T) { f := NewFile() - assert.Equal(t, "43528", f.formattedValue(0, "43528", false)) + result, err := f.formattedValue(0, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) - assert.Equal(t, "43528", f.formattedValue(15, "43528", false)) + result, err = f.formattedValue(15, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) - assert.Equal(t, "43528", f.formattedValue(1, "43528", false)) + result, err = f.formattedValue(1, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) customNumFmt := "[$-409]MM/DD/YYYY" - _, err := f.NewStyle(&Style{ + _, err = f.NewStyle(&Style{ CustomNumFmt: &customNumFmt, }) assert.NoError(t, err) - assert.Equal(t, "03/04/2019", f.formattedValue(1, "43528", false)) + result, err = f.formattedValue(1, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "03/04/2019", result) - // formatted value with no built-in number format ID + // Test format value with no built-in number format ID. numFmtID := 5 f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: &numFmtID, }) - assert.Equal(t, "43528", f.formattedValue(2, "43528", false)) + result, err = f.formattedValue(2, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) - // formatted value with invalid number format ID + // Test format value with invalid number format ID. f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: nil, }) - assert.Equal(t, "43528", f.formattedValue(3, "43528", false)) + result, err = f.formattedValue(3, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) - // formatted value with empty number format + // Test format value with empty number format. f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{ NumFmtID: &numFmtID, }) - assert.Equal(t, "43528", f.formattedValue(1, "43528", false)) + result, err = f.formattedValue(1, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) - // formatted decimal value with build-in number format ID + // Test format decimal value with build-in number format ID. styleID, err := f.NewStyle(&Style{ NumFmt: 1, }) assert.NoError(t, err) - assert.Equal(t, "311", f.formattedValue(styleID, "310.56", false)) + result, err = f.formattedValue(styleID, "310.56", false) + assert.NoError(t, err) + assert.Equal(t, "311", result) for _, fn := range builtInNumFmtFunc { assert.Equal(t, "0_0", fn("0_0", "", false)) } + + // Test format value with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + _, err = f.formattedValue(1, "43528", false) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestFormattedValueNilXfs(t *testing.T) { // Set the CellXfs to nil and verify that the formattedValue function does not crash. f := NewFile() f.Styles.CellXfs = nil - assert.Equal(t, "43528", f.formattedValue(3, "43528", false)) + result, err := f.formattedValue(3, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) } func TestFormattedValueNilNumFmts(t *testing.T) { // Set the NumFmts value to nil and verify that the formattedValue function does not crash. f := NewFile() f.Styles.NumFmts = nil - assert.Equal(t, "43528", f.formattedValue(3, "43528", false)) + result, err := f.formattedValue(3, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) } func TestFormattedValueNilWorkbook(t *testing.T) { // Set the Workbook value to nil and verify that the formattedValue function does not crash. f := NewFile() f.WorkBook = nil - assert.Equal(t, "43528", f.formattedValue(3, "43528", false)) + result, err := f.formattedValue(3, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) } func TestFormattedValueNilWorkbookPr(t *testing.T) { @@ -770,7 +821,9 @@ func TestFormattedValueNilWorkbookPr(t *testing.T) { // crash. f := NewFile() f.WorkBook.WorkbookPr = nil - assert.Equal(t, "43528", f.formattedValue(3, "43528", false)) + result, err := f.formattedValue(3, "43528", false) + assert.NoError(t, err) + assert.Equal(t, "43528", result) } func TestSharedStringsError(t *testing.T) { diff --git a/chart_test.go b/chart_test.go index 6d40b44..dac724a 100644 --- a/chart_test.go +++ b/chart_test.go @@ -95,6 +95,24 @@ func TestChartSize(t *testing.T) { func TestAddDrawingChart(t *testing.T) { f := NewFile() assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) + + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") +} + +func TestAddSheetDrawingChart(t *testing.T) { + f := NewFile() + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + assert.EqualError(t, f.addSheetDrawingChart(path, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") +} + +func TestDeleteDrawing(t *testing.T) { + f := NewFile() + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + assert.EqualError(t, f.deleteDrawing(0, 0, path, "Chart"), "XML syntax error on line 1: invalid UTF-8") } func TestAddChart(t *testing.T) { @@ -38,6 +38,7 @@ type Cols struct { sheet string f *File sheetXML []byte + sst *xlsxSST } // GetCols gets the value of all cells by columns on the worksheet based on the @@ -87,17 +88,14 @@ func (cols *Cols) Error() error { // Rows return the current column's row values. func (cols *Cols) Rows(opts ...Options) ([]string, error) { - var ( - err error - inElement string - cellCol, cellRow int - rows []string - ) + var rowIterator rowXMLIterator if cols.stashCol >= cols.curCol { - return rows, err + return rowIterator.cells, rowIterator.err } cols.rawCellValue = parseOptions(opts...).RawCellValue - d := cols.f.sharedStringsReader() + if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil { + return rowIterator.cells, rowIterator.err + } decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) for { token, _ := decoder.Token() @@ -106,42 +104,25 @@ func (cols *Cols) Rows(opts ...Options) ([]string, error) { } switch xmlElement := token.(type) { case xml.StartElement: - inElement = xmlElement.Name.Local - if inElement == "row" { - cellCol = 0 - cellRow++ + rowIterator.inElement = xmlElement.Name.Local + if rowIterator.inElement == "row" { + rowIterator.cellCol = 0 + rowIterator.cellRow++ attrR, _ := attrValToInt("r", xmlElement.Attr) if attrR != 0 { - cellRow = attrR + rowIterator.cellRow = attrR } } - if inElement == "c" { - cellCol++ - for _, attr := range xmlElement.Attr { - if attr.Name.Local == "r" { - if cellCol, cellRow, err = CellNameToCoordinates(attr.Value); err != nil { - return rows, err - } - } - } - blank := cellRow - len(rows) - for i := 1; i < blank; i++ { - rows = append(rows, "") - } - if cellCol == cols.curCol { - colCell := xlsxC{} - _ = decoder.DecodeElement(&colCell, &xmlElement) - val, _ := colCell.getValueFrom(cols.f, d, cols.rawCellValue) - rows = append(rows, val) - } + if cols.rowXMLHandler(&rowIterator, &xmlElement, decoder); rowIterator.err != nil { + return rowIterator.cells, rowIterator.err } case xml.EndElement: if xmlElement.Name.Local == "sheetData" { - return rows, err + return rowIterator.cells, rowIterator.err } } } - return rows, err + return rowIterator.cells, rowIterator.err } // columnXMLIterator defined runtime use field for the worksheet column SAX parser. @@ -183,6 +164,30 @@ func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartEleme } } +// rowXMLHandler parse the row XML element of the worksheet. +func (cols *Cols) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, decoder *xml.Decoder) { + if rowIterator.inElement == "c" { + rowIterator.cellCol++ + for _, attr := range xmlElement.Attr { + if attr.Name.Local == "r" { + if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil { + return + } + } + } + blank := rowIterator.cellRow - len(rowIterator.cells) + for i := 1; i < blank; i++ { + rowIterator.cells = append(rowIterator.cells, "") + } + if rowIterator.cellCol == cols.curCol { + colCell := xlsxC{} + _ = decoder.DecodeElement(&colCell, xmlElement) + val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue) + rowIterator.cells = append(rowIterator.cells, val) + } + } +} + // Cols returns a columns iterator, used for streaming reading data for a // worksheet with a large data. This function is concurrency safe. For // example: @@ -420,7 +425,10 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error { if err != nil { return err } - s := f.stylesReader() + s, err := f.stylesReader() + if err != nil { + return err + } s.Lock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { s.Unlock() diff --git a/col_test.go b/col_test.go index f786335..0ed1906 100644 --- a/col_test.go +++ b/col_test.go @@ -56,6 +56,15 @@ func TestCols(t *testing.T) { }) _, err = f.Rows("Sheet1") assert.NoError(t, err) + + // Test columns iterator with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + cols, err = f.Cols("Sheet1") + assert.NoError(t, err) + cols.Next() + _, err = cols.Rows() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestColumnsIterator(t *testing.T) { @@ -316,6 +325,10 @@ func TestSetColStyle(t *testing.T) { assert.NoError(t, err) assert.Equal(t, styleID, cellStyleID) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx"))) + // Test set column style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetColStyle("Sheet1", "C:F", styleID), "XML syntax error on line 1: invalid UTF-8") } func TestColWidth(t *testing.T) { @@ -16,7 +16,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "path/filepath" "strconv" "strings" @@ -24,8 +23,8 @@ import ( // GetComments retrieves all comments and returns a map of worksheet name to // the worksheet comments. -func (f *File) GetComments() (comments map[string][]Comment) { - comments = map[string][]Comment{} +func (f *File) GetComments() (map[string][]Comment, error) { + comments := map[string][]Comment{} for n, path := range f.sheetMap { target := f.getSheetComments(filepath.Base(path)) if target == "" { @@ -34,12 +33,16 @@ func (f *File) GetComments() (comments map[string][]Comment) { if !strings.HasPrefix(target, "/") { target = "xl" + strings.TrimPrefix(target, "..") } - if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil { + cmts, err := f.commentsReader(strings.TrimPrefix(target, "/")) + if err != nil { + return comments, err + } + if cmts != nil { var sheetComments []Comment - for _, comment := range d.CommentList.Comment { + for _, comment := range cmts.CommentList.Comment { sheetComment := Comment{} - if comment.AuthorID < len(d.Authors.Author) { - sheetComment.Author = d.Authors.Author[comment.AuthorID] + if comment.AuthorID < len(cmts.Authors.Author) { + sheetComment.Author = cmts.Authors.Author[comment.AuthorID] } sheetComment.Cell = comment.Ref sheetComment.AuthorID = comment.AuthorID @@ -60,7 +63,7 @@ func (f *File) GetComments() (comments map[string][]Comment) { comments[n] = sheetComments } } - return + return comments, nil } // getSheetComments provides the method to get the target comment reference by @@ -129,7 +132,9 @@ func (f *File) AddComment(sheet string, comment Comment) error { if err = f.addDrawingVML(commentID, drawingVML, comment.Cell, rows+1, cols); err != nil { return err } - f.addComment(commentsXML, comment) + if err = f.addComment(commentsXML, comment); err != nil { + return err + } f.addContentTypePart(commentID, "comments") return err } @@ -139,34 +144,36 @@ func (f *File) AddComment(sheet string, comment Comment) error { // // err := f.DeleteComment("Sheet1", "A30") func (f *File) DeleteComment(sheet, cell string) error { - var err error sheetXMLPath, ok := f.getSheetXMLPath(sheet) if !ok { - err = newNoExistSheetError(sheet) - return err + return newNoExistSheetError(sheet) } commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath)) if !strings.HasPrefix(commentsXML, "/") { commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..") } commentsXML = strings.TrimPrefix(commentsXML, "/") - if comments := f.commentsReader(commentsXML); comments != nil { - for i := 0; i < len(comments.CommentList.Comment); i++ { - cmt := comments.CommentList.Comment[i] + cmts, err := f.commentsReader(commentsXML) + if err != nil { + return err + } + if cmts != nil { + for i := 0; i < len(cmts.CommentList.Comment); i++ { + cmt := cmts.CommentList.Comment[i] if cmt.Ref != cell { continue } - if len(comments.CommentList.Comment) > 1 { - comments.CommentList.Comment = append( - comments.CommentList.Comment[:i], - comments.CommentList.Comment[i+1:]..., + if len(cmts.CommentList.Comment) > 1 { + cmts.CommentList.Comment = append( + cmts.CommentList.Comment[:i], + cmts.CommentList.Comment[i+1:]..., ) i-- continue } - comments.CommentList.Comment = nil + cmts.CommentList.Comment = nil } - f.Comments[commentsXML] = comments + f.Comments[commentsXML] = cmts } return err } @@ -209,7 +216,10 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, }, } // load exist comment shapes from xl/drawings/vmlDrawing%d.vml - d := f.decodeVMLDrawingReader(drawingVML) + d, err := f.decodeVMLDrawingReader(drawingVML) + if err != nil { + return err + } if d != nil { for _, v := range d.Shape { s := xlsxShape{ @@ -274,22 +284,30 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, // addComment provides a function to create chart as xl/comments%d.xml by // given cell and format sets. -func (f *File) addComment(commentsXML string, comment Comment) { +func (f *File) addComment(commentsXML string, comment Comment) error { if comment.Author == "" { comment.Author = "Author" } if len(comment.Author) > MaxFieldLength { comment.Author = comment.Author[:MaxFieldLength] } - comments, authorID := f.commentsReader(commentsXML), 0 - if comments == nil { - comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}} + cmts, err := f.commentsReader(commentsXML) + if err != nil { + return err + } + var authorID int + if cmts == nil { + cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}} + } + if inStrSlice(cmts.Authors.Author, comment.Author, true) == -1 { + cmts.Authors.Author = append(cmts.Authors.Author, comment.Author) + authorID = len(cmts.Authors.Author) - 1 } - if inStrSlice(comments.Authors.Author, comment.Author, true) == -1 { - comments.Authors.Author = append(comments.Authors.Author, comment.Author) - authorID = len(comments.Authors.Author) - 1 + defaultFont, err := f.GetDefaultFont() + if err != nil { + return err } - defaultFont, chars, cmt := f.GetDefaultFont(), 0, xlsxComment{ + chars, cmt := 0, xlsxComment{ Ref: comment.Cell, AuthorID: authorID, Text: xlsxText{R: []xlsxR{}}, @@ -328,8 +346,9 @@ func (f *File) addComment(commentsXML string, comment Comment) { } cmt.Text.R = append(cmt.Text.R, r) } - comments.CommentList.Comment = append(comments.CommentList.Comment, cmt) - f.Comments[commentsXML] = comments + cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt) + f.Comments[commentsXML] = cmts + return err } // countComments provides a function to get comments files count storage in @@ -355,20 +374,18 @@ func (f *File) countComments() int { // decodeVMLDrawingReader provides a function to get the pointer to the // structure after deserialization of xl/drawings/vmlDrawing%d.xml. -func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { - var err error - +func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) { if f.DecodeVMLDrawing[path] == nil { c, ok := f.Pkg.Load(path) if ok && c != nil { f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))). + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))). Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return nil, err } } } - return f.DecodeVMLDrawing[path] + return f.DecodeVMLDrawing[path], nil } // vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml @@ -384,19 +401,18 @@ func (f *File) vmlDrawingWriter() { // commentsReader provides a function to get the pointer to the structure // after deserialization of xl/comments%d.xml. -func (f *File) commentsReader(path string) *xlsxComments { - var err error +func (f *File) commentsReader(path string) (*xlsxComments, error) { if f.Comments[path] == nil { content, ok := f.Pkg.Load(path) if ok && content != nil { f.Comments[path] = new(xlsxComments) - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). Decode(f.Comments[path]); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return nil, err } } } - return f.Comments[path] + return f.Comments[path], nil } // commentsWriter provides a function to save xl/comments%d.xml after diff --git a/comment_test.go b/comment_test.go index 019dc3b..ed44508 100644 --- a/comment_test.go +++ b/comment_test.go @@ -34,16 +34,37 @@ func TestAddComments(t *testing.T) { assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist") // Test add comment on with illegal cell reference assert.EqualError(t, f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + comments, err := f.GetComments() + assert.NoError(t, err) if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) { - assert.Len(t, f.GetComments(), 2) + assert.Len(t, comments, 2) } f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`)) - comments := f.GetComments() + comments, err = f.GetComments() + assert.NoError(t, err) assert.EqualValues(t, 2, len(comments["Sheet1"])) assert.EqualValues(t, 1, len(comments["Sheet2"])) - assert.EqualValues(t, len(NewFile().GetComments()), 0) + comments, err = NewFile().GetComments() + assert.NoError(t, err) + assert.EqualValues(t, len(comments), 0) + + // Test add comments with unsupported charset. + f.Comments["xl/comments2.xml"] = nil + f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) + _, err = f.GetComments() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + + // Test add comments with unsupported charset. + f.Comments["xl/comments2.xml"] = nil + f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8") + + // Test add comments with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteComment(t *testing.T) { @@ -61,19 +82,30 @@ func TestDeleteComment(t *testing.T) { assert.NoError(t, f.DeleteComment("Sheet2", "A40")) - assert.EqualValues(t, 5, len(f.GetComments()["Sheet2"])) - assert.EqualValues(t, len(NewFile().GetComments()), 0) + comments, err := f.GetComments() + assert.NoError(t, err) + assert.EqualValues(t, 5, len(comments["Sheet2"])) + + comments, err = NewFile().GetComments() + assert.NoError(t, err) + assert.EqualValues(t, len(comments), 0) // Test delete all comments in a worksheet assert.NoError(t, f.DeleteComment("Sheet2", "A41")) assert.NoError(t, f.DeleteComment("Sheet2", "C41")) assert.NoError(t, f.DeleteComment("Sheet2", "C42")) - assert.EqualValues(t, 0, len(f.GetComments()["Sheet2"])) + comments, err = f.GetComments() + assert.NoError(t, err) + assert.EqualValues(t, 0, len(comments["Sheet2"])) // Test delete comment on not exists worksheet assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist") // Test delete comment with worksheet part f.Pkg.Delete("xl/worksheets/sheet1.xml") assert.NoError(t, f.DeleteComment("Sheet1", "A22")) + + f.Comments["xl/comments2.xml"] = nil + f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.DeleteComment("Sheet2", "A41"), "XML syntax error on line 1: invalid UTF-8") } func TestDecodeVMLDrawingReader(t *testing.T) { @@ -85,9 +117,11 @@ func TestDecodeVMLDrawingReader(t *testing.T) { func TestCommentsReader(t *testing.T) { f := NewFile() + // Test read comments with unsupported charset. path := "xl/comments1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - f.commentsReader(path) + _, err := f.commentsReader(path) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCountComments(t *testing.T) { diff --git a/docProps.go b/docProps.go index 4ee46ad..ebe929b 100644 --- a/docProps.go +++ b/docProps.go @@ -76,7 +76,6 @@ func (f *File) SetAppProps(appProperties *AppProperties) error { app = new(xlsxProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return err } fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"} @@ -103,7 +102,6 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) { app := new(xlsxProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return } ret, err = &AppProperties{ @@ -182,7 +180,6 @@ func (f *File) SetDocProps(docProperties *DocProperties) error { core = new(decodeCoreProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return err } newProps = &xlsxCoreProperties{ @@ -237,7 +234,6 @@ func (f *File) GetDocProps() (ret *DocProperties, err error) { if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return } ret, err = &DocProperties{ diff --git a/docProps_test.go b/docProps_test.go index 545059d..64b690c 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -42,7 +42,7 @@ func TestSetAppProps(t *testing.T) { // Test unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) - assert.EqualError(t, f.SetAppProps(&AppProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.SetAppProps(&AppProperties{}), "XML syntax error on line 1: invalid UTF-8") } func TestGetAppProps(t *testing.T) { @@ -58,11 +58,11 @@ func TestGetAppProps(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.Close()) - // Test unsupported charset + // Test get application properties with unsupported charset. f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) _, err = f.GetAppProps() - assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetDocProps(t *testing.T) { @@ -94,7 +94,7 @@ func TestSetDocProps(t *testing.T) { // Test unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) - assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8") } func TestGetDocProps(t *testing.T) { @@ -110,9 +110,9 @@ func TestGetDocProps(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.Close()) - // Test unsupported charset + // Test get workbook properties with unsupported charset. f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) _, err = f.GetDocProps() - assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } @@ -15,7 +15,6 @@ import ( "bytes" "encoding/xml" "io" - "log" "reflect" "strconv" "strings" @@ -1194,7 +1193,7 @@ func (f *File) drawPlotAreaTxPr(opts *chartAxisOptions) *cTxPr { // the problem that the label structure is changed after serialization and // deserialization, two different structures: decodeWsDr and encodeWsDr are // defined. -func (f *File) drawingParser(path string) (*xlsxWsDr, int) { +func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) { var ( err error ok bool @@ -1208,7 +1207,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) { decodeWsDr := decodeWsDr{} if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). Decode(&decodeWsDr); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return nil, 0, err } content.R = decodeWsDr.R for _, v := range decodeWsDr.AlternateContent { @@ -1238,7 +1237,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) { } wsDr.Lock() defer wsDr.Unlock() - return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2 + return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2, nil } // addDrawingChart provides a function to add chart graphic frame by given @@ -1254,7 +1253,10 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI width = int(float64(width) * opts.XScale) height = int(float64(height) * opts.YScale) colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = opts.Positioning from := xlsxFrom{} @@ -1302,8 +1304,11 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI // addSheetDrawingChart provides a function to add chart graphic frame for // chartsheet by given sheet, drawingXML, width, height, relationship index // and format sets. -func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) { - content, cNvPrID := f.drawingParser(drawingXML) +func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) error { + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } absoluteAnchor := xdrCellAnchor{ EditAs: opts.Positioning, Pos: &xlsxPoint2D{}, @@ -1336,6 +1341,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOpt } content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor) f.Drawings.Store(drawingXML, content) + return err } // deleteDrawing provides a function to delete chart graphic frame by given by @@ -1354,7 +1360,9 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error "Chart": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic == nil }, "Pic": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic != nil }, } - wsDr, _ = f.drawingParser(drawingXML) + if wsDr, _, err = f.drawingParser(drawingXML); err != nil { + return err + } for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ { if err = nil; wsDr.TwoCellAnchor[idx].From != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) { if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row { @@ -1367,7 +1375,6 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error deTwoCellAnchor = new(decodeTwoCellAnchor) if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeTwoCellAnchor>")). Decode(deTwoCellAnchor); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return err } if err = nil; deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[drawingType](deTwoCellAnchor) { diff --git a/drawing_test.go b/drawing_test.go index e37b771..5c090eb 100644 --- a/drawing_test.go +++ b/drawing_test.go @@ -15,6 +15,8 @@ import ( "encoding/xml" "sync" "testing" + + "github.com/stretchr/testify/assert" ) func TestDrawingParser(t *testing.T) { @@ -24,12 +26,15 @@ func TestDrawingParser(t *testing.T) { } f.Pkg.Store("charset", MacintoshCyrillicCharset) f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`)) - // Test with one cell anchor - f.drawingParser("wsDr") - // Test with unsupported charset - f.drawingParser("charset") - // Test with alternate content + // Test with one cell anchor. + _, _, err := f.drawingParser("wsDr") + assert.NoError(t, err) + // Test with unsupported charset. + _, _, err = f.drawingParser("charset") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test with alternate content. f.Drawings = sync.Map{} f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`)) - f.drawingParser("wsDr") + _, _, err = f.drawingParser("wsDr") + assert.NoError(t, err) } @@ -82,11 +82,6 @@ func newNotWorksheetError(name string) error { return fmt.Errorf("sheet %s is not a worksheet", name) } -// newDecodeXMLError defined the error message on decode XML error. -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 { diff --git a/excelize.go b/excelize.go index 987314b..256d427 100644 --- a/excelize.go +++ b/excelize.go @@ -177,11 +177,13 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) { for k, v := range file { f.Pkg.Store(k, v) } - f.CalcChain = f.calcChainReader() + if f.CalcChain, err = f.calcChainReader(); err != nil { + return f, err + } f.sheetMap = f.getSheetMap() - f.Styles = f.stylesReader() + f.Styles, err = f.stylesReader() f.Theme = f.themeReader() - return f, nil + return f, err } // parseOptions provides a function to parse the optional settings for open @@ -250,7 +252,6 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) { } if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))). Decode(ws); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return } err = nil diff --git a/excelize_test.go b/excelize_test.go index 74895f5..cab994f 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -10,6 +10,7 @@ import ( _ "image/gif" _ "image/jpeg" _ "image/png" + "io" "math" "os" "path/filepath" @@ -217,6 +218,28 @@ func TestOpenReader(t *testing.T) { _, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password", UnzipXMLSizeLimit: UnzipSizeLimit + 1}) assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) + // Test open workbook with unsupported charset internal calculation chain. + source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx")) + assert.NoError(t, err) + buf := new(bytes.Buffer) + zw := zip.NewWriter(buf) + for _, item := range source.File { + // The following statements can be simplified as zw.Copy(item) in go1.17 + writer, err := zw.Create(item.Name) + assert.NoError(t, err) + readerCloser, err := item.Open() + assert.NoError(t, err) + _, err = io.Copy(writer, readerCloser) + assert.NoError(t, err) + } + fi, err := zw.Create(defaultXMLPathCalcChain) + assert.NoError(t, err) + _, err = fi.Write(MacintoshCyrillicCharset) + assert.NoError(t, err) + assert.NoError(t, zw.Close()) + _, err = OpenReader(buf) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test open spreadsheet with unzip size limit. _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100}) assert.EqualError(t, err, newUnzipSizeLimitError(100).Error()) @@ -338,6 +361,9 @@ func TestAddDrawingVML(t *testing.T) { // Test addDrawingVML with illegal cell reference. f := NewFile() assert.EqualError(t, f.addDrawingVML(0, "", "*", 0, 0), newCellNameToCoordinatesError("*", newInvalidCellNameError("*")).Error()) + + f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) + assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", "A1", 0, 0), "XML syntax error on line 1: invalid UTF-8") } func TestSetCellHyperLink(t *testing.T) { @@ -1332,8 +1358,8 @@ func TestWorkSheetReader(t *testing.T) { f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err := f.workSheetReader("Sheet1") - assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") - assert.EqualError(t, f.UpdateLinkedValue(), "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") // Test on no checked worksheet. f = NewFile() @@ -37,11 +37,11 @@ func NewFile() *File { f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook)) f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes)) f.SheetCount = 1 - f.CalcChain = f.calcChainReader() + f.CalcChain, _ = f.calcChainReader() f.Comments = make(map[string]*xlsxComments) f.ContentTypes = f.contentTypesReader() f.Drawings = sync.Map{} - f.Styles = f.stylesReader() + f.Styles, _ = f.stylesReader() f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing) f.WorkBook = f.workbookReader() diff --git a/merge_test.go b/merge_test.go index e0b9210..31f2cf4 100644 --- a/merge_test.go +++ b/merge_test.go @@ -197,3 +197,9 @@ func TestFlatMergedCells(t *testing.T) { ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ""}}}} assert.EqualError(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), "cannot convert cell \"\" to coordinates: invalid cell name \"\"") } + +func TestMergeCellsParser(t *testing.T) { + f := NewFile() + _, err := f.mergeCellsParser(&xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{nil}}}, "A1") + assert.NoError(t, err) +} @@ -281,7 +281,10 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, col-- row-- colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = opts.Positioning from := xlsxFrom{} @@ -559,14 +562,15 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) deTwoCellAnchor *decodeTwoCellAnchor ) - wsDr, _ = f.drawingParser(drawingXML) + if wsDr, _, err = f.drawingParser(drawingXML); err != nil { + return + } if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 { return } deWsDr = new(decodeWsDr) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))). Decode(deWsDr); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return } err = nil @@ -574,7 +578,6 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) deTwoCellAnchor = new(decodeTwoCellAnchor) if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>")). Decode(deTwoCellAnchor); err != nil && err != io.EOF { - err = newDecodeXMLError(err) return } if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil { diff --git a/picture_test.go b/picture_test.go index c34780f..65abf9e 100644 --- a/picture_test.go +++ b/picture_test.go @@ -169,15 +169,25 @@ func TestGetPicture(t *testing.T) { assert.Empty(t, raw) f, err = prepareTestBook1() assert.NoError(t, err) - f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset) - _, _, err = f.getPicture(20, 5, "xl/drawings/drawing1.xml", "xl/drawings/_rels/drawing2.xml.rels") - assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + + // Test get pictures with unsupported charset. + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + _, _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + f.Drawings.Delete(path) + _, _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestAddDrawingPicture(t *testing.T) { // Test addDrawingPicture with illegal cell reference. f := NewFile() assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") } func TestAddPictureFromBytes(t *testing.T) { @@ -16,7 +16,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "math" "os" "strconv" @@ -139,7 +138,10 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) { } var rowIterator rowXMLIterator var token xml.Token - rows.rawCellValue, rows.sst = parseOptions(opts...).RawCellValue, rows.f.sharedStringsReader() + rows.rawCellValue = parseOptions(opts...).RawCellValue + if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil { + return rowIterator.cells, rowIterator.err + } for { if rows.token != nil { token = rows.token @@ -160,21 +162,21 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) { rows.seekRowOpts = extractRowOpts(xmlElement.Attr) if rows.curRow > rows.seekRow { rows.token = nil - return rowIterator.columns, rowIterator.err + return rowIterator.cells, rowIterator.err } } if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil { rows.token = nil - return rowIterator.columns, rowIterator.err + return rowIterator.cells, rowIterator.err } rows.token = nil case xml.EndElement: if xmlElement.Name.Local == "sheetData" { - return rowIterator.columns, rowIterator.err + return rowIterator.cells, rowIterator.err } } } - return rowIterator.columns, rowIterator.err + return rowIterator.cells, rowIterator.err } // extractRowOpts extract row element attributes. @@ -211,10 +213,10 @@ func (err ErrSheetNotExist) Error() string { // rowXMLIterator defined runtime use field for the worksheet row SAX parser. type rowXMLIterator struct { - err error - inElement string - cellCol int - columns []string + err error + inElement string + cellCol, cellRow int + cells []string } // rowXMLHandler parse the row XML element of the worksheet. @@ -228,9 +230,9 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta return } } - blank := rowIterator.cellCol - len(rowIterator.columns) + blank := rowIterator.cellCol - len(rowIterator.cells) if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil { - rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val) + rowIterator.cells = append(appendSpace(blank, rowIterator.cells), val) } } } @@ -409,7 +411,7 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) { // sharedStringsReader provides a function to get the pointer to the structure // after deserialization of xl/sharedStrings.xml. -func (f *File) sharedStringsReader() *xlsxSST { +func (f *File) sharedStringsReader() (*xlsxSST, error) { var err error f.Lock() defer f.Unlock() @@ -419,7 +421,7 @@ func (f *File) sharedStringsReader() *xlsxSST { ss := f.readXML(defaultXMLPathSharedStrings) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))). Decode(&sharedStrings); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return f.SharedStrings, err } if sharedStrings.Count == 0 { sharedStrings.Count = len(sharedStrings.SI) @@ -437,14 +439,14 @@ func (f *File) sharedStringsReader() *xlsxSST { rels := f.relsReader(relPath) for _, rel := range rels.Relationships { if rel.Target == "/xl/sharedStrings.xml" { - return f.SharedStrings + return f.SharedStrings, nil } } // Update workbook.xml.rels f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "") } - return f.SharedStrings + return f.SharedStrings, nil } // SetRowVisible provides a function to set visible of a single row by given @@ -800,7 +802,10 @@ func (f *File) SetRowStyle(sheet string, start, end, styleID int) error { if end > TotalRows { return ErrMaxRows } - s := f.stylesReader() + s, err := f.stylesReader() + if err != nil { + return err + } s.Lock() defer s.Unlock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { diff --git a/rows_test.go b/rows_test.go index 81572e1..5317c22 100644 --- a/rows_test.go +++ b/rows_test.go @@ -55,7 +55,7 @@ func TestRows(t *testing.T) { value, err := f.GetCellValue("Sheet1", "A19") assert.NoError(t, err) assert.Equal(t, "Total:", value) - // Test load shared string table to memory + // Test load shared string table to memory. err = f.SetCellValue("Sheet1", "A19", "A19") assert.NoError(t, err) value, err = f.GetCellValue("Sheet1", "A19") @@ -63,6 +63,14 @@ func TestRows(t *testing.T) { assert.Equal(t, "A19", value) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRow.xlsx"))) assert.NoError(t, f.Close()) + + // Test rows iterator with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + rows, err = f.Rows(sheet2) + assert.NoError(t, err) + _, err = rows.Columns() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestRowsIterator(t *testing.T) { @@ -225,6 +233,7 @@ func TestColumns(t *testing.T) { func TestSharedStringsReader(t *testing.T) { f := NewFile() + // Test read shared string with unsupported charset. f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) f.sharedStringsReader() si := xlsxSI{} @@ -965,12 +974,16 @@ func TestSetRowStyle(t *testing.T) { cellStyleID, err := f.GetCellStyle("Sheet1", "B2") assert.NoError(t, err) assert.Equal(t, style2, cellStyleID) - // Test cell inheritance rows style + // Test cell inheritance rows style. assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil)) cellStyleID, err = f.GetCellStyle("Sheet1", "C1") assert.NoError(t, err) assert.Equal(t, style2, cellStyleID) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx"))) + // Test set row style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, cellStyleID), "XML syntax error on line 1: invalid UTF-8") } func TestNumberFormats(t *testing.T) { @@ -305,8 +305,7 @@ func (f *File) AddShape(sheet, cell, opts string) error { f.addSheetDrawing(sheet, rID) f.addSheetNameSpace(sheet, SourceRelationship) } - err = f.addDrawingShape(sheet, drawingXML, cell, options) - if err != nil { + if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil { return err } f.addContentTypePart(drawingID, "drawings") @@ -328,7 +327,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = opts.Format.Positioning from := xlsxFrom{} @@ -385,6 +387,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption W: f.ptToEMUs(opts.Line.Width), } } + defaultFont, err := f.GetDefaultFont() + if err != nil { + return err + } if len(opts.Paragraph) < 1 { opts.Paragraph = []shapeParagraphOptions{ { @@ -392,7 +398,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption Bold: false, Italic: false, Underline: "none", - Family: f.GetDefaultFont(), + Family: defaultFont, Size: 11, Color: "#000000", }, diff --git a/shape_test.go b/shape_test.go index 829a9e5..9d1da8a 100644 --- a/shape_test.go +++ b/shape_test.go @@ -87,4 +87,15 @@ func TestAddShape(t *testing.T) { } }`)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) + // Test set row style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") +} + +func TestAddDrawingShape(t *testing.T) { + f := NewFile() + path := "xl/drawings/drawing1.xml" + f.Pkg.Store(path, MacintoshCyrillicCharset) + assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", &shapeOptions{}), "XML syntax error on line 1: invalid UTF-8") } @@ -881,10 +881,12 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, var ( cellName, inElement string cellCol, row int - d *xlsxSST + sst *xlsxSST ) - d = f.sharedStringsReader() + if sst, err = f.sharedStringsReader(); err != nil { + return + } decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name))) for { var token xml.Token @@ -907,7 +909,7 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, if inElement == "c" { colCell := xlsxC{} _ = decoder.DecodeElement(&colCell, &xmlElement) - val, _ := colCell.getValueFrom(f, d, false) + val, _ := colCell.getValueFrom(f, sst, false) if regSearch { regex := regexp.MustCompile(value) if !regex.MatchString(val) { diff --git a/sheet_test.go b/sheet_test.go index 4e1e448..4b9d31e 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -91,6 +91,12 @@ func TestSearchSheet(t *testing.T) { result, err = f.SearchSheet("Sheet1", "A") assert.EqualError(t, err, "invalid cell reference [1, 0]") assert.Equal(t, []string(nil), result) + + // Test search sheet with unsupported charset shared strings table. + f.SharedStrings = nil + f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) + _, err = f.SearchSheet("Sheet1", "A") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetPageLayout(t *testing.T) { diff --git a/stream_test.go b/stream_test.go index 040eee0..65af283 100644 --- a/stream_test.go +++ b/stream_test.go @@ -106,12 +106,12 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, streamWriter.rawData.tmp.Close()) assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) - // Test unsupported charset + // Test create stream writer with unsupported charset. file = NewFile() file.Sheet.Delete("xl/worksheets/sheet1.xml") file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err = file.NewStreamWriter("Sheet1") - assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, file.Close()) // Test read cell. @@ -1037,15 +1037,15 @@ func formatToE(v, format string, date1904 bool) string { // stylesReader provides a function to get the pointer to the structure after // deserialization of xl/styles.xml. -func (f *File) stylesReader() *xlsxStyleSheet { +func (f *File) stylesReader() (*xlsxStyleSheet, error) { if f.Styles == nil { f.Styles = new(xlsxStyleSheet) if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))). Decode(f.Styles); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return f.Styles, err } } - return f.Styles + return f.Styles, nil } // styleSheetWriter provides a function to save xl/styles.xml after serialize @@ -1965,9 +1965,12 @@ func parseFormatStyleSet(style interface{}) (*Style, error) { // // Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 func (f *File) NewStyle(style interface{}) (int, error) { - var fs *Style - var err error - var cellXfsID, fontID, borderID, fillID int + var ( + fs *Style + font *xlsxFont + err error + cellXfsID, fontID, borderID, fillID int + ) fs, err = parseFormatStyleSet(style) if err != nil { return cellXfsID, err @@ -1975,21 +1978,25 @@ func (f *File) NewStyle(style interface{}) (int, error) { if fs.DecimalPlaces == 0 { fs.DecimalPlaces = 2 } - s := f.stylesReader() + s, err := f.stylesReader() + if err != nil { + return cellXfsID, err + } s.Lock() defer s.Unlock() // check given style already exist. - if cellXfsID = f.getStyleID(s, fs); cellXfsID != -1 { + if cellXfsID, err = f.getStyleID(s, fs); err != nil || cellXfsID != -1 { return cellXfsID, err } numFmtID := newNumFmt(s, fs) if fs.Font != nil { - fontID = f.getFontID(s, fs) + fontID, _ = f.getFontID(s, fs) if fontID == -1 { s.Fonts.Count++ - s.Fonts.Font = append(s.Fonts.Font, f.newFont(fs)) + font, _ = f.newFont(fs) + s.Fonts.Font = append(s.Fonts.Font, font) fontID = s.Fonts.Count - 1 } } @@ -2065,12 +2072,19 @@ var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{ // getStyleID provides a function to get styleID by given style. If given // style does not exist, will return -1. -func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) { - styleID = -1 +func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) { + var ( + err error + fontID int + styleID = -1 + ) if ss.CellXfs == nil { - return + return styleID, err + } + numFmtID, borderID, fillID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style) + if fontID, err = f.getFontID(ss, style); err != nil { + return styleID, err } - numFmtID, borderID, fillID, fontID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style), f.getFontID(ss, style) if style.CustomNumFmt != nil { numFmtID = getCustomNumFmtID(ss, style) } @@ -2082,10 +2096,10 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) { getXfIDFuncs["alignment"](0, xf, style) && getXfIDFuncs["protection"](0, xf, style) { styleID = xfID - return + return styleID, err } } - return + return styleID, err } // NewConditionalStyle provides a function to create style for conditional @@ -2093,7 +2107,10 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) { // function. Note that the color field uses RGB color code and only support to // set font, fills, alignment and borders currently. func (f *File) NewConditionalStyle(style string) (int, error) { - s := f.stylesReader() + s, err := f.stylesReader() + if err != nil { + return 0, err + } fs, err := parseFormatStyleSet(style) if err != nil { return 0, err @@ -2108,7 +2125,7 @@ func (f *File) NewConditionalStyle(style string) (int, error) { dxf.Border = newBorders(fs) } if fs.Font != nil { - dxf.Font = f.newFont(fs) + dxf.Font, _ = f.newFont(fs) } dxfStr, _ := xml.Marshal(dxf) if s.Dxfs == nil { @@ -2123,41 +2140,56 @@ func (f *File) NewConditionalStyle(style string) (int, error) { // GetDefaultFont provides the default font name currently set in the // workbook. The spreadsheet generated by excelize default font is Calibri. -func (f *File) GetDefaultFont() string { - font := f.readDefaultFont() - return *font.Name.Val +func (f *File) GetDefaultFont() (string, error) { + font, err := f.readDefaultFont() + if err != nil { + return "", err + } + return *font.Name.Val, err } // SetDefaultFont changes the default font in the workbook. -func (f *File) SetDefaultFont(fontName string) { - font := f.readDefaultFont() +func (f *File) SetDefaultFont(fontName string) error { + font, err := f.readDefaultFont() + if err != nil { + return err + } font.Name.Val = stringPtr(fontName) - s := f.stylesReader() + s, _ := f.stylesReader() s.Fonts.Font[0] = font custom := true s.CellStyles.CellStyle[0].CustomBuiltIn = &custom + return err } // readDefaultFont provides an un-marshalled font value. -func (f *File) readDefaultFont() *xlsxFont { - s := f.stylesReader() - return s.Fonts.Font[0] +func (f *File) readDefaultFont() (*xlsxFont, error) { + s, err := f.stylesReader() + if err != nil { + return nil, err + } + return s.Fonts.Font[0], err } // getFontID provides a function to get font ID. // If given font does not exist, will return -1. -func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (fontID int) { - fontID = -1 +func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (int, error) { + var err error + fontID := -1 if styleSheet.Fonts == nil || style.Font == nil { - return + return fontID, err } for idx, fnt := range styleSheet.Fonts.Font { - if reflect.DeepEqual(*fnt, *f.newFont(style)) { + font, err := f.newFont(style) + if err != nil { + return fontID, err + } + if reflect.DeepEqual(*fnt, *font) { fontID = idx - return + return fontID, err } } - return + return fontID, err } // newFontColor set font color by given styles. @@ -2190,7 +2222,8 @@ func newFontColor(font *Font) *xlsxColor { // newFont provides a function to add font style by given cell format // settings. -func (f *File) newFont(style *Style) *xlsxFont { +func (f *File) newFont(style *Style) (*xlsxFont, error) { + var err error if style.Font.Size < MinFontSize { style.Font.Size = 11 } @@ -2207,7 +2240,9 @@ func (f *File) newFont(style *Style) *xlsxFont { fnt.I = &attrValBool{Val: &style.Font.Italic} } if *fnt.Name.Val == "" { - *fnt.Name.Val = f.GetDefaultFont() + if *fnt.Name.Val, err = f.GetDefaultFont(); err != nil { + return &fnt, err + } } if style.Font.Strike { fnt.Strike = &attrValBool{Val: &style.Font.Strike} @@ -2215,7 +2250,7 @@ func (f *File) newFont(style *Style) *xlsxFont { if idx := inStrSlice(supportedUnderlineTypes, style.Font.Underline, true); idx != -1 { fnt.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])} } - return &fnt + return &fnt, err } // getNumFmtID provides a function to get number format code ID. @@ -2754,7 +2789,10 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { ws.Lock() defer ws.Unlock() - s := f.stylesReader() + s, err := f.stylesReader() + if err != nil { + return err + } s.Lock() defer s.Unlock() if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { diff --git a/styles_test.go b/styles_test.go index 487a6df..605ad07 100644 --- a/styles_test.go +++ b/styles_test.go @@ -30,7 +30,8 @@ func TestStyleFill(t *testing.T) { styleID, err := xl.NewStyle(testCase.format) assert.NoError(t, err) - styles := xl.stylesReader() + styles, err := xl.stylesReader() + assert.NoError(t, err) style := styles.CellXfs.Xf[styleID] if testCase.expectFill { assert.NotEqual(t, *style.FillID, 0, testCase.label) @@ -220,7 +221,8 @@ func TestNewStyle(t *testing.T) { f := NewFile() styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`) assert.NoError(t, err) - styles := f.stylesReader() + styles, err := f.stylesReader() + assert.NoError(t, err) fontID := styles.CellXfs.Xf[styleID].FontID font := styles.Fonts.Font[*fontID] assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name") @@ -238,7 +240,7 @@ func TestNewStyle(t *testing.T) { _, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}}) assert.EqualError(t, err, ErrFontSize.Error()) - // new numeric custom style + // Test create numeric custom style. numFmt := "####;####" f.Styles.NumFmts = nil styleID, err = f.NewStyle(&Style{ @@ -254,7 +256,7 @@ func TestNewStyle(t *testing.T) { nf := f.Styles.CellXfs.Xf[styleID] assert.Equal(t, 164, *nf.NumFmtID) - // new currency custom style + // Test create currency custom style. f.Styles.NumFmts = nil styleID, err = f.NewStyle(&Style{ Lang: "ko-kr", @@ -271,7 +273,7 @@ func TestNewStyle(t *testing.T) { nf = f.Styles.CellXfs.Xf[styleID] assert.Equal(t, 32, *nf.NumFmtID) - // Test set build-in scientific number format + // Test set build-in scientific number format. styleID, err = f.NewStyle(&Style{NumFmt: 11}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", styleID)) @@ -281,7 +283,7 @@ func TestNewStyle(t *testing.T) { assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows) f = NewFile() - // Test currency number format + // Test currency number format. customNumFmt := "[$$-409]#,##0.00" style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) @@ -306,21 +308,48 @@ func TestNewStyle(t *testing.T) { style5, err := f.NewStyle(&Style{NumFmt: 160, Lang: "zh-cn"}) assert.NoError(t, err) assert.Equal(t, 0, style5) + + // Test create style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + _, err = f.NewStyle(&Style{NumFmt: 165}) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") +} + +func TestNewConditionalStyle(t *testing.T) { + f := NewFile() + // Test create conditional style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + _, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestGetDefaultFont(t *testing.T) { f := NewFile() - s := f.GetDefaultFont() + s, err := f.GetDefaultFont() + assert.NoError(t, err) assert.Equal(t, s, "Calibri", "Default font should be Calibri") + // Test get default font with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + _, err = f.GetDefaultFont() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestSetDefaultFont(t *testing.T) { f := NewFile() - f.SetDefaultFont("Arial") - styles := f.stylesReader() - s := f.GetDefaultFont() + assert.NoError(t, f.SetDefaultFont("Arial")) + styles, err := f.stylesReader() + assert.NoError(t, err) + s, err := f.GetDefaultFont() + assert.NoError(t, err) assert.Equal(t, s, "Arial", "Default font should change to Arial") assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true) + // Test set default font with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetDefaultFont("Arial"), "XML syntax error on line 1: invalid UTF-8") } func TestStylesReader(t *testing.T) { @@ -328,7 +357,9 @@ func TestStylesReader(t *testing.T) { // Test read styles with unsupported charset. f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) - assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader()) + styles, err := f.stylesReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + assert.EqualValues(t, new(xlsxStyleSheet), styles) } func TestThemeReader(t *testing.T) { @@ -346,14 +377,33 @@ func TestSetCellStyle(t *testing.T) { assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", -1), newInvalidStyleID(-1).Error()) // Test set cell style with not exists style ID. assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 10), newInvalidStyleID(10).Error()) + // Test set cell style with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8") } func TestGetStyleID(t *testing.T) { - assert.Equal(t, -1, NewFile().getStyleID(&xlsxStyleSheet{}, nil)) + f := NewFile() + styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil) + assert.NoError(t, err) + assert.Equal(t, -1, styleID) + // Test get style ID with unsupported charset style sheet. + f.Styles = nil + f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) + _, err = f.getStyleID(&xlsxStyleSheet{ + CellXfs: &xlsxCellXfs{}, + Fonts: &xlsxFonts{ + Font: []*xlsxFont{{}}, + }, + }, &Style{NumFmt: 0, Font: &Font{}}) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestGetFillID(t *testing.T) { - assert.Equal(t, -1, getFillID(NewFile().stylesReader(), &Style{Fill: Fill{Type: "unknown"}})) + styles, err := NewFile().stylesReader() + assert.NoError(t, err) + assert.Equal(t, -1, getFillID(styles, &Style{Fill: Fill{Type: "unknown"}})) } func TestThemeColor(t *testing.T) { |