diff --git a/README.md b/README.md index 89d2d001e3..f73266fff0 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,11 @@ import ( ) func main() { - f := excelize.NewFile() + f, err := excelize.NewFile() + if err != nil { + fmt.Println(err) + return + } // Create a new sheet. index := f.NewSheet("Sheet2") // Set value of a cell. @@ -126,7 +130,11 @@ func main() { "B1": "Apple", "C1": "Orange", "D1": "Pear"} values := map[string]int{ "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} - f := excelize.NewFile() + f, err := excelize.NewFile() + if err != nil { + fmt.Println(err) + return + } for k, v := range categories { f.SetCellValue("Sheet1", k, v) } diff --git a/README_zh.md b/README_zh.md index d67b63cb03..458363bd15 100644 --- a/README_zh.md +++ b/README_zh.md @@ -43,7 +43,11 @@ import ( ) func main() { - f := excelize.NewFile() + f, err := excelize.NewFile() + if err != nil { + fmt.Println(err) + return + } // 创建一个工作表 index := f.NewSheet("Sheet2") // 设置单元格的值 @@ -126,7 +130,11 @@ func main() { "B1": "Apple", "C1": "Orange", "D1": "Pear"} values := map[string]int{ "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} - f := excelize.NewFile() + f, err := excelize.NewFile() + if err != nil { + fmt.Println(err) + return + } for k, v := range categories { f.SetCellValue("Sheet1", k, v) } diff --git a/adjust_test.go b/adjust_test.go index ab6bedcc06..445badd9dc 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -7,7 +7,8 @@ import ( ) func TestAdjustMergeCells(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // testing adjustAutoFilter with illegal cell coordinates. assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ @@ -264,7 +265,8 @@ func TestAdjustMergeCells(t *testing.T) { assert.Equal(t, 0, len(c.ws.MergeCells.Cells), c.label) } - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) p1, p2 := f.adjustMergeCellsHelper(2, 1, 0, 0) assert.Equal(t, 1, p1) assert.Equal(t, 2, p2) @@ -272,7 +274,8 @@ func TestAdjustMergeCells(t *testing.T) { } func TestAdjustAutoFilter(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.adjustAutoFilter(&xlsxWorksheet{ SheetData: xlsxSheetData{ Row: []xlsxRow{{Hidden: true, R: 2}}, @@ -295,7 +298,8 @@ func TestAdjustAutoFilter(t *testing.T) { } func TestAdjustHelper(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.NewSheet("Sheet2") f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, @@ -311,7 +315,8 @@ func TestAdjustHelper(t *testing.T) { } func TestAdjustCalcChain(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.CalcChain = &xlsxCalcChain{ C: []xlsxCalcChainC{ {R: "B2", I: 2}, {R: "B2", I: 1}, diff --git a/calc_test.go b/calc_test.go index 8ad3c775f7..aec833f1c4 100644 --- a/calc_test.go +++ b/calc_test.go @@ -11,7 +11,10 @@ import ( ) func prepareCalcData(cellData [][]interface{}) *File { - f := NewFile() + f, err := NewFile() + if err != nil { + return nil + } for r, row := range cellData { for c, value := range row { cell, _ := CoordinatesToCellName(c+1, r+1) @@ -4673,7 +4676,9 @@ func TestCalcDatabase(t *testing.T) { } func TestCalcFORMULATEXT(t *testing.T) { - f, formulaText := NewFile(), "=SUM(B1:C1)" + f, err := NewFile() + assert.NoError(t, err) + formulaText := "=SUM(B1:C1)" assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formulaText)) for _, formula := range []string{"=FORMULATEXT(A1)", "=FORMULATEXT(A:A)", "=FORMULATEXT(A1:B1)"} { assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula), formula) @@ -4918,7 +4923,8 @@ func TestCalcIRR(t *testing.T) { } func TestCalcMAXMINIFS(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, row := range map[string][]interface{}{ "A1": {1, -math.MaxFloat64 - 1}, "A2": {2, -math.MaxFloat64 - 2}, @@ -5202,7 +5208,8 @@ func TestCalcXNPV(t *testing.T) { } func TestCalcMATCH(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, row := range map[string][]interface{}{ "A1": {"cccc", 7, 4, 16}, "A2": {"dddd", 2, 6, 11}, @@ -5244,7 +5251,8 @@ func TestCalcMATCH(t *testing.T) { } func TestCalcISFORMULA(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "=ISFORMULA(A1)")) for _, formula := range []string{"=NA()", "=SUM(A1:A3)"} { assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula)) @@ -5384,7 +5392,8 @@ func TestCalcSLOP(t *testing.T) { } func TestCalcSHEET(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.NewSheet("Sheet2") formulaList := map[string]string{ "=SHEET(\"Sheet2\")": "2", @@ -5400,7 +5409,8 @@ func TestCalcSHEET(t *testing.T) { } func TestCalcSHEETS(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.NewSheet("Sheet2") formulaList := map[string]string{ "=SHEETS(Sheet1!A1:B1)": "1", @@ -5629,7 +5639,8 @@ func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) { } func TestCalcZTEST(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{4, 5, 2, 5, 8, 9, 3, 2, 3, 8, 9, 5})) formulaList := map[string]string{ "=Z.TEST(A1:L1,5)": "0.371103278558538", @@ -5700,7 +5711,8 @@ func TestCalcBetainvProbIterator(t *testing.T) { } func TestNestedFunctionsWithOperators(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) formulaList := map[string]string{ `=LEN("KEEP")`: "4", `=LEN("REMOVEKEEP") - LEN("REMOVE")`: "4", diff --git a/calcchain.go b/calcchain.go index a1f9c0c56a..80f6423c9a 100644 --- a/calcchain.go +++ b/calcchain.go @@ -14,33 +14,42 @@ package excelize import ( "bytes" "encoding/xml" + "fmt" "io" - "log" ) -// calcChainReader provides a function to get the pointer to the structure +// NewCalcChainReader provides a function to get the pointer to the structure // after deserialization of xl/calcChain.xml. -func (f *File) calcChainReader() *xlsxCalcChain { +func (f *File) NewCalcChainReader() (*xlsxCalcChain, error) { var err error if f.CalcChain == nil { f.CalcChain = new(xlsxCalcChain) 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, fmt.Errorf("xml decode error: %w", err) } } + return f.CalcChain, nil +} + +// calcChainReader provides a function to get the CalcChain. +func (f *File) calcChainReader() *xlsxCalcChain { return f.CalcChain } // calcChainWriter provides a function to save xl/calcChain.xml after // serialize structure. -func (f *File) calcChainWriter() { +func (f *File) calcChainWriter() error { if f.CalcChain != nil && f.CalcChain.C != nil { - output, _ := xml.Marshal(f.CalcChain) + output, err := xml.Marshal(f.CalcChain) + if err != nil { + return err + } f.saveFileList(defaultXMLPathCalcChain, output) } + return nil } // deleteCalcChain provides a function to remove cell reference on the @@ -52,10 +61,13 @@ func (f *File) deleteCalcChain(index int, axis string) { return !((c.I == index && c.R == axis) || (c.I == index && axis == "")) }) } - if len(calc.C) == 0 { + if calc == nil || len(calc.C) == 0 { f.CalcChain = nil f.Pkg.Delete(defaultXMLPathCalcChain) content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for k, v := range content.Overrides { diff --git a/calcchain_test.go b/calcchain_test.go index c36655bc5e..eceeb80cdb 100644 --- a/calcchain_test.go +++ b/calcchain_test.go @@ -1,16 +1,21 @@ package excelize -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestCalcChainReader(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) - f.calcChainReader() } func TestDeleteCalcChain(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}} f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{ PartName: "/xl/calcChain.xml", diff --git a/cell.go b/cell.go index 286085b3ab..5e8b3edbc3 100644 --- a/cell.go +++ b/cell.go @@ -62,8 +62,12 @@ var cellTypes = map[string]CellType{ // returned, along with the raw value of the cell. All cells' values will be // the same in a merged range. func (f *File) GetCellValue(sheet, axis string, opts ...Options) (string, error) { + sst, err := f.sharedStringsReader() + if err != nil { + return "", err + } return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { - val, err := c.getValueFrom(f, f.sharedStringsReader(), parseOptions(opts...).RawCellValue) + val, err := c.getValueFrom(f, sst, parseOptions(opts...).RawCellValue) return val, true, err }) } @@ -404,7 +408,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 { @@ -544,7 +551,11 @@ type FormulaOpts struct { // ) // // func main() { -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { // if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil { // fmt.Println(err) @@ -612,7 +623,7 @@ func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) func (ws *xlsxWorksheet) setSharedFormula(ref string) error { coordinates, err := areaRefToCoordinates(ref) if err != nil { - return err + return fmt.Errorf("setSharedFormula: %w", err) } _ = sortCoordinates(coordinates) cnt := ws.countSharedFormula() @@ -627,7 +638,7 @@ func (ws *xlsxWorksheet) setSharedFormula(ref string) error { cell.F.Si = &cnt } } - return err + return nil } // countSharedFormula count shared formula in the given worksheet. @@ -747,7 +758,10 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string, opts ...Hype case "External": sheetPath := f.sheetMap[trimSheetName(sheet)] sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" - rID := f.setRels(linkData.RID, sheetRels, SourceRelationshipHyperLink, link, linkType) + rID, err := f.setRels(linkData.RID, sheetRels, SourceRelationshipHyperLink, link, linkType) + if err != nil { + return err + } linkData = xlsxHyperlink{ Ref: axis, } @@ -826,7 +840,10 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro if err != nil || cellData.T != "s" { return } - sst := f.sharedStringsReader() + sst, err := f.sharedStringsReader() + if err != nil { + return + } if len(sst.SI) <= siIdx || siIdx < 0 { return } @@ -878,7 +895,11 @@ func newRpr(fnt *Font) *xlsxRPr { // ) // // func main() { -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // if err := f.SetRowHeight("Sheet1", 1, 35); err != nil { // fmt.Println(err) // return @@ -998,7 +1019,10 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { } cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) si := xlsxSI{} - sst := f.sharedStringsReader() + sst, err := f.sharedStringsReader() + if err != nil { + return err + } var textRuns []xlsxR totalCellChars := 0 for _, textRun := range runs { @@ -1133,16 +1157,17 @@ func (f *File) getCellStringFunc(sheet, axis 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) (formatted string) { + formatted = v if raw { - return v + return } if s == 0 { - return v + return } styleSheet := f.stylesReader() if s >= len(styleSheet.CellXfs.Xf) { - return v + return } var numFmtID int if styleSheet.CellXfs.Xf[s].NumFmtID != nil { @@ -1153,17 +1178,19 @@ 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) + formatted = ok(v, builtInNumFmt[numFmtID], date1904) + return } if styleSheet == nil || styleSheet.NumFmts == nil { - return v + return } for _, xlsxFmt := range styleSheet.NumFmts.NumFmt { if xlsxFmt.NumFmtID == numFmtID { - return format(v, xlsxFmt.FormatCode, date1904) + formatted = format(v, xlsxFmt.FormatCode, date1904) + return } } - return v + return } // prepareCellStyle provides a function to prepare style index of cell in diff --git a/cell_test.go b/cell_test.go index fb1e8ef585..8deac006b0 100644 --- a/cell_test.go +++ b/cell_test.go @@ -79,7 +79,8 @@ func TestConcurrency(t *testing.T) { } func TestCheckCellInArea(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) expectedTrueCellInAreaList := [][2]string{ {"c2", "A1:AAZ32"}, {"B9", "A1:B9"}, @@ -122,7 +123,8 @@ func TestCheckCellInArea(t *testing.T) { func TestSetCellFloat(t *testing.T) { sheet := "Sheet1" t.Run("with no decimal", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.0, -1, 64)) assert.NoError(t, f.SetCellFloat(sheet, "A2", 123.0, 1, 64)) val, err := f.GetCellValue(sheet, "A1") @@ -134,7 +136,8 @@ func TestSetCellFloat(t *testing.T) { }) t.Run("with a decimal and precision limit", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, 1, 64)) val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) @@ -142,18 +145,21 @@ func TestSetCellFloat(t *testing.T) { }) t.Run("with a decimal and no limit", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellFloat(sheet, "A1", 123.42, -1, 64)) val, err := f.GetCellValue(sheet, "A1") assert.NoError(t, err) assert.Equal(t, "123.42", val, "A1 should be 123.42") }) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.SetCellFloat(sheet, "A", 123.42, -1, 64), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestSetCellValue(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test set cell value with column and row style inherit @@ -174,8 +180,9 @@ func TestSetCellValue(t *testing.T) { } func TestSetCellValues(t *testing.T) { - f := NewFile() - err := f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC)) + f, err := NewFile() + assert.NoError(t, err) + err = f.SetCellValue("Sheet1", "A1", time.Date(2010, time.December, 31, 0, 0, 0, 0, time.UTC)) assert.NoError(t, err) v, err := f.GetCellValue("Sheet1", "A1") @@ -192,7 +199,8 @@ func TestSetCellValues(t *testing.T) { } func TestSetCellBool(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } @@ -216,7 +224,8 @@ func TestSetCellTime(t *testing.T) { func TestGetCellValue(t *testing.T) { // Test get cell value without r attribute of the row. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheetData := `%s` f.Sheet.Delete("xl/worksheets/sheet1.xml") @@ -349,7 +358,8 @@ func TestGetCellValue(t *testing.T) { } func TestGetCellType(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) cellType, err := f.GetCellType("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, CellTypeUnset, cellType) @@ -362,17 +372,21 @@ func TestGetCellType(t *testing.T) { } func TestGetValueFrom(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) 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) } func TestGetCellFormula(t *testing.T) { // Test get cell formula on not exist worksheet. - f := NewFile() - _, err := f.GetCellFormula("SheetN", "A1") + f, err := NewFile() + assert.NoError(t, err) + _, err = f.GetCellFormula("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN is not exist") // Test get cell formula on no formula cell. @@ -381,7 +395,8 @@ func TestGetCellFormula(t *testing.T) { assert.NoError(t, err) // Test get cell shared formula - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) sheetData := `12*A12%s34567` for sharedFormula, expected := range map[string]string{ @@ -404,7 +419,10 @@ func TestGetCellFormula(t *testing.T) { } func ExampleFile_SetCellFloat() { - f := NewFile() + f, err := NewFile() + if err != nil { + fmt.Println(err) + } x := 3.14159265 if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil { fmt.Println(err) @@ -417,7 +435,8 @@ func ExampleFile_SetCellFloat() { func BenchmarkSetCellValue(b *testing.B) { values := []string{"First", "Second", "Third", "Fourth", "Fifth", "Sixth"} cols := []string{"A", "B", "C", "D", "E", "F"} - f := NewFile() + f, err := NewFile() + assert.NoError(b, err) b.ResetTimer() for i := 1; i <= b.N; i++ { for j := 0; j < len(values); j++ { @@ -468,7 +487,8 @@ func TestSetCellFormula(t *testing.T) { assert.NoError(t, f.Close()) // Test set shared formula for the cells. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) for r := 1; r <= 5; r++ { assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", r), &[]interface{}{r, r + 1})) } @@ -482,11 +502,12 @@ func TestSetCellFormula(t *testing.T) { ref = "D1:D5" assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType})) ref = "" - assert.EqualError(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}), ErrParameterInvalid.Error()) + assert.ErrorIs(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}), ErrRangeLength) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula5.xlsx"))) // Test set table formula for the cells. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row)) } @@ -497,7 +518,8 @@ func TestSetCellFormula(t *testing.T) { } func TestGetCellRichText(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) runsSource := []RichTextRun{ { @@ -562,7 +584,8 @@ func TestGetCellRichText(t *testing.T) { } func TestSetCellRichText(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35)) assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 44)) richTextRun := []RichTextRun{ @@ -661,7 +684,8 @@ func TestSetCellRichText(t *testing.T) { } func TestFormattedValue2(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) v := f.formattedValue(0, "43528", false) assert.Equal(t, "43528", v) @@ -671,7 +695,7 @@ func TestFormattedValue2(t *testing.T) { v = f.formattedValue(1, "43528", false) assert.Equal(t, "43528", v) customNumFmt := "[$-409]MM/DD/YYYY" - _, err := f.NewStyle(&Style{ + _, err = f.NewStyle(&Style{ CustomNumFmt: &customNumFmt, }) assert.NoError(t, err) diff --git a/chart.go b/chart.go index 7dcbe19bec..12ec96303d 100644 --- a/chart.go +++ b/chart.go @@ -514,7 +514,11 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) { // "B1": "Apple", "C1": "Orange", "D1": "Pear"} // values := map[string]int{ // "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // for k, v := range categories { // f.SetCellValue("Sheet1", k, v) // } @@ -787,7 +791,11 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) { // "B1": "Apple", "C1": "Orange", "D1": "Pear"} // values := map[string]int{ // "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // for k, v := range categories { // f.SetCellValue("Sheet1", k, v) // } @@ -883,6 +891,10 @@ func parseFormatChartSet(formatSet string) (*formatChart, error) { // } // func (f *File) AddChart(sheet, cell, format string, combo ...string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + // Read sheet data. ws, err := f.workSheetReader(sheet) if err != nil { @@ -896,14 +908,23 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error { drawingID := f.countDrawings() + 1 chartID := f.countCharts() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" - drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + drawingID, drawingXML, err = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + if err != nil { + return err + } drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" - drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + drawingRID, err := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + if err != nil { + return err + } err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format) if err != nil { return err } - f.addChart(formatSet, comboCharts) + err = f.addChart(formatSet, comboCharts) + if err != nil { + return err + } f.addContentTypePart(chartID, "chart") f.addContentTypePart(drawingID, "drawings") f.addSheetNameSpace(sheet, SourceRelationship) @@ -915,6 +936,10 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error { // and properties set. In Excel a chartsheet is a worksheet that only contains // a chart. func (f *File) AddChartSheet(sheet, format string, combo ...string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + // Check if the worksheet already exists if f.GetSheetIndex(sheet) != -1 { return ErrExistsWorksheet @@ -943,19 +968,41 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error { drawingID := f.countDrawings() + 1 chartID := f.countCharts() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" - f.prepareChartSheetDrawing(&cs, drawingID, sheet) + err = f.prepareChartSheetDrawing(&cs, drawingID, sheet) + if err != nil { + return err + } drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" - drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") - f.addSheetDrawingChart(drawingXML, drawingRID, &formatSet.Format) - f.addChart(formatSet, comboCharts) + drawingRID, err := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + if err != nil { + return err + } + err = f.addSheetDrawingChart(drawingXML, drawingRID, &formatSet.Format) + if err != nil { + return err + } + err = f.addChart(formatSet, comboCharts) + if err != nil { + return err + } f.addContentTypePart(chartID, "chart") f.addContentTypePart(sheetID, "chartsheet") f.addContentTypePart(drawingID, "drawings") + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return err + } // Update workbook.xml.rels - rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") + rID, err := f.addRels(wrp, SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") + if err != nil { + return err + } // Update workbook.xml f.setWorkbook(sheet, sheetID, rID) - chartsheet, _ := xml.Marshal(cs) + chartsheet, err := xml.Marshal(cs) + if err != nil { + return err + } f.addSheetNameSpace(sheet, NameSpaceSpreadSheet) f.saveFileList(path, replaceRelationshipsBytes(f.replaceNameSpaceBytes(path, chartsheet))) return err diff --git a/chart_test.go b/chart_test.go index 9f2287e802..3c3975de02 100644 --- a/chart_test.go +++ b/chart_test.go @@ -11,7 +11,8 @@ import ( ) func TestChartSize(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) categories := map[string]string{ @@ -93,7 +94,8 @@ func TestChartSize(t *testing.T) { } func TestAddDrawingChart(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) } @@ -212,7 +214,8 @@ func TestAddChart(t *testing.T) { func TestAddChartSheet(t *testing.T) { categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"} values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for k, v := range categories { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } @@ -254,13 +257,16 @@ func TestDeleteChart(t *testing.T) { // Test delete chart with invalid coordinates. assert.EqualError(t, f.DeleteChart("Sheet1", ""), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) // Test delete chart on no chart worksheet. - assert.NoError(t, NewFile().DeleteChart("Sheet1", "A1")) + xlsxFile, err := NewFile() + assert.NoError(t, err) + assert.NoError(t, xlsxFile.DeleteChart("Sheet1", "A1")) assert.NoError(t, f.Close()) } func TestChartWithLogarithmicBase(t *testing.T) { // Create test XLSX file with data - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) categories := map[string]float64{ "A1": 1, diff --git a/col.go b/col.go index ee1a4074d5..59592bbf2f 100644 --- a/col.go +++ b/col.go @@ -90,7 +90,10 @@ func (cols *Cols) Rows(opts ...Options) ([]string, error) { return rows, err } cols.rawCellValue = parseOptions(opts...).RawCellValue - d := cols.f.sharedStringsReader() + d, err := cols.f.sharedStringsReader() + if err != nil { + return rows, err + } decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML)) for { token, _ := decoder.Token() @@ -204,7 +207,10 @@ func (f *File) Cols(sheet string) (*Cols, error) { worksheet := ws.(*xlsxWorksheet) worksheet.Lock() defer worksheet.Unlock() - output, _ := xml.Marshal(worksheet) + output, err := xml.Marshal(worksheet) + if err != nil { + return nil, err + } f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var colIterator columnXMLIterator @@ -449,8 +455,8 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error { // SetColWidth provides a function to set the width of a single column or // multiple columns. For example: // -// f := excelize.NewFile() -// err := f.SetColWidth("Sheet1", "A", "H", 20) +// f, err := excelize.NewFile() +// err = f.SetColWidth("Sheet1", "A", "H", 20) // func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error { min, err := ColumnNameToNumber(startCol) diff --git a/col_test.go b/col_test.go index df74523a27..be31c849f0 100644 --- a/col_test.go +++ b/col_test.go @@ -41,7 +41,8 @@ func TestCols(t *testing.T) { } assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) cells := []string{"C2", "C3", "C4"} for _, cell := range cells { assert.NoError(t, f.SetCellValue("Sheet1", cell, 1)) @@ -73,7 +74,9 @@ func TestColumnsIterator(t *testing.T) { assert.Equal(t, expectedNumCol, colCount) assert.NoError(t, f.Close()) - f, sheetName, colCount, expectedNumCol = NewFile(), "Sheet1", 0, 4 + xlsxFile, err := NewFile() + assert.NoError(t, err) + f, sheetName, colCount, expectedNumCol = xlsxFile, "Sheet1", 0, 4 cells := []string{"C2", "C3", "C4", "D2", "D3", "D4"} for _, cell := range cells { assert.NoError(t, f.SetCellValue(sheetName, cell, 1)) @@ -107,7 +110,8 @@ func TestGetColsError(t *testing.T) { assert.EqualError(t, err, "sheet SheetN is not exist") assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B`)) f.checked = nil @@ -118,7 +122,8 @@ func TestGetColsError(t *testing.T) { _, err = f.GetCols("Sheet1") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) cols, err := f.Cols("Sheet1") assert.NoError(t, err) cols.totalRows = 2 @@ -135,10 +140,11 @@ func TestGetColsError(t *testing.T) { } func TestColsRows(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.NewSheet("Sheet1") - _, err := f.Cols("Sheet1") + _, err = f.Cols("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1)) @@ -148,13 +154,15 @@ func TestColsRows(t *testing.T) { }, }) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store("xl/worksheets/sheet1.xml", nil) _, err = f.Cols("Sheet1") if !assert.NoError(t, err) { t.FailNow() } - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) cols, err := f.Cols("Sheet1") if !assert.NoError(t, err) { t.FailNow() @@ -229,7 +237,8 @@ func TestColumnVisibility(t *testing.T) { } func TestOutlineLevel(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) level, err := f.GetColOutlineLevel("Sheet1", "D") assert.Equal(t, uint8(0), level) assert.NoError(t, err) @@ -287,7 +296,8 @@ func TestOutlineLevel(t *testing.T) { } func TestSetColStyle(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello")) styleID, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`) assert.NoError(t, err) @@ -311,7 +321,8 @@ func TestSetColStyle(t *testing.T) { } func TestColWidth(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetColWidth("Sheet1", "B", "A", 12)) assert.NoError(t, f.SetColWidth("Sheet1", "A", "B", 12)) width, err := f.GetColWidth("Sheet1", "A") @@ -340,7 +351,8 @@ func TestColWidth(t *testing.T) { } func TestInsertCol(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) fillCells(f, sheet1, 10, 10) @@ -358,7 +370,8 @@ func TestInsertCol(t *testing.T) { } func TestRemoveCol(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) fillCells(f, sheet1, 10, 15) diff --git a/comment.go b/comment.go index 0e3945dfa2..ecf3f30ab2 100644 --- a/comment.go +++ b/comment.go @@ -17,7 +17,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "path/filepath" "strconv" "strings" @@ -36,17 +35,27 @@ func parseFormatCommentsSet(formatSet string) (*formatComment, error) { // GetComments retrieves all comments and returns a map of worksheet name to // the worksheet comments. -func (f *File) GetComments() (comments map[string][]Comment) { +func (f *File) GetComments() (comments map[string][]Comment, firstError error) { comments = map[string][]Comment{} for n, path := range f.sheetMap { - target := f.getSheetComments(filepath.Base(path)) + target, err := f.getSheetComments(filepath.Base(path)) + if err != nil && firstError == nil { + firstError = err + } if target == "" { continue } if !strings.HasPrefix(target, "/") { target = "xl" + strings.TrimPrefix(target, "..") } - if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil { + d, err := f.commentsReader(strings.TrimPrefix(target, "/")) + if err != nil { + if firstError == nil { + firstError = err + } + continue + } + if d != nil { var sheetComments []Comment for _, comment := range d.CommentList.Comment { sheetComment := Comment{} @@ -73,18 +82,22 @@ func (f *File) GetComments() (comments map[string][]Comment) { // getSheetComments provides the method to get the target comment reference by // given worksheet file path. -func (f *File) getSheetComments(sheetFile string) string { +func (f *File) getSheetComments(sheetFile string) (string, error) { rels := "xl/worksheets/_rels/" + sheetFile + ".rels" - if sheetRels := f.relsReader(rels); sheetRels != nil { + sheetRels, err := f.relsReader(rels) + if err != nil { + return "", err + } + if sheetRels != nil { sheetRels.Lock() defer sheetRels.Unlock() for _, v := range sheetRels.Relationships { if v.Type == SourceRelationshipComments { - return v.Target + return v.Target, err } } } - return "" + return "", err } // AddComment provides the method to add comment in a sheet by given worksheet @@ -95,6 +108,10 @@ func (f *File) getSheetComments(sheetFile string) string { // err := f.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`) // func (f *File) AddComment(sheet, cell, format string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + formatSet, err := parseFormatCommentsSet(format) if err != nil { return err @@ -116,8 +133,14 @@ func (f *File) AddComment(sheet, cell, format string) error { } else { // Add first comment for given sheet. sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") - f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "") + rID, err := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") + if err != nil { + return err + } + _, err = f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "") + if err != nil { + return err + } f.addSheetNameSpace(sheet, SourceRelationship) f.addSheetLegacyDrawing(sheet, rID) } @@ -135,7 +158,10 @@ func (f *File) AddComment(sheet, cell, format string) error { if err != nil { return err } - f.addComment(commentsXML, cell, formatSet) + err = f.addComment(commentsXML, cell, formatSet) + if err != nil { + return err + } f.addContentTypePart(commentID, "comments") return err } @@ -212,7 +238,10 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, Column: yAxis, }, } - s, _ := xml.Marshal(sp) + s, err := xml.Marshal(sp) + if err != nil { + return err + } shape := xlsxShape{ ID: "_x0000_s1025", Type: "#_x0000_t202", @@ -221,7 +250,10 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, Strokecolor: "#edeaa1", Val: string(s[13 : len(s)-14]), } - d := f.decodeVMLDrawingReader(drawingVML) + d, err := f.decodeVMLDrawingReader(drawingVML) + if err != nil { + return err + } if d != nil { for _, v := range d.Shape { s := xlsxShape{ @@ -242,7 +274,7 @@ 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, cell string, formatSet *formatComment) { +func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) error { a := formatSet.Author t := formatSet.Text if len(a) > MaxFieldLength { @@ -251,7 +283,10 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) { if len(t) > 32512 { t = t[:32512] } - comments := f.commentsReader(commentsXML) + comments, err := f.commentsReader(commentsXML) + if err != nil { + return err + } authorID := 0 if comments == nil { comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{formatSet.Author}}} @@ -295,6 +330,7 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) { } comments.CommentList.Comment = append(comments.CommentList.Comment, cmt) f.Comments[commentsXML] = comments + return err } // countComments provides a function to get comments files count storage in @@ -320,7 +356,7 @@ 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 { +func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) { var err error if f.DecodeVMLDrawing[path] == nil { @@ -329,27 +365,32 @@ func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) 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, fmt.Errorf("xml decode error: %w", err) } } } - return f.DecodeVMLDrawing[path] + return f.DecodeVMLDrawing[path], nil } // vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml // after serialize structure. -func (f *File) vmlDrawingWriter() { +func (f *File) vmlDrawingWriter() (firstError error) { for path, vml := range f.VMLDrawing { if vml != nil { - v, _ := xml.Marshal(vml) + v, err := xml.Marshal(vml) + if err != nil && firstError == nil { + firstError = err + continue + } f.Pkg.Store(path, v) } } + return } // 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 { +func (f *File) commentsReader(path string) (*xlsxComments, error) { var err error if f.Comments[path] == nil { content, ok := f.Pkg.Load(path) @@ -357,20 +398,25 @@ func (f *File) commentsReader(path string) *xlsxComments { f.Comments[path] = new(xlsxComments) 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, fmt.Errorf("xml decode error: %w", err) } } } - return f.Comments[path] + return f.Comments[path], nil } // commentsWriter provides a function to save xl/comments%d.xml after // serialize structure. -func (f *File) commentsWriter() { +func (f *File) commentsWriter() (firstError error) { for path, c := range f.Comments { if c != nil { - v, _ := xml.Marshal(c) + v, err := xml.Marshal(c) + if err != nil && firstError == nil { + firstError = err + continue + } f.saveFileList(path, v) } } + return } diff --git a/comment_test.go b/comment_test.go index 01f1e42e3e..5fcab98743 100644 --- a/comment_test.go +++ b/comment_test.go @@ -35,33 +35,47 @@ func TestAddComments(t *testing.T) { // Test add comment on with illegal cell coordinates assert.EqualError(t, f.AddComment("Sheet1", "A", `{"author":"Excelize: ","text":"This is a comment."}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) { - assert.Len(t, f.GetComments(), 2) + d, err := f.GetComments() + assert.NoError(t, err) + assert.Len(t, d, 2) } f.Comments["xl/comments2.xml"] = nil f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`Excelize: Excelize: `)) - 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) + xlsxFile, err := NewFile() + assert.NoError(t, err) + d, err := xlsxFile.GetComments() + assert.NoError(t, err) + assert.EqualValues(t, len(d), 0) } func TestDecodeVMLDrawingReader(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) path := "xl/drawings/vmlDrawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - f.decodeVMLDrawingReader(path) + // Test unsupported charset. + _, err = f.decodeVMLDrawingReader(path) + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") } func TestCommentsReader(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) path := "xl/comments1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - f.commentsReader(path) + // Test unsupported charset. + _, err = f.commentsReader(path) + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") } func TestCountComments(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Comments["xl/comments1.xml"] = nil assert.Equal(t, f.countComments(), 1) } diff --git a/datavalidation_test.go b/datavalidation_test.go index d9e060a54e..8effcfa09c 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -23,7 +23,8 @@ import ( func TestDataValidation(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidation.xlsx") - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) dvRange := NewDataValidation(true) dvRange.Sqref = "A1:B2" @@ -73,7 +74,8 @@ func TestDataValidation(t *testing.T) { func TestDataValidationError(t *testing.T) { resultFile := filepath.Join("test", "TestDataValidationError.xlsx") - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellStr("Sheet1", "E1", "E1")) assert.NoError(t, f.SetCellStr("Sheet1", "E2", "E2")) assert.NoError(t, f.SetCellStr("Sheet1", "E3", "E3")) @@ -86,7 +88,7 @@ func TestDataValidationError(t *testing.T) { assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) dvRange = NewDataValidation(true) - err := dvRange.SetDropList(make([]string, 258)) + err = dvRange.SetDropList(make([]string, 258)) if dvRange.Formula1 != "" { t.Errorf("data validation error. Formula1 must be empty!") return @@ -126,12 +128,14 @@ func TestDataValidationError(t *testing.T) { assert.NoError(t, f.SaveAs(resultFile)) // Test add data validation on no exists worksheet. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.EqualError(t, f.AddDataValidation("SheetN", nil), "sheet SheetN is not exist") } func TestDeleteDataValidation(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) dvRange := NewDataValidation(true) diff --git a/docProps.go b/docProps.go index 41ea18e258..888098b933 100644 --- a/docProps.go +++ b/docProps.go @@ -77,9 +77,10 @@ func (f *File) SetAppProps(appProperties *AppProperties) (err error) { app = new(xlsxProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } + err = nil fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"} immutable, mutable = reflect.ValueOf(*appProperties), reflect.ValueOf(app).Elem() for _, field = range fields { @@ -95,6 +96,9 @@ func (f *File) SetAppProps(appProperties *AppProperties) (err error) { } app.Vt = NameSpaceDocumentPropertiesVariantTypes.Value output, err = xml.Marshal(app) + if err != nil { + return + } f.saveFileList(defaultXMLPathDocPropsApp, output) return } @@ -104,9 +108,10 @@ 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 = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } + err = nil ret, err = &AppProperties{ Application: app.Application, ScaleCrop: app.ScaleCrop, @@ -183,9 +188,10 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) { core = new(decodeCoreProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } + err = nil newProps, err = &xlsxCoreProperties{ Dc: NameSpaceDublinCore, Dcterms: NameSpaceDublinCoreTerms, @@ -222,6 +228,9 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) { newProps.Modified.Text = docProperties.Modified } output, err = xml.Marshal(newProps) + if err != nil { + return + } f.saveFileList(defaultXMLPathDocPropsCore, output) return @@ -233,9 +242,10 @@ 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 = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } + err = nil ret, err = &DocProperties{ Category: core.Category, ContentStatus: core.ContentStatus, diff --git a/docProps_test.go b/docProps_test.go index 545059d8df..314fe28fab 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -40,7 +40,8 @@ func TestSetAppProps(t *testing.T) { assert.NoError(t, f.Close()) // Test unsupported charset - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) assert.EqualError(t, f.SetAppProps(&AppProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8") } @@ -52,14 +53,15 @@ func TestGetAppProps(t *testing.T) { } props, err := f.GetAppProps() assert.NoError(t, err) - assert.Equal(t, props.Application, "Microsoft Macintosh Excel") + assert.NotEmpty(t, props.Application, "expected application name in app properties to be not empty") f.Pkg.Store(defaultXMLPathDocPropsApp, nil) _, err = f.GetAppProps() assert.NoError(t, err) assert.NoError(t, f.Close()) // Test unsupported charset - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) _, err = f.GetAppProps() assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") @@ -92,7 +94,8 @@ func TestSetDocProps(t *testing.T) { assert.NoError(t, f.Close()) // Test unsupported charset - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8") } @@ -111,7 +114,8 @@ func TestGetDocProps(t *testing.T) { assert.NoError(t, f.Close()) // Test unsupported charset - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) _, err = f.GetDocProps() assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") diff --git a/drawing.go b/drawing.go index 7de0fb9136..b05b16f173 100644 --- a/drawing.go +++ b/drawing.go @@ -16,7 +16,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "reflect" "strconv" "strings" @@ -24,7 +23,7 @@ import ( // prepareDrawing provides a function to prepare drawing ID and XML by given // drawingID, worksheet name and default drawingXML. -func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string) { +func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXML string) (int, string, error) { sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" if ws.Drawing != nil { // The worksheet already has a picture or chart relationships, use the relationships drawing ../drawings/drawing%d.xml. @@ -34,28 +33,35 @@ func (f *File) prepareDrawing(ws *xlsxWorksheet, drawingID int, sheet, drawingXM } else { // Add first picture for given sheet. sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + rID, err := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + if err != nil { + return 0, "", err + } f.addSheetDrawing(sheet, rID) } - return drawingID, drawingXML + return drawingID, drawingXML, nil } // prepareChartSheetDrawing provides a function to prepare drawing ID and XML // by given drawingID, worksheet name and default drawingXML. -func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) { +func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet string) error { sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" // Only allow one chart in a chartsheet. sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/chartsheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + rID, err := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + if err != nil { + return err + } f.addSheetNameSpace(sheet, SourceRelationship) cs.Drawing = &xlsxDrawing{ RID: "rId" + strconv.Itoa(rID), } + return err } // addChart provides a function to create chart as xl/charts/chart%d.xml by // given format sets. -func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) { +func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) error { count := f.countCharts() xlsxChartSpace := xlsxChartSpace{ XMLNSa: NameSpaceDrawingML.Value, @@ -256,9 +262,13 @@ func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) { addChart(xlsxChartSpace.Chart.PlotArea, plotAreaFunc[comboCharts[idx].Type](comboCharts[idx])) order += len(comboCharts[idx].Series) } - chart, _ := xml.Marshal(xlsxChartSpace) + chart, err := xml.Marshal(xlsxChartSpace) + if err != nil { + return err + } media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml" f.saveFileList(media, chart) + return err } // drawBaseChart provides a function to draw the c:plotArea element for bar, @@ -1148,7 +1158,7 @@ func (f *File) drawPlotAreaTxPr() *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 @@ -1162,8 +1172,9 @@ 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, fmt.Errorf("xml decode error: %w", err) } + err = nil content.R = decodeWsDr.R for _, v := range decodeWsDr.AlternateContent { content.AlternateContent = append(content.AlternateContent, &xlsxAlternateContent{ @@ -1192,7 +1203,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, err } // addDrawingChart provides a function to add chart graphic frame by given @@ -1208,7 +1219,10 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning from := xlsxFrom{} @@ -1242,7 +1256,10 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI }, }, } - graphic, _ := xml.Marshal(graphicFrame) + graphic, err := xml.Marshal(graphicFrame) + if err != nil { + return err + } twoCellAnchor.GraphicFrame = string(graphic) twoCellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: formatSet.FLocksWithSheet, @@ -1256,8 +1273,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, formatSet *formatPicture) { - content, cNvPrID := f.drawingParser(drawingXML) +func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *formatPicture) error { + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } absoluteAnchor := xdrCellAnchor{ EditAs: formatSet.Positioning, Pos: &xlsxPoint2D{}, @@ -1282,7 +1302,10 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *forma }, }, } - graphic, _ := xml.Marshal(graphicFrame) + graphic, err := xml.Marshal(graphicFrame) + if err != nil { + return err + } absoluteAnchor.GraphicFrame = string(graphic) absoluteAnchor.ClientData = &xdrClientData{ FLocksWithSheet: formatSet.FLocksWithSheet, @@ -1290,6 +1313,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, formatSet *forma } 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 @@ -1307,9 +1331,12 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err "Chart": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic == nil }, "Pic": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic != nil }, } - wsDr, _ = f.drawingParser(drawingXML) + wsDr, _, err = f.drawingParser(drawingXML) + if err != nil { + return + } 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 != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) { if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row { wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...) idx-- @@ -1320,10 +1347,11 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err deTwoCellAnchor = new(decodeTwoCellAnchor) if err = f.xmlNewDecoder(strings.NewReader("" + wsDr.TwoCellAnchor[idx].GraphicFrame + "")). Decode(deTwoCellAnchor); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } - if err = nil; deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[drawingType](deTwoCellAnchor) { + err = nil + if deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[drawingType](deTwoCellAnchor) { if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { wsDr.TwoCellAnchor = append(wsDr.TwoCellAnchor[:idx], wsDr.TwoCellAnchor[idx+1:]...) idx-- diff --git a/drawing_test.go b/drawing_test.go index e37b771fdd..48b790525a 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) { @@ -25,11 +27,14 @@ func TestDrawingParser(t *testing.T) { f.Pkg.Store("charset", MacintoshCyrillicCharset) f.Pkg.Store("wsDr", []byte(xml.Header+``)) // Test with one cell anchor - f.drawingParser("wsDr") + _, _, err := f.drawingParser("wsDr") + assert.NoError(t, err) // Test with unsupported charset - f.drawingParser("charset") + _, _, err = f.drawingParser("charset") + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") // Test with alternate content f.Drawings = sync.Map{} f.Pkg.Store("wsDr", []byte(xml.Header+``)) - f.drawingParser("wsDr") + _, _, err = f.drawingParser("wsDr") + assert.NoError(t, err) } diff --git a/errors.go b/errors.go index 6606f1eaf5..10a88b57d6 100644 --- a/errors.go +++ b/errors.go @@ -64,6 +64,11 @@ func newFieldLengthError(name string) error { return fmt.Errorf("field %s must be less or equal than 255 characters", name) } +// newRangeLengthError defined the error message on receiving the invalid range length. +func newRangeLengthError(rangeValue string) error { + return fmt.Errorf("%w: range of %q is smaller than minimum range length of 2", ErrRangeLength, rangeValue) +} + // newCellNameToCoordinatesError defined the error message on converts // alphanumeric cell name to coordinates. func newCellNameToCoordinatesError(cell string, err error) error { @@ -106,6 +111,8 @@ var ( // ErrImgExt defined the error message on receive an unsupported image // extension. ErrImgExt = errors.New("unsupported image extension") + // ErrIncompleteFileSetup defined the error message on invalid File setup. + ErrIncompleteFileSetup = errors.New("file is setup incorrectly") // ErrWorkbookFileFormat defined the error message on receive an // unsupported workbook file format. ErrWorkbookFileFormat = errors.New("unsupported workbook file format") @@ -166,6 +173,8 @@ var ( // ErrCellCharsLength defined the error message for receiving a cell // characters length that exceeds the limit. ErrCellCharsLength = fmt.Errorf("cell value must be 0-%d characters", TotalCellChars) + // ErrRangeLength defined the error message for handling a range of invalid length. + ErrRangeLength = fmt.Errorf("invalid range length") // ErrOptionsUnzipSizeLimit defined the error message for receiving // invalid UnzipSizeLimit and UnzipXMLSizeLimit. ErrOptionsUnzipSizeLimit = errors.New("the value of UnzipSizeLimit should be greater than or equal to UnzipXMLSizeLimit") diff --git a/excelize.go b/excelize.go index 580bc292ed..6025d0ba73 100644 --- a/excelize.go +++ b/excelize.go @@ -174,10 +174,31 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) { for k, v := range file { f.Pkg.Store(k, v) } - f.CalcChain = f.calcChainReader() - f.sheetMap = f.getSheetMap() - f.Styles = f.stylesReader() - f.Theme = f.themeReader() + f.CalcChain, err = f.NewCalcChainReader() + if err != nil { + return nil, err + } + f.ContentTypes, err = f.NewContentTypesReader() + if err != nil { + return nil, err + } + f.WorkBook, err = f.NewWorkbookReader() + if err != nil { + return nil, err + } + f.sheetMap, err = f.getSheetMap() + if err != nil { + return nil, err + } + f.Styles, err = f.NewStylesReader() + if err != nil { + return nil, err + } + f.Theme, err = f.NewThemeReader() + if err != nil { + return nil, err + } + return f, nil } @@ -191,6 +212,13 @@ func parseOptions(opts ...Options) *Options { return opt } +// IsValid returns true if the File's main components are initialized. +// Note: CalcChain can be nil, in case there is no calculation chain. +func (f *File) IsValid() bool { + return f.ContentTypes != nil && f.WorkBook != nil && + f.sheetMap != nil && f.Styles != nil && f.Theme != nil +} + // CharsetTranscoder Set user defined codepage transcoder function for open // XLSX from non UTF-8 encoding. func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f } @@ -247,7 +275,7 @@ 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 = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } err = nil @@ -330,9 +358,9 @@ func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) { // setRels provides a function to set relationships by given relationship ID, // XML path, relationship type, target and target mode. -func (f *File) setRels(rID, relPath, relType, target, targetMode string) int { - rels := f.relsReader(relPath) - if rels == nil || rID == "" { +func (f *File) setRels(rID, relPath, relType, target, targetMode string) (int, error) { + rels, err := f.relsReader(relPath) + if err != nil || rels == nil || rID == "" { return f.addRels(relPath, relType, target, targetMode) } rels.Lock() @@ -347,18 +375,19 @@ func (f *File) setRels(rID, relPath, relType, target, targetMode string) int { break } } - return ID + return ID, err } // addRels provides a function to add relationships by given XML path, // relationship type, target and target mode. -func (f *File) addRels(relPath, relType, target, targetMode string) int { +func (f *File) addRels(relPath, relType, target, targetMode string) (int, error) { uniqPart := map[string]string{ SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", } - rels := f.relsReader(relPath) - if rels == nil { + rels, err := f.relsReader(relPath) + if err != nil || rels == nil { rels = &xlsxRelationships{} + err = nil } rels.Lock() defer rels.Unlock() @@ -371,14 +400,20 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { if relType == rel.Type { if partName, ok := uniqPart[rel.Type]; ok { rels.Relationships[idx].Target = partName - return rID + return rID, err } } } rID++ var ID bytes.Buffer - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) + _, err = ID.WriteString("rId") + if err != nil { + return rID, err + } + _, err = ID.WriteString(strconv.Itoa(rID)) + if err != nil { + return rID, err + } rels.Relationships = append(rels.Relationships, xlsxRelationship{ ID: ID.String(), Type: relType, @@ -386,7 +421,7 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { TargetMode: targetMode, }) f.Relationships.Store(relPath, rels) - return rID + return rID, err } // UpdateLinkedValue fix linked values within a spreadsheet are not updating in @@ -416,6 +451,9 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { // func (f *File) UpdateLinkedValue() error { wb := f.workbookReader() + if wb == nil { + return ErrIncompleteFileSetup + } // recalculate formulas wb.CalcPr = nil for _, name := range f.GetSheetList() { @@ -460,7 +498,14 @@ func (f *File) AddVBAProject(bin string) error { if path.Ext(bin) != ".bin" { return ErrAddVBAProject } - wb := f.relsReader(f.getWorkbookRelsPath()) + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return err + } + wb, err := f.relsReader(wrp) + if err != nil { + return err + } wb.Lock() defer wb.Unlock() var rID int @@ -483,7 +528,10 @@ func (f *File) AddVBAProject(bin string) error { Type: SourceRelationshipVBAProject, }) } - file, _ := ioutil.ReadFile(filepath.Clean(bin)) + file, err := ioutil.ReadFile(filepath.Clean(bin)) + if err != nil { + return err + } f.Pkg.Store("xl/vbaProject.bin", file) return err } @@ -493,6 +541,9 @@ func (f *File) AddVBAProject(bin string) error { func (f *File) setContentTypePartProjectExtensions(contentType string) { var ok bool content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for _, v := range content.Defaults { diff --git a/excelize_test.go b/excelize_test.go index f1b9903cbb..3808a401fd 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -202,7 +202,8 @@ func TestSaveAsWrongPath(t *testing.T) { } func TestCharsetTranscoder(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.CharsetTranscoder(*new(charsetTranscoderFn)) } @@ -275,7 +276,7 @@ func TestBrokenFile(t *testing.T) { t.Run("SaveAsEmptyStruct", func(t *testing.T) { // Test write file with broken file struct with given path. - assert.NoError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx"))) + assert.EqualError(t, f.SaveAs(filepath.Join("test", "BadWorkbook.SaveAsEmptyStruct.xlsx")), ErrIncompleteFileSetup.Error()) }) t.Run("OpenBadWorkbook", func(t *testing.T) { @@ -298,7 +299,8 @@ func TestBrokenFile(t *testing.T) { func TestNewFile(t *testing.T) { // Test create a spreadsheet file. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.NewSheet("Sheet1") f.NewSheet("XLSXSheet2") f.NewSheet("XLSXSheet3") @@ -307,7 +309,7 @@ func TestNewFile(t *testing.T) { f.SetActiveSheet(0) // Test add picture to sheet with scaling and positioning. - err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), + err = f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), `{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) if !assert.NoError(t, err) { t.FailNow() @@ -329,9 +331,20 @@ func TestNewFile(t *testing.T) { assert.NoError(t, f.Save()) } +func TestIsValid(t *testing.T) { + f, err := NewFile() + assert.NoError(t, err) + assert.EqualValues(t, true, f.IsValid(), "expected that new file is valid") + + f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) + assert.NoError(t, err) + assert.EqualValues(t, true, f.IsValid(), "expected that new file is valid") +} + func TestAddDrawingVML(t *testing.T) { // Test addDrawingVML with illegal cell coordinates. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.addDrawingVML(0, "", "*", 0, 0), newCellNameToCoordinatesError("*", newInvalidCellNameError("*")).Error()) } @@ -358,7 +371,8 @@ func TestSetCellHyperLink(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx"))) assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") @@ -366,7 +380,8 @@ func TestSetCellHyperLink(t *testing.T) { ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)} assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/xuri/excelize", "External"), ErrTotalSheetHyperlinks.Error()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") @@ -376,7 +391,8 @@ func TestSetCellHyperLink(t *testing.T) { assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test update cell hyperlink - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com", "External")) assert.NoError(t, f.SetCellHyperLink("Sheet1", "A1", "https://github.com/xuri/excelize", "External")) link, target, err := f.GetCellHyperLink("Sheet1", "A1") @@ -405,7 +421,8 @@ func TestGetCellHyperLink(t *testing.T) { t.Log(link, target) assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") @@ -476,7 +493,8 @@ func TestWriteArrayFormula(t *testing.T) { return c } - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sample := []string{"Sample 1", "Sample 2", "Sample 3"} values := []int{1855, 1709, 1462, 1115, 1524, 625, 773, 126, 1027, 1696, 1078, 1917, 1109, 1753, 1884, 659, 994, 1911, 1925, 899, 196, 244, 1488, 1056, 1986, 66, 784, 725, 767, 1722, 1541, 1026, 1455, 264, 1538, 877, 1581, 1098, 383, 762, 237, 493, 29, 1923, 474, 430, 585, 688, 308, 200, 1259, 622, 798, 1048, 996, 601, 582, 332, 377, 805, 250, 1860, 1360, 840, 911, 1346, 1651, 1651, 665, 584, 1057, 1145, 925, 1752, 202, 149, 1917, 1398, 1894, 818, 714, 624, 1085, 1566, 635, 78, 313, 1686, 1820, 494, 614, 1913, 271, 1016, 338, 1301, 489, 1733, 1483, 1141} @@ -800,7 +818,8 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { } func TestSetCellStyleCustomNumberFormat(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) @@ -951,7 +970,8 @@ func TestCopySheet(t *testing.T) { t.FailNow() } - idx := f.NewSheet("CopySheet") + idx, err := f.NewSheet("CopySheet") + assert.NoError(t, err) assert.NoError(t, f.CopySheet(0, idx)) assert.NoError(t, f.SetCellValue("CopySheet", "F1", "Hello")) @@ -977,42 +997,50 @@ func TestCopySheetError(t *testing.T) { } func TestGetSheetComments(t *testing.T) { - f := NewFile() - assert.Equal(t, "", f.getSheetComments("sheet0")) + f, err := NewFile() + assert.NoError(t, err) + comments, err := f.getSheetComments("sheet0") + assert.NoError(t, err) + assert.Equal(t, "", comments) } func TestSetSheetVisible(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.WorkBook.Sheets.Sheet[0].Name = "SheetN" assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN is not exist") } func TestGetActiveSheetIndex(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.WorkBook.BookViews = nil assert.Equal(t, 0, f.GetActiveSheetIndex()) } func TestRelsWriter(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Relationships.Store("xl/worksheets/sheet/rels/sheet1.xml.rel", &xlsxRelationships{}) - f.relsWriter() + err = f.relsWriter() + assert.NoError(t, err) } func TestGetSheetView(t *testing.T) { - f := NewFile() - _, err := f.getSheetView("SheetN", 0) + f, err := NewFile() + assert.NoError(t, err) + _, err = f.getSheetView("SheetN", 0) assert.EqualError(t, err, "sheet SheetN is not exist") } func TestConditionalFormat(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) fillCells(f, sheet1, 10, 15) var format1, format2, format3, format4 int - var err error // Rose format for bad conditional. format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) if !assert.NoError(t, err) { @@ -1093,13 +1121,14 @@ func TestConditionalFormat(t *testing.T) { } func TestConditionalFormatError(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) fillCells(f, sheet1, 10, 15) // Set conditional format with illegal JSON string should return error. - _, err := f.NewConditionalStyle("") + _, err = f.NewConditionalStyle("") if !assert.EqualError(t, err, "unexpected end of JSON input") { t.FailNow() } @@ -1173,7 +1202,8 @@ func TestHSL(t *testing.T) { } func TestProtectSheet(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheetName := f.GetSheetName(0) assert.NoError(t, f.ProtectSheet(sheetName, nil)) // Test protect worksheet with XOR hash algorithm @@ -1229,7 +1259,8 @@ func TestUnprotectSheet(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectSheet.xlsx"))) assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) sheetName := f.GetSheetName(0) assert.NoError(t, f.ProtectSheet(sheetName, &FormatSheetProtection{Password: "password"})) // Test remove sheet protection with an incorrect password @@ -1248,7 +1279,8 @@ func TestUnprotectSheet(t *testing.T) { } func TestSetDefaultTimeStyle(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test set default time style on not exists worksheet. assert.EqualError(t, f.setDefaultTimeStyle("SheetN", "", 0), "sheet SheetN is not exist") @@ -1257,7 +1289,8 @@ func TestSetDefaultTimeStyle(t *testing.T) { } func TestAddVBAProject(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetSheetPrOptions("Sheet1", CodeName("Sheet1"))) assert.EqualError(t, f.AddVBAProject("macros.bin"), "stat macros.bin: no such file or directory") assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "Book1.xlsx")), ErrAddVBAProject.Error()) @@ -1269,31 +1302,33 @@ func TestAddVBAProject(t *testing.T) { func TestContentTypesReader(t *testing.T) { // Test unsupported charset. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - f.contentTypesReader() } func TestWorkbookReader(t *testing.T) { // Test unsupported charset. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - f.workbookReader() } func TestWorkSheetReader(t *testing.T) { // Test unsupported charset. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) - _, err := f.workSheetReader("Sheet1") + _, 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") // Test on no checked worksheet. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) f.checked = nil @@ -1303,18 +1338,24 @@ func TestWorkSheetReader(t *testing.T) { func TestRelsReader(t *testing.T) { // Test unsupported charset. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) rels := "xl/_rels/workbook.xml.rels" f.Relationships.Store(rels, nil) f.Pkg.Store(rels, MacintoshCyrillicCharset) - f.relsReader(rels) + _, err = f.relsReader(rels) + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") } func TestDeleteSheetFromWorkbookRels(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) rels := "xl/_rels/workbook.xml.rels" f.Relationships.Store(rels, nil) - assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "") + + target, err := f.deleteSheetFromWorkbookRels("rID") + assert.NoError(t, err) + assert.Equal(t, target, "") } func TestAttrValToInt(t *testing.T) { @@ -1357,7 +1398,10 @@ func prepareTestBook1() (*File, error) { } func prepareTestBook3() (*File, error) { - f := NewFile() + f, err := NewFile() + if err != nil { + return f, err + } f.NewSheet("Sheet1") f.NewSheet("XLSXSheet2") f.NewSheet("XLSXSheet3") @@ -1369,7 +1413,7 @@ func prepareTestBook3() (*File, error) { } f.SetActiveSheet(0) - err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), + err = f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), `{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) if err != nil { return nil, err @@ -1384,7 +1428,10 @@ func prepareTestBook3() (*File, error) { } func prepareTestBook4() (*File, error) { - f := NewFile() + f, err := NewFile() + if err != nil { + return f, err + } if err := f.SetColWidth("Sheet1", "B", "A", 12); err != nil { return f, err } diff --git a/file.go b/file.go index 5931bdb4fb..3241cc10ab 100644 --- a/file.go +++ b/file.go @@ -26,8 +26,8 @@ import ( // // f := NewFile() // -func NewFile() *File { - f := newFile() +func NewFile() (f *File, err error) { + f = newFile() f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels)) f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp)) f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore)) @@ -38,21 +38,56 @@ 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() + + handleFailure := func() { + f.Close() + f = nil + } + + f.CalcChain, err = f.NewCalcChainReader() + if err != nil { + handleFailure() + return + } f.Comments = make(map[string]*xlsxComments) - f.ContentTypes = f.contentTypesReader() + f.ContentTypes, err = f.NewContentTypesReader() + if err != nil { + handleFailure() + return + } f.Drawings = sync.Map{} - f.Styles = f.stylesReader() + f.Styles, err = f.NewStylesReader() + if err != nil { + handleFailure() + return + } f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing) - f.WorkBook = f.workbookReader() + f.WorkBook, err = f.NewWorkbookReader() + if err != nil { + handleFailure() + return + } f.Relationships = sync.Map{} - f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels")) + rels, err := f.relsReader("xl/_rels/workbook.xml.rels") + if err != nil { + handleFailure() + return + } + f.Relationships.Store("xl/_rels/workbook.xml.rels", rels) f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" - ws, _ := f.workSheetReader("Sheet1") + ws, err := f.workSheetReader("Sheet1") + if err != nil { + handleFailure() + return + } f.Sheet.Store("xl/worksheets/sheet1.xml", ws) - f.Theme = f.themeReader() - return f + f.Theme, err = f.NewThemeReader() + if err != nil { + handleFailure() + return + } + return } // Save provides a function to override the spreadsheet with origin path. @@ -72,6 +107,9 @@ func (f *File) SaveAs(name string, opt ...Options) error { if len(name) > MaxFilePathLength { return ErrMaxFilePathLength } + if !f.IsValid() { + return ErrIncompleteFileSetup + } f.Path = name contentType, ok := map[string]string{ ".xlam": ContentTypeAddinMacro, @@ -170,37 +208,70 @@ func (f *File) writeDirectToWriter(w io.Writer) error { } // writeToZip provides a function to write to zip.Writer -func (f *File) writeToZip(zw *zip.Writer) error { - f.calcChainWriter() - f.commentsWriter() - f.contentTypesWriter() - f.drawingsWriter() - f.vmlDrawingWriter() - f.workBookWriter() - f.workSheetWriter() - f.relsWriter() - f.sharedStringsLoader() - f.sharedStringsWriter() - f.styleSheetWriter() +func (f *File) writeToZip(zw *zip.Writer) (err error) { + err = f.calcChainWriter() + if err != nil { + return + } + err = f.commentsWriter() + if err != nil { + return + } + err = f.contentTypesWriter() + if err != nil { + return + } + err = f.drawingsWriter() + if err != nil { + return + } + err = f.vmlDrawingWriter() + if err != nil { + return + } + err = f.workBookWriter() + if err != nil { + return + } + err = f.workSheetWriter() + if err != nil { + return + } + err = f.relsWriter() + if err != nil { + return + } + err = f.sharedStringsLoader() + if err != nil { + return + } + err = f.sharedStringsWriter() + if err != nil { + return + } + err = f.styleSheetWriter() + if err != nil { + return + } for path, stream := range f.streams { - fi, err := zw.Create(path) + var fi io.Writer + fi, err = zw.Create(path) if err != nil { - return err + return } var from io.Reader from, err = stream.rawData.Reader() if err != nil { _ = stream.rawData.Close() - return err + return } _, err = io.Copy(fi, from) if err != nil { - return err + return } _ = stream.rawData.Close() } - var err error f.Pkg.Range(func(path, content interface{}) bool { if err != nil { return false @@ -214,7 +285,7 @@ func (f *File) writeToZip(zw *zip.Writer) error { return false } _, err = fi.Write(content.([]byte)) - return true + return err == nil }) f.tempFiles.Range(func(path, content interface{}) bool { if _, ok := f.Pkg.Load(path); ok { @@ -226,7 +297,7 @@ func (f *File) writeToZip(zw *zip.Writer) error { return false } _, err = fi.Write(f.readBytes(path.(string))) - return true + return err == nil }) - return err + return } diff --git a/file_test.go b/file_test.go index 8e65c5d46d..e6778fedb1 100644 --- a/file_test.go +++ b/file_test.go @@ -15,7 +15,8 @@ import ( func BenchmarkWrite(b *testing.B) { const s = "This is test data" for i := 0; i < b.N; i++ { - f := NewFile() + f, err := NewFile() + assert.NoError(b, err) for row := 1; row <= 10000; row++ { for col := 1; col <= 20; col++ { val, err := CoordinatesToCellName(col, row) @@ -28,7 +29,7 @@ func BenchmarkWrite(b *testing.B) { } } // Save spreadsheet by the given path. - err := f.SaveAs("./test.xlsx") + err = f.SaveAs("./test.xlsx") if err != nil { b.Error(err) } @@ -74,7 +75,8 @@ func TestWriteTo(t *testing.T) { } func TestClose(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.tempFiles.Store("/d/", "/d/") require.Error(t, f.Close()) } diff --git a/lib.go b/lib.go index f285a40dbc..5f13358ced 100644 --- a/lib.go +++ b/lib.go @@ -286,7 +286,7 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) { func areaRefToCoordinates(ref string) ([]int, error) { rng := strings.Split(strings.ReplaceAll(ref, "$", ""), ":") if len(rng) < 2 { - return nil, ErrParameterInvalid + return nil, newRangeLengthError(ref) } return areaRangeToCoordinates(rng[0], rng[1]) } diff --git a/lib_test.go b/lib_test.go index 027e5dd6b6..c050039a4a 100644 --- a/lib_test.go +++ b/lib_test.go @@ -217,8 +217,9 @@ func TestCoordinatesToCellName_Error(t *testing.T) { } func TestCoordinatesToAreaRef(t *testing.T) { - f := NewFile() - _, err := f.coordinatesToAreaRef([]int{}) + f, err := NewFile() + assert.NoError(t, err) + _, err = f.coordinatesToAreaRef([]int{}) assert.EqualError(t, err, ErrCoordinates.Error()) _, err = f.coordinatesToAreaRef([]int{1, -1, 1, 1}) assert.EqualError(t, err, "invalid cell coordinates [1, -1]") @@ -277,7 +278,8 @@ func TestGetRootElement(t *testing.T) { } func TestSetIgnorableNameSpace(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.xmlAttr["xml_path"] = []xml.Attr{{}} f.setIgnorableNameSpace("xml_path", 0, xml.Attr{Name: xml.Name{Local: "c14"}}) assert.EqualValues(t, "c14", f.xmlAttr["xml_path"][0].Value) @@ -343,7 +345,8 @@ func TestUnzipToTemp(t *testing.T) { os.Setenv("TMPDIR", "test") defer os.Unsetenv("TMPDIR") assert.NoError(t, os.Chmod(os.TempDir(), 0o444)) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) data := []byte("PK\x03\x040000000PK\x01\x0200000" + "0000000000000000000\x00" + "\x00\x00\x00\x00\x00000000000000PK\x01" + diff --git a/merge.go b/merge.go index 0f57826e37..b87d7d2e95 100644 --- a/merge.go +++ b/merge.go @@ -11,7 +11,10 @@ package excelize -import "strings" +import ( + "fmt" + "strings" +) // Rect gets merged cell rectangle coordinates sequence. func (mc *xlsxMergeCell) Rect() ([]int, error) { @@ -207,7 +210,7 @@ func flatMergedCells(ws *xlsxWorksheet, matrix [][]*xlsxMergeCell) error { func (f *File) mergeOverlapCells(ws *xlsxWorksheet) error { rows, cols, err := overlapRange(ws) if err != nil { - return err + return fmt.Errorf("merge overlapping cells: %w", err) } if rows == 0 || cols == 0 { return nil diff --git a/merge_test.go b/merge_test.go index 597d4b58a8..c96fece746 100644 --- a/merge_test.go +++ b/merge_test.go @@ -70,7 +70,8 @@ func TestMergeCell(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCell.xlsx"))) assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) @@ -79,12 +80,13 @@ func TestMergeCell(t *testing.T) { } func TestMergeCellOverlap(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.MergeCell("Sheet1", "A1", "C2")) assert.NoError(t, f.MergeCell("Sheet1", "B2", "D3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestMergeCellOverlap.xlsx"))) - f, err := OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx")) + f, err = OpenFile(filepath.Join("test", "TestMergeCellOverlap.xlsx")) if !assert.NoError(t, err) { t.FailNow() } @@ -167,7 +169,8 @@ func TestUnmergeCell(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnmergeCell.xlsx"))) assert.NoError(t, f.Close()) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) // Test unmerged area on not exists worksheet. assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN is not exist") @@ -185,15 +188,15 @@ func TestUnmergeCell(t *testing.T) { ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}} - assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), ErrParameterInvalid.Error()) + assert.ErrorIs(t, f.UnmergeCell("Sheet1", "A2", "B3"), ErrRangeLength) ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} - assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), "merge overlapping cells: "+newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestFlatMergedCells(t *testing.T) { ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}} - assert.EqualError(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), ErrParameterInvalid.Error()) + assert.ErrorIs(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), ErrRangeLength) } diff --git a/picture.go b/picture.go index 44f1f3b3e8..a930359ec8 100644 --- a/picture.go +++ b/picture.go @@ -53,7 +53,11 @@ func parseFormatPictureSet(formatSet string) (*formatPicture, error) { // ) // // func main() { -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // // Insert a picture. // if err := f.AddPicture("Sheet1", "A2", "image.jpg", ""); err != nil { // fmt.Println(err) @@ -137,7 +141,11 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error { // ) // // func main() { -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // // file, err := ioutil.ReadFile("image.jpg") // if err != nil { @@ -152,6 +160,10 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error { // } // func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, file []byte) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + var drawingHyperlinkRID int var hyperlinkType string ext, ok := supportedImageTypes[extension] @@ -175,16 +187,25 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. drawingID := f.countDrawings() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" - drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + drawingID, drawingXML, err = f.prepareDrawing(ws, drawingID, sheet, drawingXML) + if err != nil { + return err + } drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" mediaStr := ".." + strings.TrimPrefix(f.addMedia(file, ext), "xl") - drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType) + drawingRID, err := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType) + if err != nil { + return err + } // Add picture with hyperlink. if formatSet.Hyperlink != "" && formatSet.HyperlinkType != "" { if formatSet.HyperlinkType == "External" { hyperlinkType = formatSet.HyperlinkType } - drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) + drawingHyperlinkRID, err = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) + if err != nil { + return err + } } ws.Unlock() err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet) @@ -205,7 +226,7 @@ func (f *File) deleteSheetRelationships(sheet, rID string) { name = strings.ToLower(sheet) + ".xml" } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels := f.relsReader(rels) + sheetRels, _ := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} } @@ -284,7 +305,10 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he col-- row-- colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning from := xlsxFrom{} @@ -368,6 +392,9 @@ func (f *File) addMedia(file []byte, ext string) string { func (f *File) setContentTypePartImageExtensions() { imageTypes := map[string]string{"jpeg": "image/", "png": "image/", "gif": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-"} content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for _, file := range content.Defaults { @@ -386,6 +413,9 @@ func (f *File) setContentTypePartImageExtensions() { func (f *File) setContentTypePartVMLExtensions() { vml := false content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for _, v := range content.Defaults { @@ -433,6 +463,9 @@ func (f *File) addContentTypePart(index int, contentType string) { s() } content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for _, v := range content.Overrides { @@ -455,7 +488,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { name = strings.ToLower(sheet) + ".xml" } rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels := f.relsReader(rels) + sheetRels, _ := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} } @@ -550,14 +583,17 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) deTwoCellAnchor *decodeTwoCellAnchor ) - wsDr, _ = f.drawingParser(drawingXML) - if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 { + wsDr, _, err = f.drawingParser(drawingXML) + if err != nil { + return + } + if ret, buf, err = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 || err != nil { return } deWsDr = new(decodeWsDr) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))). Decode(deWsDr); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } err = nil @@ -565,12 +601,16 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) deTwoCellAnchor = new(decodeTwoCellAnchor) if err = f.xmlNewDecoder(strings.NewReader("" + anchor.Content + "")). Decode(deTwoCellAnchor); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) + err = fmt.Errorf("xml decode error: %w", err) return } - if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil { + err = nil + if deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil { if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { - drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) + drawRel, err = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) + if err != nil { + return + } if _, ok = supportedImageTypes[filepath.Ext(drawRel.Target)]; ok { ret = filepath.Base(drawRel.Target) if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil { @@ -587,7 +627,7 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) // getPictureFromWsDr provides a function to get picture base name and raw // content in worksheet drawing by given coordinates and drawing // relationships. -func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (ret string, buf []byte) { +func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (ret string, buf []byte, err error) { var ( ok bool anchor *xdrCellAnchor @@ -598,8 +638,11 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD for _, anchor = range wsDr.TwoCellAnchor { if anchor.From != nil && anchor.Pic != nil { if anchor.From.Col == col && anchor.From.Row == row { - if drawRel = f.getDrawingRelationships(drawingRelationships, - anchor.Pic.BlipFill.Blip.Embed); drawRel != nil { + drawRel, err = f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed) + if err != nil { + return + } + if drawRel != nil { if _, ok = supportedImageTypes[filepath.Ext(drawRel.Target)]; ok { ret = filepath.Base(drawRel.Target) if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil { @@ -617,29 +660,40 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD // getDrawingRelationships provides a function to get drawing relationships // from xl/drawings/_rels/drawing%s.xml.rels by given file name and // relationship ID. -func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship { - if drawingRels := f.relsReader(rels); drawingRels != nil { +func (f *File) getDrawingRelationships(rels, rID string) (*xlsxRelationship, error) { + drawingRels, err := f.relsReader(rels) + if err != nil { + return nil, err + } + if drawingRels != nil { drawingRels.Lock() defer drawingRels.Unlock() for _, v := range drawingRels.Relationships { if v.ID == rID { - return &v + return &v, err } } } - return nil + return nil, err } // drawingsWriter provides a function to save xl/drawings/drawing%d.xml after // serialize structure. -func (f *File) drawingsWriter() { +func (f *File) drawingsWriter() (firstError error) { f.Drawings.Range(func(path, d interface{}) bool { if d != nil { - v, _ := xml.Marshal(d.(*xlsxWsDr)) - f.saveFileList(path.(string), v) + v, err := xml.Marshal(d.(*xlsxWsDr)) + if err != nil { + if firstError == nil { + firstError = err + } + } else { + f.saveFileList(path.(string), v) + } } return true }) + return } // drawingResize calculate the height and width after resizing. diff --git a/picture_test.go b/picture_test.go index 60c6ac17cc..b06c33f487 100644 --- a/picture_test.go +++ b/picture_test.go @@ -19,7 +19,8 @@ import ( ) func BenchmarkAddPictureFromBytes(b *testing.B) { - f := NewFile() + f, err := NewFile() + assert.NoError(b, err) imgFile, err := ioutil.ReadFile(filepath.Join("test", "images", "excel.png")) if err != nil { b.Error("unable to load image for benchmark") @@ -134,8 +135,10 @@ func TestGetPicture(t *testing.T) { assert.Empty(t, file) assert.Empty(t, raw) - f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8") - f.getDrawingRelationships("", "") + _, err = f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8") + assert.NoError(t, err) + _, err = f.getDrawingRelationships("", "") + assert.NoError(t, err) f.getSheetRelationshipsTargetByID("", "") f.deleteSheetRelationships("", "") @@ -160,7 +163,8 @@ func TestGetPicture(t *testing.T) { assert.NoError(t, f.Close()) // Test get picture from none drawing worksheet. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) file, raw, err = f.GetPicture("Sheet1", "F22") assert.NoError(t, err) assert.Empty(t, file) @@ -174,12 +178,14 @@ func TestGetPicture(t *testing.T) { func TestAddDrawingPicture(t *testing.T) { // testing addDrawingPicture with illegal cell coordinates. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, 0, 0, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestAddPictureFromBytes(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) imgFile, err := ioutil.ReadFile("logo.png") assert.NoError(t, err, "Unable to load logo for test") assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile)) @@ -208,13 +214,16 @@ func TestDeletePicture(t *testing.T) { assert.EqualError(t, f.DeletePicture("Sheet1", ""), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error()) assert.NoError(t, f.Close()) // Test delete picture on no chart worksheet. - assert.NoError(t, NewFile().DeletePicture("Sheet1", "A1")) + xlsxFile, err := NewFile() + assert.NoError(t, err) + assert.NoError(t, xlsxFile.DeletePicture("Sheet1", "A1")) } func TestDrawingResize(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test calculate drawing resize on not exists worksheet. - _, _, _, _, err := f.drawingResize("SheetN", "A1", 1, 1, nil) + _, _, _, _, err = f.drawingResize("SheetN", "A1", 1, 1, nil) assert.EqualError(t, err, "sheet SheetN is not exist") // Test calculate drawing resize with invalid coordinates. _, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil) @@ -222,5 +231,5 @@ func TestDrawingResize(t *testing.T) { ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} - assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), "merge overlapping cells: "+newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } diff --git a/pivotTable.go b/pivotTable.go index 10c48cef3a..8930d27bef 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -95,7 +95,11 @@ type PivotTableField struct { // ) // // func main() { -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // // Create some data in a sheet // month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} // year := []int{2017, 2018, 2019} @@ -131,6 +135,10 @@ type PivotTableField struct { // } // func (f *File) AddPivotTable(opt *PivotTableOption) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + // parameter validation _, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opt) if err != nil { @@ -148,23 +156,36 @@ func (f *File) AddPivotTable(opt *PivotTableOption) error { return err } + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return err + } // workbook pivot cache - workBookPivotCacheRID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + workBookPivotCacheRID, err := f.addRels(wrp, SourceRelationshipPivotCache, fmt.Sprintf("/xl/pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + if err != nil { + return err + } cacheID := f.addWorkbookPivotCache(workBookPivotCacheRID) pivotCacheRels := "xl/pivotTables/_rels/pivotTable" + strconv.Itoa(pivotTableID) + ".xml.rels" // rId not used - _ = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + _, err = f.addRels(pivotCacheRels, SourceRelationshipPivotCache, fmt.Sprintf("../pivotCache/pivotCacheDefinition%d.xml", pivotCacheID), "") + if err != nil { + return err + } err = f.addPivotTable(cacheID, pivotTableID, pivotTableXML, opt) if err != nil { return err } pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels" - f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") + _, err = f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") + if err != nil { + return err + } f.addContentTypePart(pivotTableID, "pivotTable") f.addContentTypePart(pivotCacheID, "pivotCache") - return nil + return err } // parseFormatPivotTableSet provides a function to validate pivot table @@ -307,6 +328,9 @@ func (f *File) addPivotCache(pivotCacheXML string, opt *PivotTableOption) error } pc.CacheFields.Count = len(pc.CacheFields.CacheField) pivotCache, err := xml.Marshal(pc) + if err != nil { + return err + } f.saveFileList(pivotCacheXML, pivotCache) return err } @@ -386,6 +410,9 @@ func (f *File) addPivotTable(cacheID, pivotTableID int, pivotTableXML string, op _ = f.addPivotDataFields(&pt, opt) pivotTable, err := xml.Marshal(pt) + if err != nil { + return err + } f.saveFileList(pivotTableXML, pivotTable) return err } @@ -699,6 +726,9 @@ func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField) // addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml. func (f *File) addWorkbookPivotCache(RID int) int { wb := f.workbookReader() + if wb == nil { + return 0 + } if wb.PivotCaches == nil { wb.PivotCaches = &xlsxPivotCaches{} } diff --git a/pivotTable_test.go b/pivotTable_test.go index adba2eb197..70d1df63ee 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -11,7 +11,8 @@ import ( ) func TestAddPivotTable(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Create some data in a sheet month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} year := []int{2017, 2018, 2019} @@ -226,11 +227,11 @@ func TestAddPivotTable(t *testing.T) { })) // Test adjust range with invalid range - _, _, err := f.adjustRange("") + _, _, err = f.adjustRange("") assert.EqualError(t, err, ErrParameterRequired.Error()) // Test adjust range with incorrect range _, _, err = f.adjustRange("sheet1!") - assert.EqualError(t, err, "parameter is invalid") + assert.ErrorIs(t, err, ErrRangeLength) // Test get pivot fields order with empty data range _, err = f.getPivotFieldsOrder(&PivotTableOption{}) assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) @@ -262,7 +263,8 @@ func TestAddPivotTable(t *testing.T) { } func TestAddPivotRowFields(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test invalid data range assert.EqualError(t, f.addPivotRowFields(&xlsxPivotTableDefinition{}, &PivotTableOption{ DataRange: "Sheet1!$A$1:$A$1", @@ -270,7 +272,8 @@ func TestAddPivotRowFields(t *testing.T) { } func TestAddPivotPageFields(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test invalid data range assert.EqualError(t, f.addPivotPageFields(&xlsxPivotTableDefinition{}, &PivotTableOption{ DataRange: "Sheet1!$A$1:$A$1", @@ -278,7 +281,8 @@ func TestAddPivotPageFields(t *testing.T) { } func TestAddPivotDataFields(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test invalid data range assert.EqualError(t, f.addPivotDataFields(&xlsxPivotTableDefinition{}, &PivotTableOption{ DataRange: "Sheet1!$A$1:$A$1", @@ -286,7 +290,8 @@ func TestAddPivotDataFields(t *testing.T) { } func TestAddPivotColFields(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test invalid data range assert.EqualError(t, f.addPivotColFields(&xlsxPivotTableDefinition{}, &PivotTableOption{ DataRange: "Sheet1!$A$1:$A$1", @@ -295,13 +300,15 @@ func TestAddPivotColFields(t *testing.T) { } func TestGetPivotFieldsOrder(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test get pivot fields order with not exist worksheet - _, err := f.getPivotFieldsOrder(&PivotTableOption{DataRange: "SheetN!$A$1:$E$31"}) + _, err = f.getPivotFieldsOrder(&PivotTableOption{DataRange: "SheetN!$A$1:$E$31"}) assert.EqualError(t, err, "sheet SheetN is not exist") } func TestGetPivotTableFieldName(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.getPivotTableFieldName("-", []PivotTableField{}) } diff --git a/rows.go b/rows.go index f83d425382..5cf99d85ac 100644 --- a/rows.go +++ b/rows.go @@ -17,7 +17,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "math" "math/big" "os" @@ -131,7 +130,14 @@ 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 + + var err error + rows.sst, err = rows.f.sharedStringsReader() + if err != nil { + return nil, err + } + for { if rows.token != nil { token = rows.token @@ -242,7 +248,10 @@ func (f *File) Rows(sheet string) (*Rows, error) { worksheet.Lock() defer worksheet.Unlock() // flush data - output, _ := xml.Marshal(worksheet) + output, err := xml.Marshal(worksheet) + if err != nil { + return nil, err + } f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var err error @@ -386,18 +395,26 @@ 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() - relPath := f.getWorkbookRelsPath() + relPath, err := f.getWorkbookRelsPath() + if err != nil { + return nil, err + } if f.SharedStrings == nil { + if !f.IsValid() { + return nil, ErrIncompleteFileSetup + } + var sharedStrings 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 nil, fmt.Errorf("xml decode error: %s", err) } + err = nil if sharedStrings.Count == 0 { sharedStrings.Count = len(sharedStrings.SI) } @@ -411,23 +428,29 @@ func (f *File) sharedStringsReader() *xlsxSST { } } f.addContentTypePart(0, "sharedStrings") - rels := f.relsReader(relPath) + rels, err := f.relsReader(relPath) + if err != nil { + return f.SharedStrings, err + } 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 } // getValueFrom return a value from a column/row cell, this function is // intended to be used with for range on rows an argument with the spreadsheet // opened file. func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { + if !f.IsValid() { + return "", ErrIncompleteFileSetup + } f.Lock() defer f.Unlock() switch c.T { diff --git a/rows_test.go b/rows_test.go index 014b2d853f..1f6e2a0c70 100644 --- a/rows_test.go +++ b/rows_test.go @@ -82,7 +82,9 @@ func TestRowsIterator(t *testing.T) { assert.NoError(t, f.Close()) // Valued cell sparse distribution test - f, sheetName, rowCount, expectedNumRow = NewFile(), "Sheet1", 0, 3 + f, err = NewFile() + assert.NoError(t, err) + sheetName, rowCount, expectedNumRow = "Sheet1", 0, 3 cells := []string{"C1", "E1", "A3", "B3", "C3", "D3", "E3"} for _, cell := range cells { assert.NoError(t, f.SetCellValue(sheetName, cell, 1)) @@ -107,12 +109,13 @@ func TestRowsError(t *testing.T) { } func TestRowHeight(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) assert.EqualError(t, f.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), newInvalidRowNumberError(0).Error()) - _, err := f.GetRowHeight("Sheet1", 0) + _, err = f.GetRowHeight("Sheet1", 0) assert.EqualError(t, err, newInvalidRowNumberError(0).Error()) assert.NoError(t, f.SetRowHeight(sheet1, 1, 111.0)) @@ -165,7 +168,8 @@ func TestRowHeight(t *testing.T) { } func TestColumns(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) rows, err := f.Rows("Sheet1") assert.NoError(t, err) @@ -198,7 +202,8 @@ func TestColumns(t *testing.T) { } func TestSharedStringsReader(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) f.sharedStringsReader() si := xlsxSI{} @@ -232,7 +237,8 @@ func TestRowVisibility(t *testing.T) { } func TestRemoveRow(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) @@ -293,7 +299,8 @@ func TestRemoveRow(t *testing.T) { } func TestInsertRow(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) @@ -326,7 +333,8 @@ func TestInsertRow(t *testing.T) { // for insert workflow to be constant to avoid side effect with functions // related to internal structure. func TestInsertRowInEmptyFile(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheet1 := f.GetSheetName(0) r, err := f.workSheetReader(sheet1) assert.NoError(t, err) @@ -353,7 +361,8 @@ func TestDuplicateRowFromSingleRow(t *testing.T) { } t.Run("FromSingleRow", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"])) assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"])) @@ -406,7 +415,8 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) { } t.Run("UpdateDuplicatedRows", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellStr(sheet, "A1", cells["A1"])) assert.NoError(t, f.SetCellStr(sheet, "B1", cells["B1"])) @@ -446,7 +456,8 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -482,7 +493,8 @@ func TestDuplicateRowZeroWithNoRows(t *testing.T) { outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") t.Run("ZeroWithNoRows", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.EqualError(t, f.DuplicateRow(sheet, 0), newInvalidRowNumberError(0).Error()) @@ -524,7 +536,8 @@ func TestDuplicateRowMiddleRowOfEmptyFile(t *testing.T) { outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx") t.Run("MiddleRowOfEmptyFile", func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.DuplicateRow(sheet, 99)) @@ -560,7 +573,8 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -605,7 +619,8 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -650,7 +665,8 @@ func TestDuplicateRowInsertBefore(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -697,7 +713,8 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -743,7 +760,8 @@ func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) { } newFileWithDefaults := func() *File { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for cell, val := range cells { assert.NoError(t, f.SetCellStr(sheet, cell, val)) } @@ -796,7 +814,8 @@ func TestDuplicateRowInvalidRowNum(t *testing.T) { for _, row := range invalidIndexes { name := fmt.Sprintf("%d", row) t.Run(name, func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for col, val := range cells { assert.NoError(t, f.SetCellStr(sheet, col, val)) } @@ -818,7 +837,8 @@ func TestDuplicateRowInvalidRowNum(t *testing.T) { for _, row2 := range invalidIndexes { name := fmt.Sprintf("[%d,%d]", row1, row2) t.Run(name, func(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) for col, val := range cells { assert.NoError(t, f.SetCellStr(sheet, col, val)) } @@ -839,7 +859,9 @@ func TestDuplicateRowInvalidRowNum(t *testing.T) { } func TestDuplicateRowTo(t *testing.T) { - f, sheetName := NewFile(), "Sheet1" + f, err := NewFile() + assert.NoError(t, err) + sheetName := "Sheet1" // Test duplicate row with invalid target row number assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 0)) // Test duplicate row with equal source and target row number @@ -867,7 +889,8 @@ func TestDuplicateMergeCells(t *testing.T) { func TestGetValueFromInlineStr(t *testing.T) { c := &xlsxC{T: "inlineStr"} - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) d := &xlsxSST{} val, err := c.getValueFrom(f, d, false) assert.NoError(t, err) @@ -876,7 +899,8 @@ func TestGetValueFromInlineStr(t *testing.T) { func TestGetValueFromNumber(t *testing.T) { c := &xlsxC{T: "n"} - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) d := &xlsxSST{} for input, expected := range map[string]string{ "2.2.": "2.2.", @@ -901,12 +925,14 @@ func TestErrSheetNotExistError(t *testing.T) { } func TestCheckRow(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) - _, err := f.GetRows("Sheet1") + _, err = f.GetRows("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", false)) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+`12345`)) f.Sheet.Delete("xl/worksheets/sheet1.xml") delete(f.checked, "xl/worksheets/sheet1.xml") @@ -914,7 +940,8 @@ func TestCheckRow(t *testing.T) { } func TestSetRowStyle(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) style1, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#63BE7B"],"pattern":1}}`) assert.NoError(t, err) style2, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`) diff --git a/shape.go b/shape.go index ddf9e317c6..82031d6c70 100644 --- a/shape.go +++ b/shape.go @@ -279,6 +279,10 @@ func parseFormatShapeSet(formatSet string) (*formatShape, error) { // wavyDbl // func (f *File) AddShape(sheet, cell, format string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + formatSet, err := parseFormatShapeSet(format) if err != nil { return err @@ -301,7 +305,10 @@ func (f *File) AddShape(sheet, cell, format string) error { } else { // Add first shape for given sheet. sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + rID, err := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + if err != nil { + return err + } f.addSheetDrawing(sheet, rID) f.addSheetNameSpace(sheet, SourceRelationship) } @@ -349,7 +356,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY, width, height) - content, cNvPrID := f.drawingParser(drawingXML) + content, cNvPrID, err := f.drawingParser(drawingXML) + if err != nil { + return err + } twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Format.Positioning from := xlsxFrom{} diff --git a/shape_test.go b/shape_test.go index 2f005894d0..79732f2763 100644 --- a/shape_test.go +++ b/shape_test.go @@ -60,7 +60,8 @@ func TestAddShape(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx"))) // Test add first shape for given sheet. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.NoError(t, f.AddShape("Sheet1", "A1", `{ "type": "ellipseRibbon", "color": diff --git a/sheet.go b/sheet.go index 45b724f7bb..b5864f6645 100644 --- a/sheet.go +++ b/sheet.go @@ -18,7 +18,6 @@ import ( "fmt" "io" "io/ioutil" - "log" "os" "path" "path/filepath" @@ -38,13 +37,15 @@ import ( // (spreadsheet) after it appended. Note that the worksheet names are not // case-sensitive, when creating a new spreadsheet file, the default // worksheet named `Sheet1` will be created. -func (f *File) NewSheet(name string) int { +func (f *File) NewSheet(name string) (int, error) { + if !f.IsValid() { + return -1, ErrIncompleteFileSetup + } // Check if the worksheet already exists index := f.GetSheetIndex(name) if index != -1 { - return index + return index, nil } - f.DeleteSheet(name) f.SheetCount++ wb := f.workbookReader() sheetID := 0 @@ -61,49 +62,67 @@ func (f *File) NewSheet(name string) int { // Create new sheet /xl/worksheets/sheet%d.xml f.setSheet(sheetID, name) // Update workbook.xml.rels - rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipWorkSheet, fmt.Sprintf("/xl/worksheets/sheet%d.xml", sheetID), "") + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return index, err + } + rID, err := f.addRels(wrp, SourceRelationshipWorkSheet, fmt.Sprintf("/xl/worksheets/sheet%d.xml", sheetID), "") + if err != nil { + return index, err + } // Update workbook.xml f.setWorkbook(name, sheetID, rID) - return f.GetSheetIndex(name) + return f.GetSheetIndex(name), err } -// contentTypesReader provides a function to get the pointer to the -// [Content_Types].xml structure after deserialization. -func (f *File) contentTypesReader() *xlsxTypes { - var err error - +// NewContentTypesReader provides a function to get the pointer to ContentTypes. +func (f *File) NewContentTypesReader() (*xlsxTypes, error) { if f.ContentTypes == nil { f.ContentTypes = new(xlsxTypes) f.ContentTypes.Lock() defer f.ContentTypes.Unlock() - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))). + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))). Decode(f.ContentTypes); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return f.ContentTypes, fmt.Errorf("xml decode error: %w", err) } } + return f.ContentTypes, nil +} + +// contentTypesReader provides a function to get the pointer to ContentTypes. +func (f *File) contentTypesReader() *xlsxTypes { return f.ContentTypes } // contentTypesWriter provides a function to save [Content_Types].xml after // serialize structure. -func (f *File) contentTypesWriter() { +func (f *File) contentTypesWriter() error { if f.ContentTypes != nil { - output, _ := xml.Marshal(f.ContentTypes) + output, err := xml.Marshal(f.ContentTypes) + if err != nil { + return err + } f.saveFileList(defaultXMLPathContentTypes, output) } + return nil } // getWorksheetPath construct a target XML as xl/worksheets/sheet%d by split // path, compatible with different types of relative paths in // workbook.xml.rels, for example: worksheets/sheet%d.xml // and /xl/worksheets/sheet%d.xml -func (f *File) getWorksheetPath(relTarget string) (path string) { +func (f *File) getWorksheetPath(relTarget string) (path string, err error) { + var wrp string + wrp, err = f.getWorkbookPath() + if err != nil { + return + } path = filepath.ToSlash(strings.TrimPrefix( - strings.ReplaceAll(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), relTarget)), "\\", "/"), "/")) + strings.ReplaceAll(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(wrp), relTarget)), "\\", "/"), "/")) if strings.HasPrefix(relTarget, "/") { path = filepath.ToSlash(strings.TrimPrefix(strings.ReplaceAll(filepath.Clean(relTarget), "\\", "/"), "/")) } - return path + return } // mergeExpandedCols merge expanded columns. @@ -139,7 +158,7 @@ func (f *File) mergeExpandedCols(ws *xlsxWorksheet) { // workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after // serialize structure. -func (f *File) workSheetWriter() { +func (f *File) workSheetWriter() (firstError error) { var arr []byte buffer := bytes.NewBuffer(arr) encoder := xml.NewEncoder(buffer) @@ -147,7 +166,11 @@ func (f *File) workSheetWriter() { if ws != nil { sheet := ws.(*xlsxWorksheet) if sheet.MergeCells != nil && len(sheet.MergeCells.Cells) > 0 { - _ = f.mergeOverlapCells(sheet) + err := f.mergeOverlapCells(sheet) + if err != nil && firstError == nil { + sheetName := f.getSheetNameBySheetXMLPath(p.(string)) + firstError = fmt.Errorf("error in sheet %q: %w", sheetName, err) + } } if sheet.Cols != nil && len(sheet.Cols.Col) > 0 { f.mergeExpandedCols(sheet) @@ -177,6 +200,7 @@ func (f *File) workSheetWriter() { } return true }) + return } // trimCell provides a function to trim blank cells which created by fillColumns. @@ -203,6 +227,9 @@ func trimCell(column []xlsxC) []xlsxC { // type of the spreadsheet. func (f *File) setContentTypes(partName, contentType string) { content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() content.Overrides = append(content.Overrides, xlsxOverride{ @@ -227,17 +254,24 @@ func (f *File) setSheet(index int, name string) { // relsWriter provides a function to save relationships after // serialize structure. -func (f *File) relsWriter() { +func (f *File) relsWriter() (firstError error) { f.Relationships.Range(func(path, rel interface{}) bool { if rel != nil { - output, _ := xml.Marshal(rel.(*xlsxRelationships)) - if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") { - output = f.replaceNameSpaceBytes(path.(string), output) + output, err := xml.Marshal(rel.(*xlsxRelationships)) + if err != nil { + if firstError == nil { + firstError = err + } + } else { + if strings.HasPrefix(path.(string), "xl/worksheets/sheet/rels/sheet") { + output = f.replaceNameSpaceBytes(path.(string), output) + } + f.saveFileList(path.(string), replaceRelationshipsBytes(output)) } - f.saveFileList(path.(string), replaceRelationshipsBytes(output)) } return true }) + return } // setAppXML update docProps/app.xml file of XML. @@ -259,6 +293,9 @@ func replaceRelationshipsBytes(content []byte) []byte { // ID returned by function GetSheetMap(). It should be greater or equal to 0 // and less than the total worksheet numbers. func (f *File) SetActiveSheet(index int) { + if !f.IsValid() { + return + } if index < 0 { index = 0 } @@ -349,6 +386,9 @@ func (f *File) SetSheetName(oldName, newName string) { return } content := f.workbookReader() + if content == nil { + return + } for k, v := range content.Sheets.Sheet { if v.Name == oldName { content.Sheets.Sheet[k].Name = newName @@ -437,12 +477,27 @@ func (f *File) GetSheetList() (list []string) { // getSheetMap provides a function to get worksheet name and XML file path map // of the spreadsheet. -func (f *File) getSheetMap() map[string]string { +func (f *File) getSheetMap() (map[string]string, error) { maps := map[string]string{} - for _, v := range f.workbookReader().Sheets.Sheet { - for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships { + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return maps, err + } + wbRels, err := f.relsReader(wrp) + if err != nil { + return maps, err + } + wb := f.workbookReader() + if wb == nil { + return maps, ErrIncompleteFileSetup + } + for _, v := range wb.Sheets.Sheet { + for _, rel := range wbRels.Relationships { if rel.ID == v.ID { - sheetXMLPath := f.getWorksheetPath(rel.Target) + sheetXMLPath, err := f.getWorksheetPath(rel.Target) + if err != nil { + return maps, err + } if _, ok := f.Pkg.Load(sheetXMLPath); ok { maps[v.Name] = sheetXMLPath } @@ -452,7 +507,23 @@ func (f *File) getSheetMap() map[string]string { } } } - return maps + return maps, nil +} + +// getSheetNameBySheetXMLPath provides a function to get worksheet name +// by checking the sheet map against the given XML file path. +func (f *File) getSheetNameBySheetXMLPath(sheetXMLPath string) (sheet string) { + sheetMap, err := f.getSheetMap() + if err != nil { + return + } + for k, v := range sheetMap { + if v == sheetXMLPath { + sheet = k + return + } + } + return } // SetSheetBackground provides a function to set background picture by given @@ -467,10 +538,16 @@ func (f *File) SetSheetBackground(sheet, picture string) error { if !ok { return ErrImgExt } - file, _ := ioutil.ReadFile(filepath.Clean(picture)) + file, err := ioutil.ReadFile(filepath.Clean(picture)) + if err != nil { + return err + } name := f.addMedia(file, ext) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") + rID, err := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") + if err != nil { + return err + } f.addSheetPicture(sheet, rID) f.addSheetNameSpace(sheet, SourceRelationship) f.setContentTypePartImageExtensions() @@ -484,13 +561,23 @@ func (f *File) SetSheetBackground(sheet, picture string) error { // referenced value of the deleted worksheet, it will cause a file error when // you open it. This function will be invalid when only the one worksheet is // left. -func (f *File) DeleteSheet(name string) { +func (f *File) DeleteSheet(name string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 { - return + return nil } sheetName := trimSheetName(name) wb := f.workbookReader() - wbRels := f.relsReader(f.getWorkbookRelsPath()) + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return err + } + wbRels, err := f.relsReader(wrp) + if err != nil { + return err + } activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) deleteLocalSheetID := f.GetSheetIndex(name) deleteAndAdjustDefinedNames(wb, deleteLocalSheetID) @@ -505,12 +592,18 @@ func (f *File) DeleteSheet(name string) { if wbRels != nil { for _, rel := range wbRels.Relationships { if rel.ID == sheet.ID { - sheetXML = f.getWorksheetPath(rel.Target) + sheetXML, err = f.getWorksheetPath(rel.Target) + if err != nil { + return err + } rels = "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[sheetName], "xl/worksheets/") + ".rels" } } } - target := f.deleteSheetFromWorkbookRels(sheet.ID) + target, err := f.deleteSheetFromWorkbookRels(sheet.ID) + if err != nil { + return err + } f.deleteSheetFromContentTypes(target) f.deleteCalcChain(sheet.SheetID, "") delete(f.sheetMap, sheet.Name) @@ -522,6 +615,7 @@ func (f *File) DeleteSheet(name string) { f.SheetCount-- } f.SetActiveSheet(f.GetSheetIndex(activeSheetName)) + return err } // deleteAndAdjustDefinedNames delete and adjust defined name in the workbook @@ -546,17 +640,24 @@ func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) { // deleteSheetFromWorkbookRels provides a function to remove worksheet // relationships by given relationships ID in the file workbook.xml.rels. -func (f *File) deleteSheetFromWorkbookRels(rID string) string { - content := f.relsReader(f.getWorkbookRelsPath()) +func (f *File) deleteSheetFromWorkbookRels(rID string) (string, error) { + wrp, err := f.getWorkbookRelsPath() + if err != nil { + return "", err + } + content, err := f.relsReader(wrp) + if err != nil { + return "", err + } content.Lock() defer content.Unlock() for k, v := range content.Relationships { if v.ID == rID { content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...) - return v.Target + return v.Target, nil } } - return "" + return "", nil } // deleteSheetFromContentTypes provides a function to remove worksheet @@ -566,6 +667,9 @@ func (f *File) deleteSheetFromContentTypes(target string) { target = "/xl/" + target } content := f.contentTypesReader() + if content == nil { + return + } content.Lock() defer content.Unlock() for k, v := range content.Overrides { @@ -634,6 +738,9 @@ func (f *File) copySheet(from, to int) error { // err := f.SetSheetVisible("Sheet1", false) // func (f *File) SetSheetVisible(name string, visible bool) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } name = trimSheetName(name) content := f.workbookReader() if visible { @@ -801,6 +908,9 @@ func (f *File) SetPanes(sheet, panes string) error { // func (f *File) GetSheetVisible(name string) bool { content := f.workbookReader() + if content == nil { + return false + } visible := false for k, v := range content.Sheets.Sheet { if v.Name == trimSheetName(name) { @@ -841,7 +951,10 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { } if ws, ok := f.Sheet.Load(name); ok && ws != nil { // flush data - output, _ := xml.Marshal(ws.(*xlsxWorksheet)) + output, err := xml.Marshal(ws.(*xlsxWorksheet)) + if err != nil { + return result, err + } f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } return f.searchSheet(name, value, regSearch) @@ -856,7 +969,10 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, d *xlsxSST ) - d = f.sharedStringsReader() + d, err = f.sharedStringsReader() + if err != nil { + return + } decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name))) for { var token xml.Token @@ -878,8 +994,15 @@ 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) + err = decoder.DecodeElement(&colCell, &xmlElement) + if err != nil { + return + } + var val string + val, err = colCell.getValueFrom(f, d, false) + if err != nil { + return + } if regSearch { regex := regexp.MustCompile(value) if !regex.MatchString(val) { @@ -892,11 +1015,11 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, } cellCol, _, err = CellNameToCoordinates(colCell.R) if err != nil { - return result, err + return } cellName, err = CoordinatesToCellName(cellCol, row) if err != nil { - return result, err + return } result = append(result, cellName) } @@ -1520,6 +1643,9 @@ func (f *File) GetPageLayout(sheet string, opts ...PageLayoutOptionPtr) error { // func (f *File) SetDefinedName(definedName *DefinedName) error { wb := f.workbookReader() + if wb == nil { + return ErrIncompleteFileSetup + } d := xlsxDefinedName{ Name: definedName.Name, Comment: definedName.Comment, @@ -1560,6 +1686,9 @@ func (f *File) SetDefinedName(definedName *DefinedName) error { // func (f *File) DeleteDefinedName(definedName *DefinedName) error { wb := f.workbookReader() + if wb == nil { + return ErrIncompleteFileSetup + } if wb.DefinedNames != nil { for idx, dn := range wb.DefinedNames.DefinedName { scope := "Workbook" @@ -1584,6 +1713,9 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error { func (f *File) GetDefinedName() []DefinedName { var definedNames []DefinedName wb := f.workbookReader() + if wb == nil { + return definedNames + } if wb.DefinedNames != nil { for _, dn := range wb.DefinedNames.DefinedName { definedName := DefinedName{ @@ -1768,23 +1900,22 @@ func (f *File) RemovePageBreak(sheet, cell string) (err error) { // relsReader provides a function to get the pointer to the structure // after deserialization of xl/worksheets/_rels/sheet%d.xml.rels. -func (f *File) relsReader(path string) *xlsxRelationships { - var err error +func (f *File) relsReader(path string) (*xlsxRelationships, error) { rels, _ := f.Relationships.Load(path) if rels == nil { if _, ok := f.Pkg.Load(path); ok { c := xlsxRelationships{} - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). Decode(&c); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) + return nil, fmt.Errorf("xml decode error: %w", err) } f.Relationships.Store(path, &c) } } if rels, _ = f.Relationships.Load(path); rels != nil { - return rels.(*xlsxRelationships) + return rels.(*xlsxRelationships), nil } - return nil + return nil, nil } // fillSheetData ensures there are enough rows, and columns in the chosen diff --git a/sheet_test.go b/sheet_test.go index 3ad0e75245..7506f79db0 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -13,7 +13,10 @@ import ( ) func ExampleFile_SetPageLayout() { - f := NewFile() + f, err := NewFile() + if err != nil { + fmt.Println(err) + } if err := f.SetPageLayout( "Sheet1", BlackAndWhite(true), @@ -30,7 +33,10 @@ func ExampleFile_SetPageLayout() { } func ExampleFile_GetPageLayout() { - f := NewFile() + f, err := NewFile() + if err != nil { + fmt.Println(err) + } var ( blackAndWhite BlackAndWhite firstPageNumber FirstPageNumber @@ -81,19 +87,26 @@ func ExampleFile_GetPageLayout() { } func TestNewSheet(t *testing.T) { - f := NewFile() - f.NewSheet("Sheet2") - sheetID := f.NewSheet("sheet2") + f, err := NewFile() + assert.NoError(t, err) + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) + sheetID, err := f.NewSheet("sheet2") + assert.NoError(t, err) f.SetActiveSheet(sheetID) // delete original sheet - f.DeleteSheet(f.GetSheetName(f.GetSheetIndex("Sheet1"))) + err = f.DeleteSheet(f.GetSheetName(f.GetSheetIndex("Sheet1"))) + assert.NoError(t, err) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx"))) // create new worksheet with already exists name - assert.Equal(t, f.GetSheetIndex("Sheet2"), f.NewSheet("Sheet2")) + sheetID, err = f.NewSheet("Sheet2") + assert.NoError(t, err) + assert.Equal(t, f.GetSheetIndex("Sheet2"), sheetID) } func TestSetPane(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`)) f.NewSheet("Panes 2") assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`)) @@ -105,7 +118,8 @@ func TestSetPane(t *testing.T) { assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN is not exist") assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx"))) // Test add pane on empty sheet views worksheet - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.checked = nil f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) @@ -137,7 +151,8 @@ func TestPageLayoutOption(t *testing.T) { val1 := deepcopy.Copy(def).(PageLayoutOptionPtr) val2 := deepcopy.Copy(def).(PageLayoutOptionPtr) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Get the default value assert.NoError(t, f.GetPageLayout(sheet, def), opt) // Get again and check @@ -198,12 +213,14 @@ func TestSearchSheet(t *testing.T) { assert.NoError(t, f.Close()) // Test search worksheet data after set cell value - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", true)) _, err = f.SearchSheet("Sheet1", "") assert.NoError(t, err) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`A`)) f.checked = nil @@ -223,19 +240,22 @@ func TestSearchSheet(t *testing.T) { } func TestSetPageLayout(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test set page layout on not exists worksheet. assert.EqualError(t, f.SetPageLayout("SheetN"), "sheet SheetN is not exist") } func TestGetPageLayout(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test get page layout on not exists worksheet. assert.EqualError(t, f.GetPageLayout("SheetN"), "sheet SheetN is not exist") } func TestSetHeaderFooter(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter")) // Test set header and footer on not exists worksheet. assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN is not exist") @@ -266,7 +286,8 @@ func TestSetHeaderFooter(t *testing.T) { } func TestDefinedName(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5", @@ -296,7 +317,8 @@ func TestDefinedName(t *testing.T) { } func TestGroupSheets(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheets := []string{"Sheet2", "Sheet3"} for _, sheet := range sheets { f.NewSheet(sheet) @@ -308,7 +330,8 @@ func TestGroupSheets(t *testing.T) { } func TestUngroupSheets(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"} for _, sheet := range sheets { f.NewSheet(sheet) @@ -317,7 +340,8 @@ func TestUngroupSheets(t *testing.T) { } func TestInsertPageBreak(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.InsertPageBreak("Sheet1", "A1")) assert.NoError(t, f.InsertPageBreak("Sheet1", "B2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "C3")) @@ -328,7 +352,8 @@ func TestInsertPageBreak(t *testing.T) { } func TestRemovePageBreak(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.RemovePageBreak("Sheet1", "A2")) assert.NoError(t, f.InsertPageBreak("Sheet1", "A2")) @@ -378,8 +403,28 @@ func TestGetSheetMap(t *testing.T) { assert.NoError(t, f.Close()) } +func TestGetSheetNameBySheetXMLPath(t *testing.T) { + expectedNames := map[string]string{ + "xl/worksheets/sheet1.xml": "Sheet1", + "xl/worksheets/sheet2.xml": "Sheet2", + } + f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) + assert.NoError(t, err) + + sheetMap, err := f.getSheetMap() + assert.NoError(t, err) + + for _, v := range sheetMap { + sheet := f.getSheetNameBySheetXMLPath(v) + assert.Equal(t, expectedNames[v], sheet) + } + assert.Equal(t, len(sheetMap), 2) + assert.NoError(t, f.Close()) +} + func TestSetActiveSheet(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.WorkBook.BookViews = nil f.SetActiveSheet(1) f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}} @@ -391,13 +436,16 @@ func TestSetActiveSheet(t *testing.T) { assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = nil f.SetActiveSheet(1) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.SetActiveSheet(-1) assert.Equal(t, f.GetActiveSheetIndex(), 0) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.WorkBook.BookViews = nil - idx := f.NewSheet("Sheet2") + idx, err := f.NewSheet("Sheet2") + assert.NoError(t, err) ws, ok = f.Sheet.Load("xl/worksheets/sheet2.xml") assert.True(t, ok) ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}} @@ -405,48 +453,61 @@ func TestSetActiveSheet(t *testing.T) { } func TestSetSheetName(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test set worksheet with the same name. f.SetSheetName("Sheet1", "Sheet1") assert.Equal(t, "Sheet1", f.GetSheetName(0)) } func TestWorksheetWriter(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test set cell value with alternate content f.Sheet.Delete("xl/worksheets/sheet1.xml") worksheet := xml.Header + `%d` f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(worksheet, 1))) f.checked = nil assert.NoError(t, f.SetCellValue("Sheet1", "A1", 2)) - f.workSheetWriter() + err = f.workSheetWriter() + assert.NoError(t, err) value, ok := f.Pkg.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) assert.Equal(t, fmt.Sprintf(worksheet, 2), string(value.([]byte))) } func TestGetWorkbookPath(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Pkg.Delete("_rels/.rels") - assert.Equal(t, "", f.getWorkbookPath()) + wp, err := f.getWorkbookPath() + assert.NoError(t, err) + assert.Equal(t, "", wp) } func TestGetWorkbookRelsPath(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.Pkg.Delete("xl/_rels/.rels") f.Pkg.Store("_rels/.rels", []byte(xml.Header+``)) - assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath()) + wrp, err := f.getWorkbookRelsPath() + assert.NoError(t, err) + assert.Equal(t, "_rels/workbook.xml.rels", wrp) } func TestDeleteSheet(t *testing.T) { - f := NewFile() - f.SetActiveSheet(f.NewSheet("Sheet2")) + f, err := NewFile() + assert.NoError(t, err) + sheet, err := f.NewSheet("Sheet2") + assert.NoError(t, err) + f.SetActiveSheet(sheet) f.NewSheet("Sheet3") f.DeleteSheet("Sheet1") assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex())) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx"))) // Test with auto filter defined names - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.NewSheet("Sheet2") f.NewSheet("Sheet3") assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A")) @@ -474,7 +535,10 @@ func BenchmarkNewSheet(b *testing.B) { } func newSheetWithSet() { - file := NewFile() + file, err := NewFile() + if err != nil { + fmt.Println(err) + } file.NewSheet("sheet1") for i := 0; i < 1000; i++ { _ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i) @@ -491,7 +555,10 @@ func BenchmarkFile_SaveAs(b *testing.B) { } func newSheetWithSave() { - file := NewFile() + file, err := NewFile() + if err != nil { + fmt.Println(err) + } file.NewSheet("sheet1") for i := 0; i < 1000; i++ { _ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i) diff --git a/sheetpr_test.go b/sheetpr_test.go index 91685d8837..c948b34bf6 100644 --- a/sheetpr_test.go +++ b/sheetpr_test.go @@ -29,7 +29,8 @@ var _ = []SheetPrOptionPtr{ } func ExampleFile_SetSheetPrOptions() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" if err := f.SetSheetPrOptions(sheet, @@ -47,7 +48,8 @@ func ExampleFile_SetSheetPrOptions() { } func ExampleFile_GetSheetPrOptions() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" var ( @@ -115,7 +117,8 @@ func TestSheetPrOptions(t *testing.T) { val1 := deepcopy.Copy(def).(SheetPrOptionPtr) val2 := deepcopy.Copy(def).(SheetPrOptionPtr) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Get the default value assert.NoError(t, f.GetSheetPrOptions(sheet, def), opt) // Get again and check @@ -153,14 +156,16 @@ func TestSheetPrOptions(t *testing.T) { } func TestSetSheetPrOptions(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) assert.NoError(t, f.SetSheetPrOptions("Sheet1", TabColor(""))) // Test SetSheetPrOptions on not exists worksheet. assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist") } func TestGetSheetPrOptions(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) // Test GetSheetPrOptions on not exists worksheet. assert.EqualError(t, f.GetSheetPrOptions("SheetN"), "sheet SheetN is not exist") } @@ -184,7 +189,8 @@ var _ = []PageMarginsOptionsPtr{ } func ExampleFile_SetPageMargins() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" if err := f.SetPageMargins(sheet, @@ -201,7 +207,8 @@ func ExampleFile_SetPageMargins() { } func ExampleFile_GetPageMargins() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" var ( @@ -264,7 +271,8 @@ func TestPageMarginsOption(t *testing.T) { val1 := deepcopy.Copy(def).(PageMarginsOptionsPtr) val2 := deepcopy.Copy(def).(PageMarginsOptionsPtr) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Get the default value assert.NoError(t, f.GetPageMargins(sheet, def), opt) // Get again and check @@ -302,19 +310,22 @@ func TestPageMarginsOption(t *testing.T) { } func TestSetPageMargins(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) // Test set page margins on not exists worksheet. assert.EqualError(t, f.SetPageMargins("SheetN"), "sheet SheetN is not exist") } func TestGetPageMargins(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) // Test get page margins on not exists worksheet. assert.EqualError(t, f.GetPageMargins("SheetN"), "sheet SheetN is not exist") } func ExampleFile_SetSheetFormatPr() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" if err := f.SetSheetFormatPr(sheet, @@ -332,7 +343,8 @@ func ExampleFile_SetSheetFormatPr() { } func ExampleFile_GetSheetFormatPr() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" var ( @@ -400,7 +412,8 @@ func TestSheetFormatPrOptions(t *testing.T) { val1 := deepcopy.Copy(def).(SheetFormatPrOptionsPtr) val2 := deepcopy.Copy(def).(SheetFormatPrOptionsPtr) - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Get the default value assert.NoError(t, f.GetSheetFormatPr(sheet, def), opt) // Get again and check @@ -438,7 +451,8 @@ func TestSheetFormatPrOptions(t *testing.T) { } func TestSetSheetFormatPr(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) assert.NoError(t, f.GetSheetFormatPr("Sheet1")) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) @@ -449,7 +463,8 @@ func TestSetSheetFormatPr(t *testing.T) { } func TestGetSheetFormatPr(t *testing.T) { - f := NewFile() + f, err := NewFile() + fmt.Println(err) assert.NoError(t, f.GetSheetFormatPr("Sheet1")) ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) diff --git a/sheetview_test.go b/sheetview_test.go index 2bba8f9802..0e0119a77a 100644 --- a/sheetview_test.go +++ b/sheetview_test.go @@ -45,7 +45,8 @@ var _ = []SheetViewOptionPtr{ } func ExampleFile_SetSheetViewOptions() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" if err := f.SetSheetViewOptions(sheet, 0, @@ -98,7 +99,8 @@ func ExampleFile_SetSheetViewOptions() { } func ExampleFile_GetSheetViewOptions() { - f := NewFile() + f, err := NewFile() + fmt.Println(err) const sheet = "Sheet1" var ( @@ -199,7 +201,8 @@ func ExampleFile_GetSheetViewOptions() { } func TestSheetViewOptionsErrors(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) const sheet = "Sheet1" assert.NoError(t, f.GetSheetViewOptions(sheet, 0)) diff --git a/sparkline_test.go b/sparkline_test.go index c21687cbb9..ac1c4e5291 100644 --- a/sparkline_test.go +++ b/sparkline_test.go @@ -273,7 +273,8 @@ func TestAddSparkline(t *testing.T) { func TestAppendSparkline(t *testing.T) { // Test unsupported charset. - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) ws, err := f.workSheetReader("Sheet1") assert.NoError(t, err) ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)} @@ -281,7 +282,8 @@ func TestAppendSparkline(t *testing.T) { } func prepareSparklineDataset() *File { - f := NewFile() + f, err := NewFile() + fmt.Println(err) sheet2 := [][]int{ {-2, 2, 3, -1, 0}, {30, 20, 33, 20, 15}, diff --git a/stream.go b/stream.go index 641340ede9..47f95de804 100644 --- a/stream.go +++ b/stream.go @@ -141,6 +141,10 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) { // // See File.AddTable for details on the table format. func (sw *StreamWriter) AddTable(hCell, vCell, format string) error { + if !sw.File.IsValid() { + return ErrIncompleteFileSetup + } + formatSet, err := parseFormatTableSet(format) if err != nil { return err @@ -211,15 +215,21 @@ func (sw *StreamWriter) AddTable(hCell, vCell, format string) error { // Add first table for given sheet. sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)] sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" - rID := sw.File.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") + rID, err := sw.File.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") + if err != nil { + return err + } sw.tableParts = fmt.Sprintf(``, rID) sw.File.addContentTypePart(tableID, "table") - b, _ := xml.Marshal(table) + b, err := xml.Marshal(table) + if err != nil { + return err + } sw.File.saveFileList(tableXML, b) - return nil + return err } // Extract values from a row in the StreamWriter. diff --git a/stream_test.go b/stream_test.go index 9776b384a9..775ab761c9 100644 --- a/stream_test.go +++ b/stream_test.go @@ -15,7 +15,8 @@ import ( ) func BenchmarkStreamWriter(b *testing.B) { - file := NewFile() + file, err := NewFile() + assert.NoError(b, err) row := make([]interface{}, 10) for colID := 0; colID < 10; colID++ { @@ -34,7 +35,8 @@ func BenchmarkStreamWriter(b *testing.B) { } func TestStreamWriter(t *testing.T) { - file := NewFile() + file, err := NewFile() + assert.NoError(t, err) streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) @@ -78,7 +80,8 @@ func TestStreamWriter(t *testing.T) { assert.EqualError(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber.Error()) // Test close temporary file error. - file = NewFile() + file, err = NewFile() + assert.NoError(t, err) streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) for rowID := 10; rowID <= 25600; rowID++ { @@ -100,14 +103,16 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) // Test unsupported charset - file = NewFile() + file, err = NewFile() + assert.NoError(t, err) 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") // Test read cell. - file = NewFile() + file, err = NewFile() + assert.NoError(t, err) streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{Cell{StyleID: styleID, Value: "Data"}})) @@ -135,7 +140,8 @@ func TestStreamWriter(t *testing.T) { } func TestStreamSetColWidth(t *testing.T) { - file := NewFile() + file, err := NewFile() + assert.NoError(t, err) streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetColWidth(3, 2, 20)) @@ -147,7 +153,8 @@ func TestStreamSetColWidth(t *testing.T) { } func TestStreamTable(t *testing.T) { - file := NewFile() + file, err := NewFile() + assert.NoError(t, err) streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) @@ -181,7 +188,8 @@ func TestStreamTable(t *testing.T) { } func TestStreamMergeCells(t *testing.T) { - file := NewFile() + file, err := NewFile() + assert.NoError(t, err) streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.MergeCell("A1", "D1")) @@ -194,8 +202,9 @@ func TestStreamMergeCells(t *testing.T) { func TestNewStreamWriter(t *testing.T) { // Test error exceptions - file := NewFile() - _, err := file.NewStreamWriter("Sheet1") + file, err := NewFile() + assert.NoError(t, err) + _, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) _, err = file.NewStreamWriter("SheetN") assert.EqualError(t, err, "sheet SheetN is not exist") @@ -203,14 +212,16 @@ func TestNewStreamWriter(t *testing.T) { func TestSetRow(t *testing.T) { // Test error exceptions - file := NewFile() + file, err := NewFile() + assert.NoError(t, err) streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestSetCellValFunc(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) sw, err := f.NewStreamWriter("Sheet1") assert.NoError(t, err) c := &xlsxC{} diff --git a/styles.go b/styles.go index 0220e9c976..f5d1bfd194 100644 --- a/styles.go +++ b/styles.go @@ -17,7 +17,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "math" "reflect" "strconv" @@ -922,35 +921,48 @@ func formatToE(v, format string, date1904 bool) string { return fmt.Sprintf("%.2E", f) } -// stylesReader provides a function to get the pointer to the structure after +// NewStylesReader provides a function to get the pointer to the structure after // deserialization of xl/styles.xml. -func (f *File) stylesReader() *xlsxStyleSheet { +func (f *File) NewStylesReader() (*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, fmt.Errorf("xml decode error: %w", err) } } + return f.Styles, nil +} + +// stylesReader provides a function to get the pointer to Styles. +func (f *File) stylesReader() *xlsxStyleSheet { return f.Styles } // styleSheetWriter provides a function to save xl/styles.xml after serialize // structure. -func (f *File) styleSheetWriter() { +func (f *File) styleSheetWriter() error { if f.Styles != nil { - output, _ := xml.Marshal(f.Styles) + output, err := xml.Marshal(f.Styles) + if err != nil { + return err + } f.saveFileList(defaultXMLPathStyles, f.replaceNameSpaceBytes(defaultXMLPathStyles, output)) } + return nil } // sharedStringsWriter provides a function to save xl/sharedStrings.xml after // serialize structure. -func (f *File) sharedStringsWriter() { +func (f *File) sharedStringsWriter() error { if f.SharedStrings != nil { - output, _ := xml.Marshal(f.SharedStrings) + output, err := xml.Marshal(f.SharedStrings) + if err != nil { + return err + } f.saveFileList(defaultXMLPathSharedStrings, f.replaceNameSpaceBytes(defaultXMLPathSharedStrings, output)) } + return nil } // parseFormatStyleSet provides a function to parse the format settings of the @@ -1834,7 +1846,11 @@ func parseFormatStyleSet(style interface{}) (*Style, error) { // Excelize support set custom number format for cell. For example, set number // as date type in Uruguay (Spanish) format for Sheet1!A6: // -// f := excelize.NewFile() +// f, err := excelize.NewFile() +// if err != nil { +// fmt.Println(err) +// return +// } // f.SetCellValue("Sheet1", "A6", 42920.5) // exp := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" // style, err := f.NewStyle(&excelize.Style{CustomNumFmt: &exp}) @@ -1854,6 +1870,9 @@ func (f *File) NewStyle(style interface{}) (int, error) { fs.DecimalPlaces = 2 } s := f.stylesReader() + if s == nil { + return cellXfsID, ErrIncompleteFileSetup + } s.Lock() defer s.Unlock() // check given style already exist. @@ -1971,6 +1990,10 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) { // NewStyle(). 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) { + if f.Styles == nil { + return 0, ErrIncompleteFileSetup + } + s := f.stylesReader() fs, err := parseFormatStyleSet(style) if err != nil { @@ -1988,7 +2011,10 @@ func (f *File) NewConditionalStyle(style string) (int, error) { if fs.Font != nil { dxf.Font = f.newFont(fs) } - dxfStr, _ := xml.Marshal(dxf) + dxfStr, err := xml.Marshal(dxf) + if err != nil { + return 0, err + } if s.Dxfs == nil { s.Dxfs = &xlsxDxfs{} } @@ -2003,11 +2029,18 @@ func (f *File) NewConditionalStyle(style string) (int, error) { // workbook. The spreadsheet generated by excelize default font is Calibri. func (f *File) GetDefaultFont() string { font := f.readDefaultFont() + if font == nil { + return "" + } return *font.Name.Val } // SetDefaultFont changes the default font in the workbook. func (f *File) SetDefaultFont(fontName string) { + if f.Styles == nil { + return + } + font := f.readDefaultFont() font.Name.Val = stringPtr(fontName) s := f.stylesReader() @@ -3021,18 +3054,15 @@ func getPaletteColor(color string) string { return "FF" + strings.ReplaceAll(strings.ToUpper(color), "#", "") } -// themeReader provides a function to get the pointer to the xl/theme/theme1.xml +// NewThemeReader provides a function to get the pointer to the xl/theme/theme1.xml // structure after deserialization. -func (f *File) themeReader() *xlsxTheme { - var ( - err error - theme xlsxTheme - ) - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))). +func (f *File) NewThemeReader() (*xlsxTheme, error) { + var theme xlsxTheme + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))). Decode(&theme); err != nil && err != io.EOF { - log.Printf("xml decoder error: %s", err) + return &theme, fmt.Errorf("xml decode error: %w", err) } - return &theme + return &theme, nil } // ThemeColor applied the color with tint value. diff --git a/styles_test.go b/styles_test.go index 156b4e33b1..8a122958d6 100644 --- a/styles_test.go +++ b/styles_test.go @@ -26,7 +26,8 @@ func TestStyleFill(t *testing.T) { }} for _, testCase := range cases { - xl := NewFile() + xl, err := NewFile() + assert.NoError(t, err) styleID, err := xl.NewStyle(testCase.format) assert.NoError(t, err) @@ -38,7 +39,8 @@ func TestStyleFill(t *testing.T) { assert.Equal(t, *style.FillID, 0, testCase.label) } } - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) styleID1, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`) assert.NoError(t, err) styleID2, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`) @@ -156,11 +158,12 @@ func TestSetConditionalFormat(t *testing.T) { }} for _, testCase := range cases { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) const sheet = "Sheet1" const cellRange = "A1:A1" - err := f.SetConditionalFormat(sheet, cellRange, testCase.format) + err = f.SetConditionalFormat(sheet, cellRange, testCase.format) if err != nil { t.Fatalf("%s", err) } @@ -176,7 +179,8 @@ func TestSetConditionalFormat(t *testing.T) { } func TestUnsetConditionalFormat(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7)) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) format, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) @@ -190,7 +194,8 @@ func TestUnsetConditionalFormat(t *testing.T) { } func TestNewStyle(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`) assert.NoError(t, err) styles := f.stylesReader() @@ -253,7 +258,8 @@ func TestNewStyle(t *testing.T) { assert.NoError(t, err) assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) // Test currency number format customNumFmt := "[$$-409]#,##0.00" style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) @@ -266,14 +272,16 @@ func TestNewStyle(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 2, style3) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = nil style4, err := f.NewStyle(&Style{NumFmt: 160, Lang: "unknown"}) assert.NoError(t, err) assert.Equal(t, 0, style4) - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) f.Styles.NumFmts = nil f.Styles.CellXfs.Xf = nil style5, err := f.NewStyle(&Style{NumFmt: 160, Lang: "zh-cn"}) @@ -282,13 +290,15 @@ func TestNewStyle(t *testing.T) { } func TestGetDefaultFont(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) s := f.GetDefaultFont() assert.Equal(t, s, "Calibri", "Default font should be Calibri") } func TestSetDefaultFont(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) f.SetDefaultFont("Arial") styles := f.stylesReader() s := f.GetDefaultFont() @@ -297,32 +307,43 @@ func TestSetDefaultFont(t *testing.T) { } func TestStylesReader(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test read styles with unsupported charset. f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) - assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader()) + stylesReader, err := f.NewStylesReader() + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualValues(t, new(xlsxStyleSheet), stylesReader) } -func TestThemeReader(t *testing.T) { - f := NewFile() +func TestNewThemeReader(t *testing.T) { + f, err := NewFile() + assert.NoError(t, err) // Test read theme with unsupported charset. f.Pkg.Store("xl/theme/theme1.xml", MacintoshCyrillicCharset) - assert.EqualValues(t, new(xlsxTheme), f.themeReader()) + theme, err := f.NewThemeReader() + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + assert.EqualValues(t, new(xlsxTheme), theme) } func TestSetCellStyle(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test set cell style on not exists worksheet. assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN is not exist") } func TestGetStyleID(t *testing.T) { - assert.Equal(t, -1, NewFile().getStyleID(&xlsxStyleSheet{}, nil)) + f, err := NewFile() + assert.NoError(t, err) + assert.Equal(t, -1, f.getStyleID(&xlsxStyleSheet{}, nil)) } func TestGetFillID(t *testing.T) { - assert.Equal(t, -1, getFillID(NewFile().stylesReader(), &Style{Fill: Fill{Type: "unknown"}})) + f, err := NewFile() + assert.NoError(t, err) + assert.Equal(t, -1, getFillID(f.stylesReader(), &Style{Fill: Fill{Type: "unknown"}})) } func TestThemeColor(t *testing.T) { @@ -339,7 +360,8 @@ func TestThemeColor(t *testing.T) { } func TestGetNumFmtID(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) fs1, err := parseFormatStyleSet(`{"protection":{"hidden":false,"locked":false},"number_format":10}`) assert.NoError(t, err) diff --git a/table.go b/table.go index 413118c31a..14538d1ff8 100644 --- a/table.go +++ b/table.go @@ -59,6 +59,10 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) { // TableStyleDark1 - TableStyleDark11 // func (f *File) AddTable(sheet, hCell, vCell, format string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + formatSet, err := parseFormatTableSet(format) if err != nil { return err @@ -86,7 +90,10 @@ func (f *File) AddTable(sheet, hCell, vCell, format string) error { tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl") // Add first table for given sheet. sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels" - rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") + rID, err := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") + if err != nil { + return err + } if err = f.addSheetTable(sheet, rID); err != nil { return err } @@ -152,13 +159,22 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet if err != nil { return err } - name, _ := f.GetCellValue(sheet, cell) + name, err := f.GetCellValue(sheet, cell) + if err != nil { + return err + } if _, err := strconv.Atoi(name); err == nil { - _ = f.SetCellStr(sheet, cell, name) + err = f.SetCellStr(sheet, cell, name) + if err != nil { + return err + } } if name == "" { name = "Column" + strconv.Itoa(idx) - _ = f.SetCellStr(sheet, cell, name) + err = f.SetCellStr(sheet, cell, name) + if err != nil { + return err + } } tableColumn = append(tableColumn, &xlsxTableColumn{ ID: idx, @@ -190,9 +206,12 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet ShowColumnStripes: formatSet.ShowColumnStripes, }, } - table, _ := xml.Marshal(t) + table, err := xml.Marshal(t) + if err != nil { + return err + } f.saveFileList(tableXML, table) - return nil + return err } // parseAutoFilterSet provides a function to parse the settings of the auto @@ -274,6 +293,10 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) { // Price < 2000 // func (f *File) AutoFilter(sheet, hCell, vCell, format string) error { + if !f.IsValid() { + return ErrIncompleteFileSetup + } + hCol, hRow, err := CellNameToCoordinates(hCell) if err != nil { return err diff --git a/table_test.go b/table_test.go index 0a74b1b568..94ac9a653c 100644 --- a/table_test.go +++ b/table_test.go @@ -40,7 +40,8 @@ func TestAddTable(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx"))) // Test addTable with illegal cell coordinates. - f = NewFile() + f, err = NewFile() + assert.NoError(t, err) assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell coordinates [0, 0]") assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]") } @@ -121,9 +122,10 @@ func TestAutoFilterError(t *testing.T) { } func TestParseFilterTokens(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) // Test with unknown operator. - _, _, err := f.parseFilterTokens("", []string{"", "!"}) + _, _, err = f.parseFilterTokens("", []string{"", "!"}) assert.EqualError(t, err, "unknown operator: !") // Test invalid operator in context. _, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"}) diff --git a/test/Book1.xlsx b/test/Book1.xlsx index 6a497e33af..f6a78d0055 100644 Binary files a/test/Book1.xlsx and b/test/Book1.xlsx differ diff --git a/workbook.go b/workbook.go index 417524b1da..d5d5b76879 100644 --- a/workbook.go +++ b/workbook.go @@ -14,8 +14,8 @@ package excelize import ( "bytes" "encoding/xml" + "fmt" "io" - "log" "path/filepath" "strconv" "strings" @@ -45,6 +45,9 @@ type ( // characters are allowed in sheet title. func (f *File) setWorkbook(name string, sheetID, rid int) { content := f.workbookReader() + if content == nil { + return + } content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{ Name: trimSheetName(name), SheetID: sheetID, @@ -54,8 +57,13 @@ func (f *File) setWorkbook(name string, sheetID, rid int) { // getWorkbookPath provides a function to get the path of the workbook.xml in // the spreadsheet. -func (f *File) getWorkbookPath() (path string) { - if rels := f.relsReader("_rels/.rels"); rels != nil { +func (f *File) getWorkbookPath() (path string, err error) { + var rels *xlsxRelationships + rels, err = f.relsReader("_rels/.rels") + if err != nil { + return + } + if rels != nil { rels.Lock() defer rels.Unlock() for _, rel := range rels.Relationships { @@ -70,8 +78,12 @@ func (f *File) getWorkbookPath() (path string) { // getWorkbookRelsPath provides a function to get the path of the workbook.xml.rels // in the spreadsheet. -func (f *File) getWorkbookRelsPath() (path string) { - wbPath := f.getWorkbookPath() +func (f *File) getWorkbookRelsPath() (path string, err error) { + var wbPath string + wbPath, err = f.getWorkbookPath() + if err != nil { + return + } wbDir := filepath.Dir(wbPath) if wbDir == "." { path = "_rels/" + filepath.Base(wbPath) + ".rels" @@ -81,29 +93,34 @@ func (f *File) getWorkbookRelsPath() (path string) { return } -// workbookReader provides a function to get the pointer to the workbook.xml +// NewWorkbookReader provides a function to get the pointer to the workbook.xml // structure after deserialization. -func (f *File) workbookReader() *xlsxWorkbook { - var err error - if f.WorkBook == nil { - wbPath := f.getWorkbookPath() - f.WorkBook = new(xlsxWorkbook) - if _, ok := f.xmlAttr[wbPath]; !ok { - d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))) - f.xmlAttr[wbPath] = append(f.xmlAttr[wbPath], getRootElement(d)...) - f.addNameSpaces(wbPath, SourceRelationship) - } - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))). - Decode(f.WorkBook); err != nil && err != io.EOF { - log.Printf("xml decode error: %s", err) - } +func (f *File) NewWorkbookReader() (*xlsxWorkbook, error) { + wbPath, err := f.getWorkbookPath() + if err != nil { + return nil, err + } + f.WorkBook = new(xlsxWorkbook) + if _, ok := f.xmlAttr[wbPath]; !ok { + d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))) + f.xmlAttr[wbPath] = append(f.xmlAttr[wbPath], getRootElement(d)...) + f.addNameSpaces(wbPath, SourceRelationship) } + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))). + Decode(f.WorkBook); err != nil && err != io.EOF { + return f.WorkBook, fmt.Errorf("xml decode error: %w", err) + } + return f.WorkBook, nil +} + +// workbookReader provides a function to get the pointer to WorkBook. +func (f *File) workbookReader() *xlsxWorkbook { return f.WorkBook } // workBookWriter provides a function to save workbook.xml after serialize // structure. -func (f *File) workBookWriter() { +func (f *File) workBookWriter() error { if f.WorkBook != nil { if f.WorkBook.DecodeAlternateContent != nil { f.WorkBook.AlternateContent = &xlsxAlternateContent{ @@ -112,9 +129,17 @@ func (f *File) workBookWriter() { } } f.WorkBook.DecodeAlternateContent = nil - output, _ := xml.Marshal(f.WorkBook) - f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output))) + output, err := xml.Marshal(f.WorkBook) + if err != nil { + return err + } + wp, err := f.getWorkbookPath() + if err != nil { + return err + } + f.saveFileList(wp, replaceRelationshipsBytes(f.replaceNameSpaceBytes(wp, output))) } + return nil } // SetWorkbookPrOptions provides a function to sets workbook properties. @@ -125,6 +150,9 @@ func (f *File) workBookWriter() { // CodeName(string) func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error { wb := f.workbookReader() + if wb == nil { + return ErrIncompleteFileSetup + } pr := wb.WorkbookPr if pr == nil { pr = new(xlsxWorkbookPr) @@ -159,6 +187,9 @@ func (o CodeName) setWorkbookPrOption(pr *xlsxWorkbookPr) { // CodeName(string) func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error { wb := f.workbookReader() + if wb == nil { + return ErrIncompleteFileSetup + } pr := wb.WorkbookPr for _, opt := range opts { opt.getWorkbookPrOption(pr) diff --git a/workbook_test.go b/workbook_test.go index 18b222c00f..e922a642f1 100644 --- a/workbook_test.go +++ b/workbook_test.go @@ -8,7 +8,10 @@ import ( ) func ExampleFile_SetWorkbookPrOptions() { - f := NewFile() + f, err := NewFile() + if err != nil { + fmt.Println(err) + } if err := f.SetWorkbookPrOptions( Date1904(false), FilterPrivacy(false), @@ -20,7 +23,10 @@ func ExampleFile_SetWorkbookPrOptions() { } func ExampleFile_GetWorkbookPrOptions() { - f := NewFile() + f, err := NewFile() + if err != nil { + fmt.Println(err) + } var ( date1904 Date1904 filterPrivacy FilterPrivacy @@ -47,7 +53,8 @@ func ExampleFile_GetWorkbookPrOptions() { } func TestWorkbookPr(t *testing.T) { - f := NewFile() + f, err := NewFile() + assert.NoError(t, err) wb := f.workbookReader() wb.WorkbookPr = nil var date1904 Date1904