From ac564afa56a691e378ab9bb04cb14bb283886a16 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 13 Nov 2022 00:40:04 +0800 Subject: [PATCH] Remove internal error log print, throw XML deserialize error --- calcchain.go | 5 +- calcchain_test.go | 7 +++ cell.go | 13 +++-- cell_test.go | 16 ++++-- chart.go | 20 ++++--- chart_test.go | 13 +++++ comment.go | 7 ++- comment_test.go | 3 +- excelize.go | 36 ++++++++----- excelize_test.go | 98 ++++++++++++++++++---------------- file.go | 15 +++--- file_test.go | 9 ++++ picture.go | 52 ++++++++++++------ picture_test.go | 30 +++++++++++ pivotTable.go | 10 ++-- pivotTable_test.go | 9 ++++ rows.go | 9 +++- rows_test.go | 17 ++++-- shape.go | 3 +- shape_test.go | 7 ++- sheet.go | 129 +++++++++++++++++++++++++-------------------- sheet_test.go | 37 +++++++++++++ stream.go | 34 +++++++----- stream_test.go | 16 ++++-- styles.go | 9 ++-- styles_test.go | 4 +- table.go | 8 +-- table_test.go | 6 ++- templates.go | 1 + workbook.go | 26 +++++---- workbook_test.go | 12 ++++- 31 files changed, 458 insertions(+), 203 deletions(-) diff --git a/calcchain.go b/calcchain.go index 3aa5d81..5e511dc 100644 --- a/calcchain.go +++ b/calcchain.go @@ -54,7 +54,10 @@ func (f *File) deleteCalcChain(index int, cell string) error { if len(calc.C) == 0 { f.CalcChain = nil f.Pkg.Delete(defaultXMLPathCalcChain) - content := f.contentTypesReader() + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() for k, v := range content.Overrides { diff --git a/calcchain_test.go b/calcchain_test.go index fae3a51..9eec804 100644 --- a/calcchain_test.go +++ b/calcchain_test.go @@ -33,7 +33,14 @@ func TestDeleteCalcChain(t *testing.T) { formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) + + // Test delete calculation chain with unsupported charset calculation chain. f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") + + // Test delete calculation chain with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") } diff --git a/cell.go b/cell.go index cbb7932..a0a2818 100644 --- a/cell.go +++ b/cell.go @@ -241,11 +241,14 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error { ws.Lock() c.S = f.prepareCellStyle(ws, col, row, c.S) ws.Unlock() - date1904, wb := false, f.workbookReader() + var date1904, isNum bool + wb, err := f.workbookReader() + if err != nil { + return err + } if wb != nil && wb.WorkbookPr != nil { date1904 = wb.WorkbookPr.Date1904 } - var isNum bool if isNum, err = c.setCellTime(value, date1904); err != nil { return err } @@ -1320,7 +1323,11 @@ func (f *File) formattedValue(s int, v string, raw bool) (string, error) { if styleSheet.CellXfs.Xf[s].NumFmtID != nil { numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID } - date1904, wb := false, f.workbookReader() + date1904 := false + wb, err := f.workbookReader() + if err != nil { + return v, err + } if wb != nil && wb.WorkbookPr != nil { date1904 = wb.WorkbookPr.Date1904 } diff --git a/cell_test.go b/cell_test.go index 18bc101..40bab9b 100644 --- a/cell_test.go +++ b/cell_test.go @@ -173,7 +173,7 @@ func TestSetCellValue(t *testing.T) { f := NewFile() 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 + // Test set cell value with column and row style inherit. style1, err := f.NewStyle(&Style{NumFmt: 2}) assert.NoError(t, err) style2, err := f.NewStyle(&Style{NumFmt: 9}) @@ -189,10 +189,14 @@ func TestSetCellValue(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "0.50", B2) - // Test set cell value with unsupported charset shared strings table + // Test set cell value with unsupported charset shared strings table. f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8") + // Test set cell value with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetCellValue("Sheet1", "A1", time.Now().UTC()), "XML syntax error on line 1: invalid UTF-8") } func TestSetCellValues(t *testing.T) { @@ -204,7 +208,7 @@ func TestSetCellValues(t *testing.T) { assert.NoError(t, err) assert.Equal(t, v, "12/31/10 00:00") - // Test date value lower than min date supported by Excel + // Test date value lower than min date supported by Excel. err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC)) assert.NoError(t, err) @@ -782,6 +786,12 @@ func TestFormattedValue(t *testing.T) { assert.Equal(t, "0_0", fn("0_0", "", false)) } + // Test format value with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + _, err = f.formattedValue(1, "43528", false) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test format value with unsupported charset style sheet. f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) diff --git a/chart.go b/chart.go index be6ddd8..ec5e468 100644 --- a/chart.go +++ b/chart.go @@ -927,8 +927,10 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error { return err } f.addChart(options, comboCharts) - f.addContentTypePart(chartID, "chart") - f.addContentTypePart(drawingID, "drawings") + if err = f.addContentTypePart(chartID, "chart"); err != nil { + return err + } + _ = f.addContentTypePart(drawingID, "drawings") f.addSheetNameSpace(sheet, SourceRelationship) return err } @@ -952,7 +954,7 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { }, } f.SheetCount++ - wb := f.workbookReader() + wb, _ := f.workbookReader() sheetID := 0 for _, v := range wb.Sheets.Sheet { if v.SheetID > sheetID { @@ -969,11 +971,15 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { f.prepareChartSheetDrawing(&cs, drawingID, sheet) 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, &options.Format) + if err = f.addSheetDrawingChart(drawingXML, drawingRID, &options.Format); err != nil { + return err + } f.addChart(options, comboCharts) - f.addContentTypePart(chartID, "chart") - f.addContentTypePart(sheetID, "chartsheet") - f.addContentTypePart(drawingID, "drawings") + if err = f.addContentTypePart(chartID, "chart"); err != nil { + return err + } + _ = f.addContentTypePart(sheetID, "chartsheet") + _ = f.addContentTypePart(drawingID, "drawings") // Update workbook.xml.rels rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") // Update workbook.xml diff --git a/chart_test.go b/chart_test.go index dac724a..efce55d 100644 --- a/chart_test.go +++ b/chart_test.go @@ -226,6 +226,11 @@ func TestAddChart(t *testing.T) { // Test add combo chart with unsupported chart type assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown") assert.NoError(t, f.Close()) + + // Test add chart with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8") } func TestAddChartSheet(t *testing.T) { @@ -259,6 +264,14 @@ func TestAddChartSheet(t *testing.T) { assert.NoError(t, f.UpdateLinkedValue()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx"))) + // Test add chart sheet with unsupported charset drawing XML. + f.Pkg.Store("xl/drawings/drawing4.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.AddChartSheet("Chart3", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8") + // Test add chart sheet with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddChartSheet("Chart4", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteChart(t *testing.T) { diff --git a/comment.go b/comment.go index eec5fa6..ae62c37 100644 --- a/comment.go +++ b/comment.go @@ -69,8 +69,8 @@ func (f *File) GetComments() (map[string][]Comment, error) { // getSheetComments provides the method to get the target comment reference by // given worksheet file path. func (f *File) getSheetComments(sheetFile string) string { - rels := "xl/worksheets/_rels/" + sheetFile + ".rels" - if sheetRels := f.relsReader(rels); sheetRels != nil { + rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels") + if sheetRels := rels; sheetRels != nil { sheetRels.Lock() defer sheetRels.Unlock() for _, v := range sheetRels.Relationships { @@ -135,8 +135,7 @@ func (f *File) AddComment(sheet string, comment Comment) error { if err = f.addComment(commentsXML, comment); err != nil { return err } - f.addContentTypePart(commentID, "comments") - return err + return f.addContentTypePart(commentID, "comments") } // DeleteComment provides the method to delete comment in a sheet by given diff --git a/comment_test.go b/comment_test.go index ed44508..ead3939 100644 --- a/comment_test.go +++ b/comment_test.go @@ -112,7 +112,8 @@ func TestDecodeVMLDrawingReader(t *testing.T) { f := NewFile() path := "xl/drawings/vmlDrawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - f.decodeVMLDrawingReader(path) + _, err := f.decodeVMLDrawingReader(path) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestCommentsReader(t *testing.T) { diff --git a/excelize.go b/excelize.go index 256d427..f4c7a25 100644 --- a/excelize.go +++ b/excelize.go @@ -181,8 +181,10 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) { return f, err } f.sheetMap = f.getSheetMap() - f.Styles, err = f.stylesReader() - f.Theme = f.themeReader() + if f.Styles, err = f.stylesReader(); err != nil { + return f, err + } + f.Theme, err = f.themeReader() return f, err } @@ -335,7 +337,7 @@ 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) + rels, _ := f.relsReader(relPath) if rels == nil || rID == "" { return f.addRels(relPath, relType, target, targetMode) } @@ -360,7 +362,7 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { uniqPart := map[string]string{ SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", } - rels := f.relsReader(relPath) + rels, _ := f.relsReader(relPath) if rels == nil { rels = &xlsxRelationships{} } @@ -418,7 +420,10 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int { // // func (f *File) UpdateLinkedValue() error { - wb := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } // recalculate formulas wb.CalcPr = nil for _, name := range f.GetSheetList() { @@ -465,12 +470,15 @@ func (f *File) AddVBAProject(bin string) error { if path.Ext(bin) != ".bin" { return ErrAddVBAProject } - wb := f.relsReader(f.getWorkbookRelsPath()) - wb.Lock() - defer wb.Unlock() + rels, err := f.relsReader(f.getWorkbookRelsPath()) + if err != nil { + return err + } + rels.Lock() + defer rels.Unlock() var rID int var ok bool - for _, rel := range wb.Relationships { + for _, rel := range rels.Relationships { if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject { ok = true continue @@ -482,7 +490,7 @@ func (f *File) AddVBAProject(bin string) error { } rID++ if !ok { - wb.Relationships = append(wb.Relationships, xlsxRelationship{ + rels.Relationships = append(rels.Relationships, xlsxRelationship{ ID: "rId" + strconv.Itoa(rID), Target: "vbaProject.bin", Type: SourceRelationshipVBAProject, @@ -495,9 +503,12 @@ func (f *File) AddVBAProject(bin string) error { // setContentTypePartProjectExtensions provides a function to set the content // type for relationship parts and the main document part. -func (f *File) setContentTypePartProjectExtensions(contentType string) { +func (f *File) setContentTypePartProjectExtensions(contentType string) error { var ok bool - content := f.contentTypesReader() + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() for _, v := range content.Defaults { @@ -516,4 +527,5 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) { ContentType: ContentTypeVBA, }) } + return err } diff --git a/excelize_test.go b/excelize_test.go index cab994f..ece74b2 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -219,26 +219,31 @@ func TestOpenReader(t *testing.T) { assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) // Test open workbook with unsupported charset internal calculation chain. - source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx")) - assert.NoError(t, err) - buf := new(bytes.Buffer) - zw := zip.NewWriter(buf) - for _, item := range source.File { - // The following statements can be simplified as zw.Copy(item) in go1.17 - writer, err := zw.Create(item.Name) + preset := func(filePath string) *bytes.Buffer { + source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) - readerCloser, err := item.Open() + buf := new(bytes.Buffer) + zw := zip.NewWriter(buf) + for _, item := range source.File { + // The following statements can be simplified as zw.Copy(item) in go1.17 + writer, err := zw.Create(item.Name) + assert.NoError(t, err) + readerCloser, err := item.Open() + assert.NoError(t, err) + _, err = io.Copy(writer, readerCloser) + assert.NoError(t, err) + } + fi, err := zw.Create(filePath) assert.NoError(t, err) - _, err = io.Copy(writer, readerCloser) + _, err = fi.Write(MacintoshCyrillicCharset) assert.NoError(t, err) + assert.NoError(t, zw.Close()) + return buf + } + for _, defaultXMLPath := range []string{defaultXMLPathCalcChain, defaultXMLPathStyles} { + _, err = OpenReader(preset(defaultXMLPath)) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } - fi, err := zw.Create(defaultXMLPathCalcChain) - assert.NoError(t, err) - _, err = fi.Write(MacintoshCyrillicCharset) - assert.NoError(t, err) - assert.NoError(t, zw.Close()) - _, err = OpenReader(buf) - assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") // Test open spreadsheet with unzip size limit. _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100}) @@ -466,29 +471,16 @@ func TestGetCellHyperLink(t *testing.T) { func TestSetSheetBackground(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } - - err = f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")) - if !assert.NoError(t, err) { - t.FailNow() - } - - err = f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")) - if !assert.NoError(t, err) { - t.FailNow() - } - + assert.NoError(t, err) + assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))) + assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackground.xlsx"))) assert.NoError(t, f.Close()) } func TestSetSheetBackgroundErrors(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) err = f.SetSheetBackground("Sheet2", filepath.Join("test", "not_exists", "not_exists.png")) if assert.Error(t, err) { @@ -497,7 +489,16 @@ func TestSetSheetBackgroundErrors(t *testing.T) { err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx")) assert.EqualError(t, err, ErrImgExt.Error()) + // Test set sheet background on not exist worksheet. + err = f.SetSheetBackground("SheetN", filepath.Join("test", "images", "background.jpg")) + assert.EqualError(t, err, "sheet SheetN does not exist") assert.NoError(t, f.Close()) + + // Test set sheet background with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8") } // TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function @@ -1027,12 +1028,6 @@ func TestGetSheetComments(t *testing.T) { assert.Equal(t, "", f.getSheetComments("sheet0")) } -func TestSetSheetVisible(t *testing.T) { - f := NewFile() - f.WorkBook.Sheets.Sheet[0].Name = "SheetN" - assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist") -} - func TestGetActiveSheetIndex(t *testing.T) { f := NewFile() f.WorkBook.BookViews = nil @@ -1334,6 +1329,10 @@ func TestAddVBAProject(t *testing.T) { // Test add VBA project twice. assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) + // Test add VBs with unsupported charset workbook relationships. + f.Relationships.Delete(defaultXMLPathWorkbookRels) + f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")), "XML syntax error on line 1: invalid UTF-8") } func TestContentTypesReader(t *testing.T) { @@ -1341,7 +1340,8 @@ func TestContentTypesReader(t *testing.T) { f := NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - f.contentTypesReader() + _, err := f.contentTypesReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestWorkbookReader(t *testing.T) { @@ -1349,7 +1349,8 @@ func TestWorkbookReader(t *testing.T) { f := NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - f.workbookReader() + _, err := f.workbookReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestWorkSheetReader(t *testing.T) { @@ -1373,19 +1374,28 @@ func TestWorkSheetReader(t *testing.T) { func TestRelsReader(t *testing.T) { // Test unsupported charset. f := NewFile() - rels := "xl/_rels/workbook.xml.rels" + rels := defaultXMLPathWorkbookRels f.Relationships.Store(rels, nil) f.Pkg.Store(rels, MacintoshCyrillicCharset) - f.relsReader(rels) + _, err := f.relsReader(rels) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestDeleteSheetFromWorkbookRels(t *testing.T) { f := NewFile() - rels := "xl/_rels/workbook.xml.rels" + rels := defaultXMLPathWorkbookRels f.Relationships.Store(rels, nil) assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "") } +func TestUpdateLinkedValue(t *testing.T) { + f := NewFile() + // Test update lined value with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") +} + func TestAttrValToInt(t *testing.T) { _, err := attrValToInt("r", []xml.Attr{ {Name: xml.Name{Local: "r"}, Value: "s"}, diff --git a/file.go b/file.go index 31eaa3d..30ae506 100644 --- a/file.go +++ b/file.go @@ -30,7 +30,7 @@ func NewFile() *File { 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)) - f.Pkg.Store("xl/_rels/workbook.xml.rels", []byte(xml.Header+templateWorkbookRels)) + f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels)) f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet)) f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles)) @@ -39,18 +39,19 @@ func NewFile() *File { f.SheetCount = 1 f.CalcChain, _ = f.calcChainReader() f.Comments = make(map[string]*xlsxComments) - f.ContentTypes = f.contentTypesReader() + f.ContentTypes, _ = f.contentTypesReader() f.Drawings = sync.Map{} f.Styles, _ = f.stylesReader() f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing) - f.WorkBook = f.workbookReader() + f.WorkBook, _ = f.workbookReader() f.Relationships = sync.Map{} - f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels")) + rels, _ := f.relsReader(defaultXMLPathWorkbookRels) + f.Relationships.Store(defaultXMLPathWorkbookRels, rels) f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" ws, _ := f.workSheetReader("Sheet1") f.Sheet.Store("xl/worksheets/sheet1.xml", ws) - f.Theme = f.themeReader() + f.Theme, _ = f.themeReader() return f } @@ -119,7 +120,9 @@ func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) { if !ok { return 0, ErrWorkbookFileFormat } - f.setContentTypePartProjectExtensions(contentType) + if err := f.setContentTypePartProjectExtensions(contentType); err != nil { + return 0, err + } } if f.options != nil && f.options.Password != "" { buf, err := f.WriteToBuffer() diff --git a/file_test.go b/file_test.go index 83a9b78..4272a7b 100644 --- a/file_test.go +++ b/file_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "os" + "path/filepath" "strings" "sync" "testing" @@ -79,6 +80,14 @@ func TestWriteTo(t *testing.T) { _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) } + // Test write with unsupported charset content types. + { + f, buf := NewFile(), bytes.Buffer{} + f.ContentTypes, f.Path = nil, filepath.Join("test", "TestWriteTo.xlsx") + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + _, err := f.WriteTo(bufio.NewWriter(&buf)) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + } } func TestClose(t *testing.T) { diff --git a/picture.go b/picture.go index 6cf1104..4e8a652 100644 --- a/picture.go +++ b/picture.go @@ -187,7 +187,9 @@ func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, fi if err != nil { return err } - f.addContentTypePart(drawingID, "drawings") + if err = f.addContentTypePart(drawingID, "drawings"); err != nil { + return err + } f.addSheetNameSpace(sheet, SourceRelationship) return err } @@ -201,7 +203,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{} } @@ -235,11 +237,15 @@ func (f *File) addSheetDrawing(sheet string, rID int) { // addSheetPicture provides a function to add picture element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. -func (f *File) addSheetPicture(sheet string, rID int) { - ws, _ := f.workSheetReader(sheet) +func (f *File) addSheetPicture(sheet string, rID int) error { + ws, err := f.workSheetReader(sheet) + if err != nil { + return err + } ws.Picture = &xlsxPicture{ RID: "rId" + strconv.Itoa(rID), } + return err } // countDrawings provides a function to get drawing files count storage in the @@ -378,12 +384,15 @@ func (f *File) addMedia(file []byte, ext string) string { // setContentTypePartImageExtensions provides a function to set the content // type for relationship parts and the Main Document part. -func (f *File) setContentTypePartImageExtensions() { +func (f *File) setContentTypePartImageExtensions() error { imageTypes := map[string]string{ "jpeg": "image/", "png": "image/", "gif": "image/", "svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-", } - content := f.contentTypesReader() + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() for _, file := range content.Defaults { @@ -395,13 +404,17 @@ func (f *File) setContentTypePartImageExtensions() { ContentType: prefix + extension, }) } + return err } // setContentTypePartVMLExtensions provides a function to set the content type // for relationship parts and the Main Document part. -func (f *File) setContentTypePartVMLExtensions() { - vml := false - content := f.contentTypesReader() +func (f *File) setContentTypePartVMLExtensions() error { + var vml bool + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() for _, v := range content.Defaults { @@ -415,12 +428,13 @@ func (f *File) setContentTypePartVMLExtensions() { ContentType: ContentTypeVML, }) } + return err } // addContentTypePart provides a function to add content type part // relationships in the file [Content_Types].xml by given index. -func (f *File) addContentTypePart(index int, contentType string) { - setContentType := map[string]func(){ +func (f *File) addContentTypePart(index int, contentType string) error { + setContentType := map[string]func() error{ "comments": f.setContentTypePartVMLExtensions, "drawings": f.setContentTypePartImageExtensions, } @@ -446,20 +460,26 @@ func (f *File) addContentTypePart(index int, contentType string) { } s, ok := setContentType[contentType] if ok { - s() + if err := s(); err != nil { + return err + } + } + content, err := f.contentTypesReader() + if err != nil { + return err } - content := f.contentTypesReader() content.Lock() defer content.Unlock() for _, v := range content.Overrides { if v.PartName == partNames[contentType] { - return + return err } } content.Overrides = append(content.Overrides, xlsxOverride{ PartName: partNames[contentType], ContentType: contentTypes[contentType], }) + return err } // getSheetRelationshipsTargetByID provides a function to get Target attribute @@ -471,7 +491,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{} } @@ -630,7 +650,7 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD // 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 { + if drawingRels, _ := f.relsReader(rels); drawingRels != nil { drawingRels.Lock() defer drawingRels.Unlock() for _, v := range drawingRels.Relationships { diff --git a/picture_test.go b/picture_test.go index 65abf9e..11196c6 100644 --- a/picture_test.go +++ b/picture_test.go @@ -67,6 +67,12 @@ func TestAddPicture(t *testing.T) { // Test write file to given path. assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx"))) assert.NoError(t, f.Close()) + + // Test add picture with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file), "XML syntax error on line 1: invalid UTF-8") } func TestAddPictureErrors(t *testing.T) { @@ -236,3 +242,27 @@ func TestDrawingResize(t *testing.T) { 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()) } + +func TestSetContentTypePartImageExtensions(t *testing.T) { + f := NewFile() + // Test set content type part image extensions with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.setContentTypePartImageExtensions(), "XML syntax error on line 1: invalid UTF-8") +} + +func TestSetContentTypePartVMLExtensions(t *testing.T) { + f := NewFile() + // Test set content type part VML extensions with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.setContentTypePartVMLExtensions(), "XML syntax error on line 1: invalid UTF-8") +} + +func TestAddContentTypePart(t *testing.T) { + f := NewFile() + // Test add content type part with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.addContentTypePart(0, "unknown"), "XML syntax error on line 1: invalid UTF-8") +} diff --git a/pivotTable.go b/pivotTable.go index 0999a97..7b4b553 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -160,10 +160,10 @@ func (f *File) AddPivotTable(opts *PivotTableOptions) error { } pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels" f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") - f.addContentTypePart(pivotTableID, "pivotTable") - f.addContentTypePart(pivotCacheID, "pivotCache") - - return nil + if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil { + return err + } + return f.addContentTypePart(pivotCacheID, "pivotCache") } // parseFormatPivotTableSet provides a function to validate pivot table @@ -697,7 +697,7 @@ 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() + wb, _ := f.workbookReader() if wb.PivotCaches == nil { wb.PivotCaches = &xlsxPivotCaches{} } diff --git a/pivotTable_test.go b/pivotTable_test.go index 5d2e537..fc9e090 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -259,6 +259,15 @@ func TestAddPivotTable(t *testing.T) { // Test get pivot fields index with empty data range _, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOptions{}) assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) + // Test add pivot table with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{ + DataRange: "Sheet1!$A$1:$E$31", + PivotTableRange: "Sheet1!$G$2:$M$34", + Rows: []PivotTableField{{Data: "Year"}}, + }), "XML syntax error on line 1: invalid UTF-8") } func TestAddPivotRowFields(t *testing.T) { diff --git a/rows.go b/rows.go index 34a227f..9f1ac73 100644 --- a/rows.go +++ b/rows.go @@ -435,8 +435,13 @@ func (f *File) sharedStringsReader() (*xlsxSST, error) { f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i } } - f.addContentTypePart(0, "sharedStrings") - rels := f.relsReader(relPath) + if err = f.addContentTypePart(0, "sharedStrings"); err != nil { + return f.SharedStrings, err + } + 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, nil diff --git a/rows_test.go b/rows_test.go index 5317c22..2e49c28 100644 --- a/rows_test.go +++ b/rows_test.go @@ -235,9 +235,20 @@ func TestSharedStringsReader(t *testing.T) { f := NewFile() // Test read shared string with unsupported charset. f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) - f.sharedStringsReader() - si := xlsxSI{} - assert.EqualValues(t, "", si.String()) + _, err := f.sharedStringsReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test read shared strings with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + _, err = f.sharedStringsReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test read shared strings with unsupported charset workbook relationships. + f = NewFile() + f.Relationships.Delete(defaultXMLPathWorkbookRels) + f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset) + _, err = f.sharedStringsReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } func TestRowVisibility(t *testing.T) { diff --git a/shape.go b/shape.go index 6f7c8fd..2022ee6 100644 --- a/shape.go +++ b/shape.go @@ -308,8 +308,7 @@ func (f *File) AddShape(sheet, cell, opts string) error { if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil { return err } - f.addContentTypePart(drawingID, "drawings") - return err + return f.addContentTypePart(drawingID, "drawings") } // addDrawingShape provides a function to add preset geometry by given sheet, diff --git a/shape_test.go b/shape_test.go index 9d1da8a..2b2e87c 100644 --- a/shape_test.go +++ b/shape_test.go @@ -87,10 +87,15 @@ func TestAddShape(t *testing.T) { } }`)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) - // Test set row style with unsupported charset style sheet. + // Test add shape with unsupported charset style sheet. f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") + // Test add shape with unsupported charset content types. + f = NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") } func TestAddDrawingShape(t *testing.T) { diff --git a/sheet.go b/sheet.go index 0616d95..b9de81c 100644 --- a/sheet.go +++ b/sheet.go @@ -17,7 +17,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "os" "path" "path/filepath" @@ -47,7 +46,7 @@ func (f *File) NewSheet(sheet string) int { } f.DeleteSheet(sheet) f.SheetCount++ - wb := f.workbookReader() + wb, _ := f.workbookReader() sheetID := 0 for _, v := range wb.Sheets.Sheet { if v.SheetID > sheetID { @@ -56,7 +55,7 @@ func (f *File) NewSheet(sheet string) int { } sheetID++ // Update [Content_Types].xml - f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet) + _ = f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet) // Create new sheet /xl/worksheets/sheet%d.xml f.setSheet(sheetID, sheet) // Update workbook.xml.rels @@ -68,19 +67,17 @@ func (f *File) NewSheet(sheet string) int { // contentTypesReader provides a function to get the pointer to the // [Content_Types].xml structure after deserialization. -func (f *File) contentTypesReader() *xlsxTypes { - var err error - +func (f *File) contentTypesReader() (*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, err } } - return f.ContentTypes + return f.ContentTypes, nil } // contentTypesWriter provides a function to save [Content_Types].xml after @@ -215,14 +212,18 @@ func trimCell(column []xlsxC) []xlsxC { // setContentTypes provides a function to read and update property of contents // type of the spreadsheet. -func (f *File) setContentTypes(partName, contentType string) { - content := f.contentTypesReader() +func (f *File) setContentTypes(partName, contentType string) error { + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() content.Overrides = append(content.Overrides, xlsxOverride{ PartName: partName, ContentType: contentType, }) + return err } // setSheet provides a function to update sheet property by given index. @@ -271,7 +272,7 @@ func (f *File) SetActiveSheet(index int) { if index < 0 { index = 0 } - wb := f.workbookReader() + wb, _ := f.workbookReader() for activeTab := range wb.Sheets.Sheet { if activeTab == index { if wb.BookViews == nil { @@ -316,7 +317,7 @@ func (f *File) SetActiveSheet(index int) { // spreadsheet. If not found the active sheet will be return integer 0. func (f *File) GetActiveSheetIndex() (index int) { sheetID := f.getActiveSheetID() - wb := f.workbookReader() + wb, _ := f.workbookReader() if wb != nil { for idx, sheet := range wb.Sheets.Sheet { if sheet.SheetID == sheetID { @@ -331,7 +332,7 @@ func (f *File) GetActiveSheetIndex() (index int) { // getActiveSheetID provides a function to get active sheet ID of the // spreadsheet. If not found the active sheet will be return integer 0. func (f *File) getActiveSheetID() int { - wb := f.workbookReader() + wb, _ := f.workbookReader() if wb != nil { if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 { activeTab := wb.BookViews.WorkBookView[0].ActiveTab @@ -357,10 +358,10 @@ func (f *File) SetSheetName(source, target string) { if strings.EqualFold(target, source) { return } - content := f.workbookReader() - for k, v := range content.Sheets.Sheet { + wb, _ := f.workbookReader() + for k, v := range wb.Sheets.Sheet { if v.Name == source { - content.Sheets.Sheet[k].Name = target + wb.Sheets.Sheet[k].Name = target f.sheetMap[target] = f.sheetMap[source] delete(f.sheetMap, source) } @@ -422,7 +423,7 @@ func (f *File) GetSheetIndex(sheet string) int { // fmt.Println(index, name) // } func (f *File) GetSheetMap() map[int]string { - wb := f.workbookReader() + wb, _ := f.workbookReader() sheetMap := map[int]string{} if wb != nil { for _, sheet := range wb.Sheets.Sheet { @@ -435,7 +436,7 @@ func (f *File) GetSheetMap() map[int]string { // GetSheetList provides a function to get worksheets, chart sheets, and // dialog sheets name list of the workbook. func (f *File) GetSheetList() (list []string) { - wb := f.workbookReader() + wb, _ := f.workbookReader() if wb != nil { for _, sheet := range wb.Sheets.Sheet { list = append(list, sheet.Name) @@ -448,8 +449,10 @@ func (f *File) GetSheetList() (list []string) { // of the spreadsheet. func (f *File) getSheetMap() map[string]string { maps := map[string]string{} - for _, v := range f.workbookReader().Sheets.Sheet { - for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships { + wb, _ := f.workbookReader() + rels, _ := f.relsReader(f.getWorkbookRelsPath()) + for _, v := range wb.Sheets.Sheet { + for _, rel := range rels.Relationships { if rel.ID == v.ID { sheetXMLPath := f.getWorksheetPath(rel.Target) if _, ok := f.Pkg.Load(sheetXMLPath); ok { @@ -498,10 +501,11 @@ func (f *File) SetSheetBackground(sheet, picture string) error { sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") - f.addSheetPicture(sheet, rID) + if err = f.addSheetPicture(sheet, rID); err != nil { + return err + } f.addSheetNameSpace(sheet, SourceRelationship) - f.setContentTypePartImageExtensions() - return err + return f.setContentTypePartImageExtensions() } // DeleteSheet provides a function to delete worksheet in a workbook by given @@ -514,8 +518,8 @@ func (f *File) DeleteSheet(sheet string) { return } sheetName := trimSheetName(sheet) - wb := f.workbookReader() - wbRels := f.relsReader(f.getWorkbookRelsPath()) + wb, _ := f.workbookReader() + wbRels, _ := f.relsReader(f.getWorkbookRelsPath()) activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) deleteLocalSheetID := f.GetSheetIndex(sheet) deleteAndAdjustDefinedNames(wb, deleteLocalSheetID) @@ -537,8 +541,8 @@ func (f *File) DeleteSheet(sheet string) { } } target := f.deleteSheetFromWorkbookRels(v.ID) - f.deleteSheetFromContentTypes(target) - f.deleteCalcChain(f.getSheetID(sheet), "") + _ = f.deleteSheetFromContentTypes(target) + _ = f.deleteCalcChain(f.getSheetID(sheet), "") delete(f.sheetMap, v.Name) f.Pkg.Delete(sheetXML) f.Pkg.Delete(rels) @@ -573,12 +577,12 @@ 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()) - content.Lock() - defer content.Unlock() - for k, v := range content.Relationships { + rels, _ := f.relsReader(f.getWorkbookRelsPath()) + rels.Lock() + defer rels.Unlock() + for k, v := range rels.Relationships { if v.ID == rID { - content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...) + rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...) return v.Target } } @@ -587,11 +591,14 @@ func (f *File) deleteSheetFromWorkbookRels(rID string) string { // deleteSheetFromContentTypes provides a function to remove worksheet // relationships by given target name in the file [Content_Types].xml. -func (f *File) deleteSheetFromContentTypes(target string) { +func (f *File) deleteSheetFromContentTypes(target string) error { if !strings.HasPrefix(target, "/") { target = "/xl/" + target } - content := f.contentTypesReader() + content, err := f.contentTypesReader() + if err != nil { + return err + } content.Lock() defer content.Unlock() for k, v := range content.Overrides { @@ -599,6 +606,7 @@ func (f *File) deleteSheetFromContentTypes(target string) { content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) } } + return err } // CopySheet provides a function to duplicate a worksheet by gave source and @@ -659,22 +667,25 @@ func (f *File) copySheet(from, to int) error { // err := f.SetSheetVisible("Sheet1", false) func (f *File) SetSheetVisible(sheet string, visible bool) error { sheet = trimSheetName(sheet) - content := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } if visible { - for k, v := range content.Sheets.Sheet { + for k, v := range wb.Sheets.Sheet { if strings.EqualFold(v.Name, sheet) { - content.Sheets.Sheet[k].State = "" + wb.Sheets.Sheet[k].State = "" } } - return nil + return err } count := 0 - for _, v := range content.Sheets.Sheet { + for _, v := range wb.Sheets.Sheet { if v.State != "hidden" { count++ } } - for k, v := range content.Sheets.Sheet { + for k, v := range wb.Sheets.Sheet { ws, err := f.workSheetReader(v.Name) if err != nil { return err @@ -684,10 +695,10 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error { tabSelected = ws.SheetViews.SheetView[0].TabSelected } if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected { - content.Sheets.Sheet[k].State = "hidden" + wb.Sheets.Sheet[k].State = "hidden" } } - return nil + return err } // parsePanesOptions provides a function to parse the panes settings. @@ -830,10 +841,11 @@ func (f *File) SetPanes(sheet, panes string) error { // // f.GetSheetVisible("Sheet1") func (f *File) GetSheetVisible(sheet string) bool { - content, name, visible := f.workbookReader(), trimSheetName(sheet), false - for k, v := range content.Sheets.Sheet { + name, visible := trimSheetName(sheet), false + wb, _ := f.workbookReader() + for k, v := range wb.Sheets.Sheet { if strings.EqualFold(v.Name, name) { - if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" { + if wb.Sheets.Sheet[k].State == "" || wb.Sheets.Sheet[k].State == "visible" { visible = true } } @@ -1460,7 +1472,10 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) { // Scope: "Sheet2", // }) func (f *File) SetDefinedName(definedName *DefinedName) error { - wb := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } d := xlsxDefinedName{ Name: definedName.Name, Comment: definedName.Comment, @@ -1499,7 +1514,10 @@ func (f *File) SetDefinedName(definedName *DefinedName) error { // Scope: "Sheet2", // }) func (f *File) DeleteDefinedName(definedName *DefinedName) error { - wb := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } if wb.DefinedNames != nil { for idx, dn := range wb.DefinedNames.DefinedName { scope := "Workbook" @@ -1512,7 +1530,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error { } if scope == deleteScope && dn.Name == definedName.Name { wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) - return nil + return err } } } @@ -1523,7 +1541,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error { // or worksheet. func (f *File) GetDefinedName() []DefinedName { var definedNames []DefinedName - wb := f.workbookReader() + wb, _ := f.workbookReader() if wb.DefinedNames != nil { for _, dn := range wb.DefinedNames.DefinedName { definedName := DefinedName{ @@ -1715,23 +1733,22 @@ func (f *File) RemovePageBreak(sheet, cell string) 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, 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 4b9d31e..08c7c1a 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -188,6 +188,17 @@ func TestDefinedName(t *testing.T) { assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo) assert.Exactly(t, 1, len(f.GetDefinedName())) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx"))) + // Test set defined name with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetDefinedName(&DefinedName{ + Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5", + }), "XML syntax error on line 1: invalid UTF-8") + // Test delete defined name with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.DeleteDefinedName(&DefinedName{Name: "Amount"}), + "XML syntax error on line 1: invalid UTF-8") } func TestGroupSheets(t *testing.T) { @@ -367,6 +378,32 @@ func TestGetSheetID(t *testing.T) { assert.NotEqual(t, -1, id) } +func TestSetSheetVisible(t *testing.T) { + f := NewFile() + f.WorkBook.Sheets.Sheet[0].Name = "SheetN" + assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist") + // Test set sheet visible with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "XML syntax error on line 1: invalid UTF-8") +} + +func TestSetContentTypes(t *testing.T) { + f := NewFile() + // Test set content type with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8") +} + +func TestDeleteSheetFromContentTypes(t *testing.T) { + f := NewFile() + // Test delete sheet from content types with unsupported charset content types. + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") +} + func BenchmarkNewSheet(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { diff --git a/stream.go b/stream.go index 766e83a..d348eba 100644 --- a/stream.go +++ b/stream.go @@ -226,11 +226,12 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error { sw.tableParts = fmt.Sprintf(``, rID) - sw.File.addContentTypePart(tableID, "table") - + if err = sw.File.addContentTypePart(tableID, "table"); err != nil { + return err + } b, _ := xml.Marshal(table) sw.File.saveFileList(tableXML, b) - return nil + return err } // Extract values from a row in the StreamWriter. @@ -471,6 +472,23 @@ func setCellFormula(c *xlsxC, formula string) { } } +// setCellTime provides a function to set number of a cell with a time. +func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error { + var date1904, isNum bool + wb, err := sw.File.workbookReader() + if err != nil { + return err + } + if wb != nil && wb.WorkbookPr != nil { + date1904 = wb.WorkbookPr.Date1904 + } + if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 { + style, _ := sw.File.NewStyle(&Style{NumFmt: 22}) + c.S = style + } + return nil +} + // setCellValFunc provides a function to set value of a cell. func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error { var err error @@ -488,15 +506,7 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error { case time.Duration: c.T, c.V = setCellDuration(val) case time.Time: - var isNum bool - date1904, wb := false, sw.File.workbookReader() - if wb != nil && wb.WorkbookPr != nil { - date1904 = wb.WorkbookPr.Date1904 - } - if isNum, err = c.setCellTime(val, date1904); isNum && c.S == 0 { - style, _ := sw.File.NewStyle(&Style{NumFmt: 22}) - c.S = style - } + err = sw.setCellTime(c, val) case bool: c.T, c.V = setCellBool(val) case nil: diff --git a/stream_test.go b/stream_test.go index 65af283..dca06aa 100644 --- a/stream_test.go +++ b/stream_test.go @@ -186,7 +186,7 @@ func TestStreamTable(t *testing.T) { } // Write a table. - assert.NoError(t, streamWriter.AddTable("A1", "C2", ``)) + assert.NoError(t, streamWriter.AddTable("A1", "C2", "")) assert.NoError(t, streamWriter.Flush()) // Verify the table has names. @@ -198,13 +198,17 @@ func TestStreamTable(t *testing.T) { assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) - assert.NoError(t, streamWriter.AddTable("A1", "C1", ``)) + assert.NoError(t, streamWriter.AddTable("A1", "C1", "")) // Test add table with illegal options. assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string") // Test add table with illegal cell reference. assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) + // Test add table with unsupported charset content types. + file.ContentTypes = nil + file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, streamWriter.AddTable("A1", "C2", ""), "XML syntax error on line 1: invalid UTF-8") } func TestStreamMergeCells(t *testing.T) { @@ -242,7 +246,7 @@ func TestStreamMarshalAttrs(t *testing.T) { } func TestStreamSetRow(t *testing.T) { - // Test error exceptions + // Test error exceptions. file := NewFile() defer func() { assert.NoError(t, file.Close()) @@ -250,9 +254,13 @@ func TestStreamSetRow(t *testing.T) { streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - // Test set row with non-ascending row number + // Test set row with non-ascending row number. assert.NoError(t, streamWriter.SetRow("A1", []interface{}{})) assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error()) + // Test set row with unsupported charset workbook. + file.WorkBook = nil + file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8") } func TestStreamSetRowNilValues(t *testing.T) { diff --git a/styles.go b/styles.go index 08d6b0c..79bd6d3 100644 --- a/styles.go +++ b/styles.go @@ -17,7 +17,6 @@ import ( "encoding/xml" "fmt" "io" - "log" "math" "reflect" "strconv" @@ -3357,16 +3356,16 @@ func getPaletteColor(color string) string { // themeReader provides a function to get the pointer to the xl/theme/theme1.xml // structure after deserialization. -func (f *File) themeReader() *xlsxTheme { +func (f *File) themeReader() (*xlsxTheme, error) { if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok { - return nil + return nil, nil } theme := xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value} if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))). Decode(&theme); err != nil && err != io.EOF { - log.Printf("xml decoder error: %s", err) + return &theme, err } - return &theme + return &theme, nil } // ThemeColor applied the color with tint value. diff --git a/styles_test.go b/styles_test.go index 605ad07..9001d5b 100644 --- a/styles_test.go +++ b/styles_test.go @@ -366,7 +366,9 @@ func TestThemeReader(t *testing.T) { f := NewFile() // Test read theme with unsupported charset. f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset) - assert.EqualValues(t, &xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}, f.themeReader()) + theme, err := f.themeReader() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + assert.EqualValues(t, &xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}, theme) } func TestSetCellStyle(t *testing.T) { diff --git a/table.go b/table.go index 867af9e..06336ff 100644 --- a/table.go +++ b/table.go @@ -94,8 +94,7 @@ func (f *File) AddTable(sheet, hCell, vCell, opts string) error { if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, options); err != nil { return err } - f.addContentTypePart(tableID, "table") - return err + return f.addContentTypePart(tableID, "table") } // countTables provides a function to get table files count storage in the @@ -301,7 +300,10 @@ func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error { cellStart, _ := CoordinatesToCellName(hCol, hRow, true) cellEnd, _ := CoordinatesToCellName(vCol, vRow, true) ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase" - wb := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } sheetID := f.GetSheetIndex(sheet) filterRange := fmt.Sprintf("'%s'!%s", sheet, ref) d := xlsxDefinedName{ diff --git a/table_test.go b/table_test.go index 409b49f..5ac464b 100644 --- a/table_test.go +++ b/table_test.go @@ -78,9 +78,13 @@ func TestAutoFilter(t *testing.T) { }) } - // Test AutoFilter with illegal cell reference. + // Test add auto filter with illegal cell reference. assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) + // Test add auto filter with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.AutoFilter("Sheet1", "D4", "B1", formats[0]), "XML syntax error on line 1: invalid UTF-8") } func TestAutoFilterError(t *testing.T) { diff --git a/templates.go b/templates.go index c8233c1..2e0c051 100644 --- a/templates.go +++ b/templates.go @@ -23,6 +23,7 @@ const ( defaultXMLPathStyles = "xl/styles.xml" defaultXMLPathTheme = "xl/theme/theme1.xml" defaultXMLPathWorkbook = "xl/workbook.xml" + defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels" defaultTempFileSST = "sharedStrings" ) diff --git a/workbook.go b/workbook.go index 937c9ca..eb57cb5 100644 --- a/workbook.go +++ b/workbook.go @@ -15,7 +15,6 @@ import ( "bytes" "encoding/xml" "io" - "log" "path/filepath" "strconv" "strings" @@ -23,7 +22,10 @@ import ( // SetWorkbookProps provides a function to sets workbook properties. func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error { - wb := f.workbookReader() + wb, err := f.workbookReader() + if err != nil { + return err + } if wb.WorkbookPr == nil { wb.WorkbookPr = new(xlsxWorkbookPr) } @@ -44,20 +46,24 @@ func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error { // GetWorkbookProps provides a function to gets workbook properties. func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) { - wb, opts := f.workbookReader(), WorkbookPropsOptions{} + var opts WorkbookPropsOptions + wb, err := f.workbookReader() + if err != nil { + return opts, err + } if wb.WorkbookPr != nil { opts.Date1904 = boolPtr(wb.WorkbookPr.Date1904) opts.FilterPrivacy = boolPtr(wb.WorkbookPr.FilterPrivacy) opts.CodeName = stringPtr(wb.WorkbookPr.CodeName) } - return opts, nil + return opts, err } // setWorkbook update workbook property of the spreadsheet. Maximum 31 // characters are allowed in sheet title. func (f *File) setWorkbook(name string, sheetID, rid int) { - content := f.workbookReader() - content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{ + wb, _ := f.workbookReader() + wb.Sheets.Sheet = append(wb.Sheets.Sheet, xlsxSheet{ Name: trimSheetName(name), SheetID: sheetID, ID: "rId" + strconv.Itoa(rid), @@ -67,7 +73,7 @@ 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 { + if rels, _ := f.relsReader("_rels/.rels"); rels != nil { rels.Lock() defer rels.Unlock() for _, rel := range rels.Relationships { @@ -95,7 +101,7 @@ func (f *File) getWorkbookRelsPath() (path string) { // workbookReader provides a function to get the pointer to the workbook.xml // structure after deserialization. -func (f *File) workbookReader() *xlsxWorkbook { +func (f *File) workbookReader() (*xlsxWorkbook, error) { var err error if f.WorkBook == nil { wbPath := f.getWorkbookPath() @@ -107,10 +113,10 @@ func (f *File) workbookReader() *xlsxWorkbook { } 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) + return f.WorkBook, err } } - return f.WorkBook + return f.WorkBook, err } // workBookWriter provides a function to save workbook.xml after serialize diff --git a/workbook_test.go b/workbook_test.go index 29571fa..a3b2b52 100644 --- a/workbook_test.go +++ b/workbook_test.go @@ -9,7 +9,8 @@ import ( func TestWorkbookProps(t *testing.T) { f := NewFile() assert.NoError(t, f.SetWorkbookProps(nil)) - wb := f.workbookReader() + wb, err := f.workbookReader() + assert.NoError(t, err) wb.WorkbookPr = nil expected := WorkbookPropsOptions{ Date1904: boolPtr(true), @@ -20,4 +21,13 @@ func TestWorkbookProps(t *testing.T) { opts, err := f.GetWorkbookProps() assert.NoError(t, err) assert.Equal(t, expected, opts) + // Test set workbook properties with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8") + // Test get workbook properties with unsupported charset workbook. + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + _, err = f.GetWorkbookProps() + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") }