From ae2865d9237cfd27d7bc4fbef3870b3361597be8 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 22 Dec 2019 00:02:09 +0800 Subject: [PATCH] Improve code coverage unit tests --- calcchain_test.go | 19 ++++++ cell_test.go | 9 ++- comment_test.go | 56 +++++++++++++++++ datavalidation_test.go | 9 +++ docProps_test.go | 13 ++++ excelize_test.go | 133 ++++++++++++++++++++++++++++++++--------- picture.go | 39 ++++++++---- picture_test.go | 42 +++++++------ rows_test.go | 78 +++++++++++++++++------- sheet.go | 28 ++++++--- sheet_test.go | 20 +++++++ sparkline.go | 103 ++++++++++++++++--------------- sparkline_test.go | 9 +++ stream_test.go | 18 +++++- xmlWorkbook.go | 2 +- xmlWorksheet.go | 2 +- 16 files changed, 438 insertions(+), 142 deletions(-) create mode 100644 calcchain_test.go create mode 100644 comment_test.go diff --git a/calcchain_test.go b/calcchain_test.go new file mode 100644 index 0000000..842dde1 --- /dev/null +++ b/calcchain_test.go @@ -0,0 +1,19 @@ +package excelize + +import "testing" + +func TestCalcChainReader(t *testing.T) { + f := NewFile() + f.CalcChain = nil + f.XLSX["xl/calcChain.xml"] = MacintoshCyrillicCharset + f.calcChainReader() +} + +func TestDeleteCalcChain(t *testing.T) { + f := NewFile() + f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{}} + f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{ + PartName: "/xl/calcChain.xml", + }) + f.deleteCalcChain(1, "A1") +} diff --git a/cell_test.go b/cell_test.go index b030622..7d3339f 100644 --- a/cell_test.go +++ b/cell_test.go @@ -95,8 +95,15 @@ func TestSetCellBool(t *testing.T) { } func TestGetCellFormula(t *testing.T) { + // Test get cell formula on not exist worksheet. f := NewFile() - f.GetCellFormula("Sheet", "A1") + _, err := f.GetCellFormula("SheetN", "A1") + assert.EqualError(t, err, "sheet SheetN is not exist") + + // Test get cell formula on no formula cell. + f.SetCellValue("Sheet1", "A1", true) + _, err = f.GetCellFormula("Sheet1", "A1") + assert.NoError(t, err) } func ExampleFile_SetCellFloat() { diff --git a/comment_test.go b/comment_test.go new file mode 100644 index 0000000..dd07951 --- /dev/null +++ b/comment_test.go @@ -0,0 +1,56 @@ +// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to +// and read from XLSX files. Support reads and writes XLSX file generated by +// Microsoft Excelâ„¢ 2007 and later. Support save file without losing original +// charts of XLSX. This library needs Go version 1.10 or later. + +package excelize + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddComments(t *testing.T) { + f, err := prepareTestBook1() + if !assert.NoError(t, err) { + t.FailNow() + } + + s := strings.Repeat("c", 32768) + assert.NoError(t, f.AddComment("Sheet1", "A30", `{"author":"`+s+`","text":"`+s+`"}`)) + assert.NoError(t, f.AddComment("Sheet2", "B7", `{"author":"Excelize: ","text":"This is a comment."}`)) + + // Test add comment on not exists worksheet. + assert.EqualError(t, f.AddComment("SheetN", "B7", `{"author":"Excelize: ","text":"This is a comment."}`), "sheet SheetN is not exist") + + if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) { + assert.Len(t, f.GetComments(), 2) + } +} + +func TestDecodeVMLDrawingReader(t *testing.T) { + f := NewFile() + path := "xl/drawings/vmlDrawing1.xml" + f.XLSX[path] = MacintoshCyrillicCharset + f.decodeVMLDrawingReader(path) +} + +func TestCommentsReader(t *testing.T) { + f := NewFile() + path := "xl/comments1.xml" + f.XLSX[path] = MacintoshCyrillicCharset + f.commentsReader(path) +} + +func TestCountComments(t *testing.T) { + f := NewFile() + f.Comments["xl/comments1.xml"] = nil + assert.Equal(t, f.countComments(), 1) +} diff --git a/datavalidation_test.go b/datavalidation_test.go index 211830d..763bad1 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -11,6 +11,7 @@ package excelize import ( "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -85,4 +86,12 @@ func TestDataValidationError(t *testing.T) { if !assert.NoError(t, f.SaveAs(resultFile)) { t.FailNow() } + + // Test width invalid data validation formula. + dvRange.Formula1 = strings.Repeat("s", dataValidationFormulaStrLen+22) + assert.EqualError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan), "data validation must be 0-255 characters") + + // Test add data validation on no exists worksheet. + f = NewFile() + assert.EqualError(t, f.AddDataValidation("SheetN", nil), "sheet SheetN is not exist") } diff --git a/docProps_test.go b/docProps_test.go index df3122b..30c3149 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" ) +var MacintoshCyrillicCharset = []byte{0x8F, 0xF0, 0xE8, 0xE2, 0xE5, 0xF2, 0x20, 0xEC, 0xE8, 0xF0} + func TestSetDocProps(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { @@ -40,6 +42,11 @@ func TestSetDocProps(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx"))) f.XLSX["docProps/core.xml"] = nil assert.NoError(t, f.SetDocProps(&DocProperties{})) + + // Test unsupport charset + f = NewFile() + f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset + assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8") } func TestGetDocProps(t *testing.T) { @@ -53,4 +60,10 @@ func TestGetDocProps(t *testing.T) { f.XLSX["docProps/core.xml"] = nil _, err = f.GetDocProps() assert.NoError(t, err) + + // Test unsupport charset + f = NewFile() + f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset + _, err = f.GetDocProps() + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") } diff --git a/excelize_test.go b/excelize_test.go index 95d63fd..6929a4f 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -2,6 +2,8 @@ package excelize import ( "bytes" + "compress/gzip" + "encoding/xml" "fmt" "image/color" _ "image/gif" @@ -184,6 +186,11 @@ func TestSaveAsWrongPath(t *testing.T) { } } +func TestCharsetTranscoder(t *testing.T) { + f := NewFile() + f.CharsetTranscoder(*new(charsetTranscoderFn)) +} + func TestOpenReader(t *testing.T) { _, err := OpenReader(strings.NewReader("")) assert.EqualError(t, err, "zip: not a valid zip file") @@ -195,6 +202,18 @@ func TestOpenReader(t *testing.T) { 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, })) assert.EqualError(t, err, "not support encrypted file currently") + + // Test unexpected EOF. + var b bytes.Buffer + w := gzip.NewWriter(&b) + defer w.Close() + w.Flush() + + r, _ := gzip.NewReader(&b) + defer r.Close() + + _, err = OpenReader(r) + assert.EqualError(t, err, "unexpected EOF") } func TestBrokenFile(t *testing.T) { @@ -924,24 +943,6 @@ func TestAddShape(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) } -func TestAddComments(t *testing.T) { - f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } - - s := strings.Repeat("c", 32768) - assert.NoError(t, f.AddComment("Sheet1", "A30", `{"author":"`+s+`","text":"`+s+`"}`)) - assert.NoError(t, f.AddComment("Sheet2", "B7", `{"author":"Excelize: ","text":"This is a comment."}`)) - - // Test add comment on not exists worksheet. - assert.EqualError(t, f.AddComment("SheetN", "B7", `{"author":"Excelize: ","text":"This is a comment."}`), "sheet SheetN is not exist") - - if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) { - assert.Len(t, f.GetComments(), 2) - } -} - func TestGetSheetComments(t *testing.T) { f := NewFile() assert.Equal(t, "", f.getSheetComments(0)) @@ -1005,18 +1006,37 @@ func TestAutoFilterError(t *testing.T) { } } -func TestSetPane(t *testing.T) { +func TestSetActiveSheet(t *testing.T) { + f := NewFile() + f.WorkBook.BookViews = nil + f.SetActiveSheet(1) + f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}} + f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}} + f.SetActiveSheet(1) +} + +func TestSetSheetVisible(t *testing.T) { + f := NewFile() + 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.WorkBook.BookViews = nil + assert.Equal(t, 1, f.GetActiveSheetIndex()) +} + +func TestRelsWriter(t *testing.T) { + f := NewFile() + f.Relationships["xl/worksheets/sheet/rels/sheet1.xml.rel"] = &xlsxRelationships{} + f.relsWriter() +} + +func TestGetSheetView(t *testing.T) { f := NewFile() - f.SetPanes("Sheet1", `{"freeze":false,"split":false}`) - f.NewSheet("Panes 2") - 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"}]}`) - f.NewSheet("Panes 3") - f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`) - f.NewSheet("Panes 4") - f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`) - f.SetPanes("Panes 4", "") - - assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx"))) + _, err := f.getSheetView("SheetN", 0) + assert.EqualError(t, err, "sheet SheetN is not exist") } func TestConditionalFormat(t *testing.T) { @@ -1207,6 +1227,61 @@ func TestAddVBAProject(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) } +func TestContentTypesReader(t *testing.T) { + // Test unsupport charset. + f := NewFile() + f.ContentTypes = nil + f.XLSX["[Content_Types].xml"] = MacintoshCyrillicCharset + f.contentTypesReader() +} + +func TestWorkbookReader(t *testing.T) { + // Test unsupport charset. + f := NewFile() + f.WorkBook = nil + f.XLSX["xl/workbook.xml"] = MacintoshCyrillicCharset + f.workbookReader() +} + +func TestWorkSheetReader(t *testing.T) { + // Test unsupport charset. + f := NewFile() + delete(f.Sheet, "xl/worksheets/sheet1.xml") + f.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset + _, err := f.workSheetReader("Sheet1") + assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") + + // Test on no checked worksheet. + f = NewFile() + delete(f.Sheet, "xl/worksheets/sheet1.xml") + f.XLSX["xl/worksheets/sheet1.xml"] = []byte(``) + f.checked = nil + _, err = f.workSheetReader("Sheet1") + assert.NoError(t, err) +} + +func TestRelsReader(t *testing.T) { + // Test unsupport charset. + f := NewFile() + rels := "xl/_rels/workbook.xml.rels" + f.Relationships[rels] = nil + f.XLSX[rels] = MacintoshCyrillicCharset + f.relsReader(rels) +} + +func TestDeleteSheetFromWorkbookRels(t *testing.T) { + f := NewFile() + rels := "xl/_rels/workbook.xml.rels" + f.Relationships[rels] = nil + assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "") +} + +func TestAttrValToInt(t *testing.T) { + _, err := attrValToInt("r", []xml.Attr{ + {Name: xml.Name{Local: "r"}, Value: "s"}}) + assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`) +} + func prepareTestBook1() (*File, error) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if err != nil { diff --git a/picture.go b/picture.go index 2420350..01df849 100644 --- a/picture.go +++ b/picture.go @@ -477,24 +477,14 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) var ( wsDr *xlsxWsDr ok bool - anchor *xdrCellAnchor deWsDr *decodeWsDr drawRel *xlsxRelationship deTwoCellAnchor *decodeTwoCellAnchor ) wsDr, _ = f.drawingParser(drawingXML) - for _, anchor = range wsDr.TwoCellAnchor { - if anchor.From != nil && anchor.Pic != nil { - if anchor.From.Col == col && anchor.From.Row == row { - drawRel = f.getDrawingRelationships(drawingRelationships, - anchor.Pic.BlipFill.Blip.Embed) - if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { - ret, buf = filepath.Base(drawRel.Target), []byte(f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]) - return - } - } - } + if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 { + return } deWsDr = new(decodeWsDr) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))). @@ -514,13 +504,36 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { - ret, buf = filepath.Base(drawRel.Target), []byte(f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)]) + ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)] return } } } } + return +} +// 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) { + var ( + ok bool + anchor *xdrCellAnchor + drawRel *xlsxRelationship + ) + for _, anchor = range wsDr.TwoCellAnchor { + if anchor.From != nil && anchor.Pic != nil { + if anchor.From.Col == col && anchor.From.Row == row { + drawRel = f.getDrawingRelationships(drawingRelationships, + anchor.Pic.BlipFill.Blip.Embed) + if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { + ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)] + return + } + } + } + } return } diff --git a/picture_test.go b/picture_test.go index 9a2edda..6af3904 100644 --- a/picture_test.go +++ b/picture_test.go @@ -92,12 +92,12 @@ func TestAddPictureErrors(t *testing.T) { } func TestGetPicture(t *testing.T) { - xlsx, err := prepareTestBook1() + f, err := prepareTestBook1() if !assert.NoError(t, err) { t.FailNow() } - file, raw, err := xlsx.GetPicture("Sheet1", "F21") + file, raw, err := f.GetPicture("Sheet1", "F21") assert.NoError(t, err) if !assert.NotEmpty(t, filepath.Join("test", file)) || !assert.NotEmpty(t, raw) || !assert.NoError(t, ioutil.WriteFile(filepath.Join("test", file), raw, 0644)) { @@ -106,37 +106,37 @@ func TestGetPicture(t *testing.T) { } // Try to get picture from a worksheet with illegal cell coordinates. - _, _, err = xlsx.GetPicture("Sheet1", "A") + _, _, err = f.GetPicture("Sheet1", "A") assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) // Try to get picture from a worksheet that doesn't contain any images. - file, raw, err = xlsx.GetPicture("Sheet3", "I9") + file, raw, err = f.GetPicture("Sheet3", "I9") assert.EqualError(t, err, "sheet Sheet3 is not exist") assert.Empty(t, file) assert.Empty(t, raw) // Try to get picture from a cell that doesn't contain an image. - file, raw, err = xlsx.GetPicture("Sheet2", "A2") + file, raw, err = f.GetPicture("Sheet2", "A2") assert.NoError(t, err) assert.Empty(t, file) assert.Empty(t, raw) - xlsx.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8") - xlsx.getDrawingRelationships("", "") - xlsx.getSheetRelationshipsTargetByID("", "") - xlsx.deleteSheetRelationships("", "") + f.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8") + f.getDrawingRelationships("", "") + f.getSheetRelationshipsTargetByID("", "") + f.deleteSheetRelationships("", "") // Try to get picture from a local storage file. - if !assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) { + if !assert.NoError(t, f.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) { t.FailNow() } - xlsx, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) + f, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) if !assert.NoError(t, err) { t.FailNow() } - file, raw, err = xlsx.GetPicture("Sheet1", "F21") + file, raw, err = f.GetPicture("Sheet1", "F21") assert.NoError(t, err) if !assert.NotEmpty(t, filepath.Join("test", file)) || !assert.NotEmpty(t, raw) || !assert.NoError(t, ioutil.WriteFile(filepath.Join("test", file), raw, 0644)) { @@ -145,7 +145,14 @@ func TestGetPicture(t *testing.T) { } // Try to get picture from a local storage file that doesn't contain an image. - file, raw, err = xlsx.GetPicture("Sheet1", "F22") + file, raw, err = f.GetPicture("Sheet1", "F22") + assert.NoError(t, err) + assert.Empty(t, file) + assert.Empty(t, raw) + + // Test get picture from none drawing worksheet. + f = NewFile() + file, raw, err = f.GetPicture("Sheet1", "F22") assert.NoError(t, err) assert.Empty(t, file) assert.Empty(t, raw) @@ -160,11 +167,9 @@ func TestAddDrawingPicture(t *testing.T) { func TestAddPictureFromBytes(t *testing.T) { f := NewFile() imgFile, err := ioutil.ReadFile("logo.png") - if err != nil { - t.Error("Unable to load logo for test") - } - f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile) - f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile) + assert.NoError(t, err, "Unable to load logo for test") + assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile)) + assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile)) imageCount := 0 for fileName := range f.XLSX { if strings.Contains(fileName, "media/image") { @@ -172,4 +177,5 @@ func TestAddPictureFromBytes(t *testing.T) { } } assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.") + assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN is not exist") } diff --git a/rows_test.go b/rows_test.go index 6b50c75..6494242 100644 --- a/rows_test.go +++ b/rows_test.go @@ -1,6 +1,7 @@ package excelize import ( + "bytes" "fmt" "path/filepath" "testing" @@ -12,12 +13,12 @@ import ( func TestRows(t *testing.T) { const sheet2 = "Sheet2" - xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx")) + f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) if !assert.NoError(t, err) { t.FailNow() } - rows, err := xlsx.Rows(sheet2) + rows, err := f.Rows(sheet2) if !assert.NoError(t, err) { t.FailNow() } @@ -32,7 +33,7 @@ func TestRows(t *testing.T) { t.FailNow() } - returnedRows, err := xlsx.GetRows(sheet2) + returnedRows, err := f.GetRows(sheet2) assert.NoError(t, err) for i := range returnedRows { returnedRows[i] = trimSliceSpace(returnedRows[i]) @@ -40,6 +41,11 @@ func TestRows(t *testing.T) { if !assert.Equal(t, collectedRows, returnedRows) { t.FailNow() } + + f = NewFile() + f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`1B`) + _, err = f.Rows("Sheet1") + assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) } func TestRowsIterator(t *testing.T) { @@ -126,6 +132,35 @@ func TestRowHeight(t *testing.T) { convertColWidthToPixels(0) } +func TestColumns(t *testing.T) { + f := NewFile() + rows, err := f.Rows("Sheet1") + assert.NoError(t, err) + rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1B`))) + _, err = rows.Columns() + assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) + + rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1B`))) + _, err = rows.Columns() + assert.NoError(t, err) + + rows.curRow = 3 + rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`1`))) + _, err = rows.Columns() + assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) + + // Test token is nil + rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil)) + _, err = rows.Columns() + assert.NoError(t, err) +} + +func TestSharedStringsReader(t *testing.T) { + f := NewFile() + f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset + f.sharedStringsReader() +} + func TestRowVisibility(t *testing.T) { f, err := prepareTestBook1() if !assert.NoError(t, err) { @@ -149,61 +184,64 @@ func TestRowVisibility(t *testing.T) { } func TestRemoveRow(t *testing.T) { - xlsx := NewFile() - sheet1 := xlsx.GetSheetName(1) - r, err := xlsx.workSheetReader(sheet1) + f := NewFile() + sheet1 := f.GetSheetName(1) + r, err := f.workSheetReader(sheet1) assert.NoError(t, err) const ( colCount = 10 rowCount = 10 ) - fillCells(xlsx, sheet1, colCount, rowCount) + fillCells(f, sheet1, colCount, rowCount) - xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External") + f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External") - assert.EqualError(t, xlsx.RemoveRow(sheet1, -1), "invalid row number -1") + assert.EqualError(t, f.RemoveRow(sheet1, -1), "invalid row number -1") - assert.EqualError(t, xlsx.RemoveRow(sheet1, 0), "invalid row number 0") + assert.EqualError(t, f.RemoveRow(sheet1, 0), "invalid row number 0") - assert.NoError(t, xlsx.RemoveRow(sheet1, 4)) + assert.NoError(t, f.RemoveRow(sheet1, 4)) if !assert.Len(t, r.SheetData.Row, rowCount-1) { t.FailNow() } - xlsx.MergeCell(sheet1, "B3", "B5") + f.MergeCell(sheet1, "B3", "B5") - assert.NoError(t, xlsx.RemoveRow(sheet1, 2)) + assert.NoError(t, f.RemoveRow(sheet1, 2)) if !assert.Len(t, r.SheetData.Row, rowCount-2) { t.FailNow() } - assert.NoError(t, xlsx.RemoveRow(sheet1, 4)) + assert.NoError(t, f.RemoveRow(sheet1, 4)) if !assert.Len(t, r.SheetData.Row, rowCount-3) { t.FailNow() } - err = xlsx.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`) + err = f.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`) if !assert.NoError(t, err) { t.FailNow() } - assert.NoError(t, xlsx.RemoveRow(sheet1, 1)) + assert.NoError(t, f.RemoveRow(sheet1, 1)) if !assert.Len(t, r.SheetData.Row, rowCount-4) { t.FailNow() } - assert.NoError(t, xlsx.RemoveRow(sheet1, 2)) + assert.NoError(t, f.RemoveRow(sheet1, 2)) if !assert.Len(t, r.SheetData.Row, rowCount-5) { t.FailNow() } - assert.NoError(t, xlsx.RemoveRow(sheet1, 1)) + assert.NoError(t, f.RemoveRow(sheet1, 1)) if !assert.Len(t, r.SheetData.Row, rowCount-6) { t.FailNow() } - assert.NoError(t, xlsx.RemoveRow(sheet1, 10)) - assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx"))) + assert.NoError(t, f.RemoveRow(sheet1, 10)) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx"))) + + // Test remove row on not exist worksheet + assert.EqualError(t, f.RemoveRow("SheetN", 1), `sheet SheetN is not exist`) } func TestInsertRow(t *testing.T) { diff --git a/sheet.go b/sheet.go index 6ef7c6e..7412fce 100644 --- a/sheet.go +++ b/sheet.go @@ -505,7 +505,7 @@ func (f *File) copySheet(from, to int) error { // SetSheetVisible provides a function to set worksheet visible by given worksheet // name. A workbook must contain at least one visible worksheet. If the given // worksheet has been activated, this setting will be invalidated. Sheet state -// values as defined by http://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetstatevalues.aspx +// values as defined by https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues // // visible // hidden @@ -738,7 +738,8 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, d = f.sharedStringsReader() decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name))) for { - token, err := decoder.Token() + var token xml.Token + token, err = decoder.Token() if err != nil || token == nil { if err == io.EOF { err = nil @@ -749,13 +750,9 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, case xml.StartElement: inElement = startElement.Name.Local if inElement == "row" { - for _, attr := range startElement.Attr { - if attr.Name.Local == "r" { - row, err = strconv.Atoi(attr.Value) - if err != nil { - return result, err - } - } + row, err = attrValToInt("r", startElement.Attr) + if err != nil { + return } } if inElement == "c" { @@ -785,7 +782,20 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string, default: } } + return +} +// attrValToInt provides a function to convert the local names to an integer +// by given XML attributes and specified names. +func attrValToInt(name string, attrs []xml.Attr) (val int, err error) { + for _, attr := range attrs { + if attr.Name.Local == name { + val, err = strconv.Atoi(attr.Value) + if err != nil { + return + } + } + } return } diff --git a/sheet_test.go b/sheet_test.go index b9e4abf..aada60a 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -75,6 +75,20 @@ func TestNewSheet(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx"))) } +func TestSetPane(t *testing.T) { + f := excelize.NewFile() + 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"}]}`)) + f.NewSheet("Panes 3") + assert.NoError(t, f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`)) + f.NewSheet("Panes 4") + assert.NoError(t, f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`)) + assert.NoError(t, f.SetPanes("Panes 4", "")) + assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN is not exist") + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx"))) +} + func TestPageLayoutOption(t *testing.T) { const sheet = "Sheet1" @@ -156,6 +170,12 @@ func TestSearchSheet(t *testing.T) { result, err = f.SearchSheet("Sheet1", "[0-9]", true) assert.NoError(t, err) assert.EqualValues(t, expected, result) + + // Test search worksheet data after set cell value + f = excelize.NewFile() + assert.NoError(t, f.SetCellValue("Sheet1", "A1", true)) + _, err = f.SearchSheet("Sheet1", "") + assert.NoError(t, err) } func TestSetPageLayout(t *testing.T) { diff --git a/sparkline.go b/sparkline.go index 9ad5087..47c8d5a 100644 --- a/sparkline.go +++ b/sparkline.go @@ -390,21 +390,14 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup { // func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) { var ( - ws *xlsxWorksheet - sparkType string - sparkTypes map[string]string - specifiedSparkTypes string - ok bool - group *xlsxX14SparklineGroup - groups *xlsxX14SparklineGroups - decodeExtLst *decodeWorksheetExt - idx int - ext *xlsxWorksheetExt - decodeSparklineGroups *decodeX14SparklineGroups - sparklineGroupBytes []byte - sparklineGroupsBytes []byte - extLst string - extLstBytes, extBytes []byte + ws *xlsxWorksheet + sparkType string + sparkTypes map[string]string + specifiedSparkTypes string + ok bool + group *xlsxX14SparklineGroup + groups *xlsxX14SparklineGroups + sparklineGroupsBytes, extBytes []byte ) // parameter validation @@ -442,38 +435,9 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) { } f.addSparkline(opt, group) if ws.ExtLst.Ext != "" { // append mode ext - decodeExtLst = new(decodeWorksheetExt) - if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + ws.ExtLst.Ext + ""))). - Decode(decodeExtLst); err != nil && err != io.EOF { + if err = f.appendSparkline(ws, group, groups); err != nil { return } - for idx, ext = range decodeExtLst.Ext { - if ext.URI == ExtURISparklineGroups { - decodeSparklineGroups = new(decodeX14SparklineGroups) - if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))). - Decode(decodeSparklineGroups); err != nil && err != io.EOF { - return - } - if sparklineGroupBytes, err = xml.Marshal(group); err != nil { - return - } - groups = &xlsxX14SparklineGroups{ - XMLNSXM: NameSpaceSpreadSheetExcel2006Main, - Content: decodeSparklineGroups.Content + string(sparklineGroupBytes), - } - if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { - return - } - decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) - } - } - if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil { - return - } - extLst = string(extLstBytes) - ws.ExtLst = &xlsxExtLst{ - Ext: strings.TrimSuffix(strings.TrimPrefix(extLst, ""), ""), - } } else { groups = &xlsxX14SparklineGroups{ XMLNSXM: NameSpaceSpreadSheetExcel2006Main, @@ -482,11 +446,10 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) { if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { return } - ext = &xlsxWorksheetExt{ + if extBytes, err = xml.Marshal(&xlsxWorksheetExt{ URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes), - } - if extBytes, err = xml.Marshal(ext); err != nil { + }); err != nil { return } ws.ExtLst.Ext = string(extBytes) @@ -534,3 +497,47 @@ func (f *File) addSparkline(opt *SparklineOption, group *xlsxX14SparklineGroup) }) } } + +// appendSparkline provides a function to append sparkline to sparkline +// groups. +func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) (err error) { + var ( + idx int + decodeExtLst *decodeWorksheetExt + decodeSparklineGroups *decodeX14SparklineGroups + ext *xlsxWorksheetExt + sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte + ) + decodeExtLst = new(decodeWorksheetExt) + if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + ws.ExtLst.Ext + ""))). + Decode(decodeExtLst); err != nil && err != io.EOF { + return + } + for idx, ext = range decodeExtLst.Ext { + if ext.URI == ExtURISparklineGroups { + decodeSparklineGroups = new(decodeX14SparklineGroups) + if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))). + Decode(decodeSparklineGroups); err != nil && err != io.EOF { + return + } + if sparklineGroupBytes, err = xml.Marshal(group); err != nil { + return + } + groups = &xlsxX14SparklineGroups{ + XMLNSXM: NameSpaceSpreadSheetExcel2006Main, + Content: decodeSparklineGroups.Content + string(sparklineGroupBytes), + } + if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { + return + } + decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) + } + } + if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil { + return + } + ws.ExtLst = &xlsxExtLst{ + Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), ""), + } + return +} diff --git a/sparkline_test.go b/sparkline_test.go index d52929b..a5cb216 100644 --- a/sparkline_test.go +++ b/sparkline_test.go @@ -269,6 +269,15 @@ func TestAddSparkline(t *testing.T) { }), "XML syntax error on line 6: element closed by ") } +func TestAppendSparkline(t *testing.T) { + // Test unsupport charset. + f := NewFile() + ws, err := f.workSheetReader("Sheet1") + assert.NoError(t, err) + ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)} + assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8") +} + func prepareSparklineDataset() *File { f := NewFile() sheet2 := [][]int{ diff --git a/stream_test.go b/stream_test.go index 97c55a7..8371a4e 100644 --- a/stream_test.go +++ b/stream_test.go @@ -37,8 +37,7 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, streamWriter.SetRow(cell, &row)) } - err = streamWriter.Flush() - assert.NoError(t, err) + assert.NoError(t, streamWriter.Flush()) // Save xlsx file by the given path. assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx"))) @@ -54,6 +53,21 @@ func TestFlush(t *testing.T) { assert.NoError(t, err) streamWriter.Sheet = "SheetN" assert.EqualError(t, streamWriter.Flush(), "sheet SheetN is not exist") + + // Test close temporary file error + file = NewFile() + streamWriter, err = file.NewStreamWriter("Sheet1") + assert.NoError(t, err) + for rowID := 10; rowID <= 51200; rowID++ { + row := make([]interface{}, 50) + for colID := 0; colID < 50; colID++ { + row[colID] = rand.Intn(640000) + } + cell, _ := CoordinatesToCellName(1, rowID) + assert.NoError(t, streamWriter.SetRow(cell, &row)) + } + assert.NoError(t, streamWriter.tmpFile.Close()) + assert.Error(t, streamWriter.Flush()) } func TestSetRow(t *testing.T) { diff --git a/xmlWorkbook.go b/xmlWorkbook.go index e9ded6c..65606b0 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -203,7 +203,7 @@ type xlsxDefinedNames struct { // http://schemas.openxmlformats.org/spreadsheetml/2006/main This element // defines a defined name within this workbook. A defined name is descriptive // text that is used to represents a cell, range of cells, formula, or constant -// value. For a descriptions of the attributes see https://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.definedname.aspx +// value. For a descriptions of the attributes see https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname type xlsxDefinedName struct { Comment string `xml:"comment,attr,omitempty"` CustomMenu string `xml:"customMenu,attr,omitempty"` diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 71ff4cc..8f39adf 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -182,7 +182,7 @@ type xlsxSheetViews struct { // last sheetView definition is loaded, and the others are discarded. When // multiple windows are viewing the same sheet, multiple sheetView elements // (with corresponding workbookView entries) are saved. -// See https://msdn.microsoft.com/en-us/library/office/documentformat.openxml.spreadsheet.sheetview.aspx +// See https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetview type xlsxSheetView struct { WindowProtection bool `xml:"windowProtection,attr,omitempty"` ShowFormulas bool `xml:"showFormulas,attr,omitempty"`