diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | README_zh.md | 4 | ||||
-rw-r--r-- | adjust.go | 35 | ||||
-rw-r--r-- | cell.go | 210 | ||||
-rw-r--r-- | cell_test.go | 35 | ||||
-rw-r--r-- | chart.go | 13 | ||||
-rw-r--r-- | col.go | 100 | ||||
-rw-r--r-- | comment.go | 15 | ||||
-rw-r--r-- | date.go | 9 | ||||
-rw-r--r-- | date_test.go | 9 | ||||
-rw-r--r-- | excelize.go | 12 | ||||
-rw-r--r-- | excelize_test.go | 202 | ||||
-rw-r--r-- | lib.go | 53 | ||||
-rw-r--r-- | lib_test.go | 9 | ||||
-rw-r--r-- | picture.go | 32 | ||||
-rw-r--r-- | picture_test.go | 7 | ||||
-rw-r--r-- | rows.go | 157 | ||||
-rw-r--r-- | rows_test.go | 175 | ||||
-rw-r--r-- | shape.go | 13 | ||||
-rw-r--r-- | sheet.go | 31 | ||||
-rw-r--r-- | styles.go | 34 | ||||
-rw-r--r-- | table.go | 70 |
23 files changed, 692 insertions, 540 deletions
@@ -1,5 +1,4 @@ ~$*.xlsx test/Test*.xlsx *.out -test/image3.png *.test
\ No newline at end of file @@ -15,11 +15,6 @@ Excelize is a library written in pure Go and 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.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) and [docs reference](https://xuri.me/excelize/). -**WARNING!** - -From version 1.5.0 all row manipulation methods uses Excel row numbering starting with `1` instead of zero-based numbering -which take place in some methods in eraler versions. - ## Basic Usage ### Installation @@ -123,7 +118,6 @@ func main() { fmt.Println(err) } } - ``` ### Add picture to XLSX file diff --git a/README_zh.md b/README_zh.md index cb0de08..f8ace28 100644 --- a/README_zh.md +++ b/README_zh.md @@ -15,10 +15,6 @@ Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。使用本类库要求使用的 Go 语言为 1.8 或更高版本,完整的 API 使用文档请访问 [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) 或查看 [参考文档](https://xuri.me/excelize/)。 -**重要提示** - -从版本 1.5.0 开始,所有行操作方法都使用从 `1` 开始的 Excel 行编号,早期版本中某些方法中的基于 `0` 的行编号将不再使用。 - ## 快速上手 ### 安装 @@ -30,7 +30,7 @@ const ( // TODO: adjustCalcChain, adjustPageBreaks, adjustComments, // adjustDataValidations, adjustProtectedCells // -func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) { +func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error { xlsx := f.workSheetReader(sheet) if dir == rows { @@ -39,11 +39,16 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) f.adjustColDimensions(xlsx, num, offset) } f.adjustHyperlinks(xlsx, sheet, dir, num, offset) - f.adjustMergeCells(xlsx, dir, num, offset) - f.adjustAutoFilter(xlsx, dir, num, offset) + if err := f.adjustMergeCells(xlsx, dir, num, offset); err != nil { + return err + } + if err := f.adjustAutoFilter(xlsx, dir, num, offset); err != nil { + return err + } checkSheet(xlsx) checkRow(xlsx) + return nil } // adjustColDimensions provides a function to update column dimensions when @@ -127,9 +132,9 @@ func (f *File) adjustHyperlinks(xlsx *xlsxWorksheet, sheet string, dir adjustDir // adjustAutoFilter provides a function to update the auto filter when // inserting or deleting rows or columns. -func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) { +func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error { if xlsx.AutoFilter == nil { - return + return nil } rng := strings.Split(xlsx.AutoFilter.Ref, ":") @@ -138,12 +143,12 @@ func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, o firstCol, firstRow, err := CellNameToCoordinates(firstCell) if err != nil { - panic(err) + return err } lastCol, lastRow, err := CellNameToCoordinates(lastCell) if err != nil { - panic(err) + return err } if (dir == rows && firstRow == num && offset < 0) || (dir == columns && firstCol == num && lastCol == num) { @@ -154,7 +159,7 @@ func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, o rowData.Hidden = false } } - return + return nil } if dir == rows { @@ -171,13 +176,14 @@ func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, dir adjustDirection, num, o } xlsx.AutoFilter.Ref = firstCell + ":" + lastCell + return nil } // adjustMergeCells provides a function to update merged cells when inserting // or deleting rows or columns. -func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) { +func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, offset int) error { if xlsx.MergeCells == nil { - return + return nil } for i, areaData := range xlsx.MergeCells.Cells { @@ -187,12 +193,12 @@ func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, o firstCol, firstRow, err := CellNameToCoordinates(firstCell) if err != nil { - panic(err) + return err } lastCol, lastRow, err := CellNameToCoordinates(lastCell) if err != nil { - panic(err) + return err } adjust := func(v int) int { @@ -224,13 +230,14 @@ func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, dir adjustDirection, num, o } if firstCell, err = CoordinatesToCellName(firstCol, firstRow); err != nil { - panic(err) + return err } if lastCell, err = CoordinatesToCellName(lastCol, lastRow); err != nil { - panic(err) + return err } areaData.Ref = firstCell + ":" + lastCell } + return nil } @@ -34,13 +34,13 @@ const ( // worksheet name and axis in XLSX file. If it is possible to apply a format // to the cell value, it will do so, if not then an error will be returned, // along with the raw value of the cell. -func (f *File) GetCellValue(sheet, axis string) string { - return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) { +func (f *File) GetCellValue(sheet, axis string) (string, error) { + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { val, err := c.getValueFrom(f, f.sharedStringsReader()) if err != nil { - panic(err) // Fail fast to avoid future side effects! + return val, false, err } - return val, true + return val, true, err }) } @@ -68,7 +68,7 @@ func (f *File) GetCellValue(sheet, axis string) string { // // Note that default date format is m/d/yy h:mm of time.Time type value. You can // set numbers format by SetCellStyle() method. -func (f *File) SetCellValue(sheet, axis string, value interface{}) { +func (f *File) SetCellValue(sheet, axis string, value interface{}) error { switch v := value.(type) { case int: f.SetCellInt(sheet, axis, v) @@ -102,9 +102,12 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) { f.SetCellDefault(sheet, axis, strconv.FormatFloat(v.Seconds()/86400.0, 'f', -1, 32)) f.setDefaultTimeStyle(sheet, axis, 21) case time.Time: - vv := timeToExcelTime(v) - if vv > 0 { - f.SetCellDefault(sheet, axis, strconv.FormatFloat(timeToExcelTime(v), 'f', -1, 64)) + excelTime, err := timeToExcelTime(v) + if err != nil { + return err + } + if excelTime > 0 { + f.SetCellDefault(sheet, axis, strconv.FormatFloat(excelTime, 'f', -1, 64)) f.setDefaultTimeStyle(sheet, axis, 22) } else { f.SetCellStr(sheet, axis, v.Format(time.RFC3339Nano)) @@ -116,23 +119,31 @@ func (f *File) SetCellValue(sheet, axis string, value interface{}) { default: f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value)) } + return nil } // SetCellInt provides a function to set int type value of a cell by given // worksheet name, cell coordinates and cell value. -func (f *File) SetCellInt(sheet, axis string, value int) { +func (f *File) SetCellInt(sheet, axis string, value int) error { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } cellData.S = f.prepareCellStyle(xlsx, col, cellData.S) cellData.T = "" cellData.V = strconv.Itoa(value) + return err } // SetCellBool provides a function to set bool type value of a cell by given // worksheet name, cell name and cell value. -func (f *File) SetCellBool(sheet, axis string, value bool) { +func (f *File) SetCellBool(sheet, axis string, value bool) error { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } cellData.S = f.prepareCellStyle(xlsx, col, cellData.S) cellData.T = "b" if value { @@ -140,6 +151,7 @@ func (f *File) SetCellBool(sheet, axis string, value bool) { } else { cellData.V = "0" } + return err } // SetCellFloat sets a floating point value into a cell. The prec parameter @@ -151,20 +163,26 @@ func (f *File) SetCellBool(sheet, axis string, value bool) { // var x float32 = 1.325 // f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) // -func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) { +func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } cellData.S = f.prepareCellStyle(xlsx, col, cellData.S) cellData.T = "" cellData.V = strconv.FormatFloat(value, 'f', prec, bitSize) + return err } // SetCellStr provides a function to set string type value of a cell. Total // number of characters that a cell can contain 32767 characters. -func (f *File) SetCellStr(sheet, axis, value string) { +func (f *File) SetCellStr(sheet, axis, value string) error { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) - + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } // Leading space(s) character detection. if len(value) > 0 && value[0] == 32 { cellData.XMLSpace = xml.Attr{ @@ -176,42 +194,49 @@ func (f *File) SetCellStr(sheet, axis, value string) { cellData.S = f.prepareCellStyle(xlsx, col, cellData.S) cellData.T = "str" cellData.V = value + return err } // SetCellDefault provides a function to set string type value of a cell as // default format without escaping the cell. -func (f *File) SetCellDefault(sheet, axis, value string) { +func (f *File) SetCellDefault(sheet, axis, value string) error { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } cellData.S = f.prepareCellStyle(xlsx, col, cellData.S) cellData.T = "" cellData.V = value + return err } // GetCellFormula provides a function to get formula from cell by given // worksheet name and axis in XLSX file. -func (f *File) GetCellFormula(sheet, axis string) string { - return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) { +func (f *File) GetCellFormula(sheet, axis string) (string, error) { + return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) { if c.F == nil { - return "", false + return "", false, nil } if c.F.T == STCellFormulaTypeShared { - return getSharedForumula(x, c.F.Si), true + return getSharedForumula(x, c.F.Si), true, nil } - return c.F.Content, true + return c.F.Content, true, nil }) } // SetCellFormula provides a function to set cell formula by given string and // worksheet name. -func (f *File) SetCellFormula(sheet, axis, formula string) { +func (f *File) SetCellFormula(sheet, axis, formula string) error { xlsx := f.workSheetReader(sheet) - cellData, _, _ := f.prepareCell(xlsx, sheet, axis) - + cellData, _, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return err + } if formula == "" { cellData.F = nil f.deleteCalcChain(axis) - return + return err } if cellData.F != nil { @@ -219,6 +244,7 @@ func (f *File) SetCellFormula(sheet, axis, formula string) { } else { cellData.F = &xlsxF{Content: formula} } + return err } // GetCellHyperLink provides a function to get cell hyperlink by given @@ -227,28 +253,30 @@ func (f *File) SetCellFormula(sheet, axis, formula string) { // the value of link will be false and the value of the target will be a blank // string. For example get hyperlink of Sheet1!H6: // -// link, target := xlsx.GetCellHyperLink("Sheet1", "H6") +// link, target, err := xlsx.GetCellHyperLink("Sheet1", "H6") // -func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) { +func (f *File) GetCellHyperLink(sheet, axis string) (bool, string, error) { // Check for correct cell name if _, _, err := SplitCellName(axis); err != nil { - panic(err) // Fail fast to avoid possible future side effects + return false, "", err } xlsx := f.workSheetReader(sheet) - axis = f.mergeCellsParser(xlsx, axis) - + axis, err := f.mergeCellsParser(xlsx, axis) + if err != nil { + return false, "", err + } if xlsx.Hyperlinks != nil { for _, link := range xlsx.Hyperlinks.Hyperlink { if link.Ref == axis { if link.RID != "" { - return true, f.getSheetRelationshipsTargetByID(sheet, link.RID) + return true, f.getSheetRelationshipsTargetByID(sheet, link.RID), err } - return true, link.Location + return true, link.Location, err } } } - return false, "" + return false, "", err } // SetCellHyperLink provides a function to set cell hyperlink by given @@ -256,23 +284,26 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) { // hyperlink "External" for web site or "Location" for moving to one of cell // in this workbook. The below is example for external link. // -// xlsx.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External") +// err := xlsx.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External") // // Set underline and font color style for the cell. -// style, _ := xlsx.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`) -// xlsx.SetCellStyle("Sheet1", "A3", "A3", style) +// style, err := xlsx.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`) +// err = xlsx.SetCellStyle("Sheet1", "A3", "A3", style) // // A this is another example for "Location": // -// xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location") +// err := xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location") // -func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) { +func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error { // Check for correct cell name if _, _, err := SplitCellName(axis); err != nil { - panic(err) // Fail fast to avoid possible future side effects + return err } xlsx := f.workSheetReader(sheet) - axis = f.mergeCellsParser(xlsx, axis) + axis, err := f.mergeCellsParser(xlsx, axis) + if err != nil { + return err + } var linkData xlsxHyperlink @@ -289,35 +320,36 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) { Location: link, } default: - panic(fmt.Errorf("invalid link type %q", linkType)) + return fmt.Errorf("invalid link type %q", linkType) } if xlsx.Hyperlinks == nil { xlsx.Hyperlinks = new(xlsxHyperlinks) } xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData) + return nil } // MergeCell provides a function to merge cells by given coordinate area and // sheet name. For example create a merged cell of D3:E9 on Sheet1: // -// xlsx.MergeCell("Sheet1", "D3", "E9") +// err := xlsx.MergeCell("Sheet1", "D3", "E9") // // If you create a merged cell that overlaps with another existing merged cell, // those merged cells that already exist will be removed. -func (f *File) MergeCell(sheet, hcell, vcell string) { +func (f *File) MergeCell(sheet, hcell, vcell string) error { hcol, hrow, err := CellNameToCoordinates(hcell) if err != nil { - panic(err) + return err } vcol, vrow, err := CellNameToCoordinates(vcell) if err != nil { - panic(err) + return err } if hcol == vcol && hrow == vrow { - return + return err } if vcol < hcol { @@ -340,11 +372,13 @@ func (f *File) MergeCell(sheet, hcell, vcell string) { for _, cellData := range xlsx.MergeCells.Cells { cc := strings.Split(cellData.Ref, ":") if len(cc) != 2 { - panic(fmt.Errorf("invalid area %q", cellData.Ref)) + return fmt.Errorf("invalid area %q", cellData.Ref) } - - if !checkCellInArea(hcell, cellData.Ref) && !checkCellInArea(vcell, cellData.Ref) && - !checkCellInArea(cc[0], ref) && !checkCellInArea(cc[1], ref) { + c1, _ := checkCellInArea(hcell, cellData.Ref) + c2, _ := checkCellInArea(vcell, cellData.Ref) + c3, _ := checkCellInArea(cc[0], ref) + c4, _ := checkCellInArea(cc[1], ref) + if !c1 && !c2 && !c3 && !c4 { cells = append(cells, cellData) } } @@ -353,24 +387,25 @@ func (f *File) MergeCell(sheet, hcell, vcell string) { } else { xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}} } + return err } // SetSheetRow writes an array to row by given worksheet name, starting // coordinate and a pointer to array type 'slice'. For example, writes an // array to row 6 start with the cell B6 on Sheet1: // -// xlsx.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2}) +// err := xlsx.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2}) // -func (f *File) SetSheetRow(sheet, axis string, slice interface{}) { +func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error { col, row, err := CellNameToCoordinates(axis) if err != nil { - panic(err) // Fail fast to avoid future side effects! + return err } // Make sure 'slice' is a Ptr to Slice v := reflect.ValueOf(slice) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice { - panic(errors.New("pointer to slice expected")) // Fail fast to avoid future side effects! + return errors.New("pointer to slice expected") } v = v.Elem() @@ -379,35 +414,42 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) { // Error should never happens here. But keep ckecking to early detect regresions // if it will be introduced in furure if err != nil { - panic(err) // Fail fast to avoid future side effects! + return err } f.SetCellValue(sheet, cell, v.Index(i).Interface()) } + return err } // getCellInfo does common preparation for all SetCell* methods. -func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int) { - cell = f.mergeCellsParser(xlsx, cell) - +func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) { + var err error + cell, err = f.mergeCellsParser(xlsx, cell) + if err != nil { + return nil, 0, 0, err + } col, row, err := CellNameToCoordinates(cell) if err != nil { - panic(err) // Fail fast and prevent future side effects + return nil, 0, 0, err } prepareSheetXML(xlsx, col, row) - return &xlsx.SheetData.Row[row-1].C[col-1], col, row + return &xlsx.SheetData.Row[row-1].C[col-1], col, row, err } -// getCellStringFunc does common value extraction workflow for all GetCell* methods. -// Passed function implements specific part of required logic. -func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool)) string { +// getCellStringFunc does common value extraction workflow for all GetCell* +// methods. Passed function implements specific part of required logic. +func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c *xlsxC) (string, bool, error)) (string, error) { xlsx := f.workSheetReader(sheet) - axis = f.mergeCellsParser(xlsx, axis) - + var err error + axis, err = f.mergeCellsParser(xlsx, axis) + if err != nil { + return "", err + } _, row, err := CellNameToCoordinates(axis) if err != nil { - panic(err) // Fail fast to avoid future side effects! + return "", err } lastRowNum := 0 @@ -417,7 +459,7 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c // keep in mind: row starts from 1 if row > lastRowNum { - return "" + return "", nil } for rowIdx := range xlsx.SheetData.Row { @@ -430,12 +472,16 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c if axis != colData.R { continue } - if val, ok := fn(xlsx, colData); ok { - return val + val, ok, err := fn(xlsx, colData) + if err != nil { + return "", err + } + if ok { + return val, nil } } } - return "" + return "", nil } // formattedValue provides a function to returns a value after formatted. If @@ -468,35 +514,39 @@ func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int { // mergeCellsParser provides a function to check merged cells in worksheet by // given axis. -func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string { +func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) (string, error) { axis = strings.ToUpper(axis) if xlsx.MergeCells != nil { for i := 0; i < len(xlsx.MergeCells.Cells); i++ { - if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) { + ok, err := checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) + if err != nil { + return axis, err + } + if ok { axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0] } } } - return axis + return axis, nil } // checkCellInArea provides a function to determine if a given coordinate is // within an area. -func checkCellInArea(cell, area string) bool { +func checkCellInArea(cell, area string) (bool, error) { col, row, err := CellNameToCoordinates(cell) if err != nil { - panic(err) + return false, err } rng := strings.Split(area, ":") if len(rng) != 2 { - return false + return false, err } firstCol, firtsRow, _ := CellNameToCoordinates(rng[0]) lastCol, lastRow, _ := CellNameToCoordinates(rng[1]) - return col >= firstCol && col <= lastCol && row >= firtsRow && row <= lastRow + return col >= firstCol && col <= lastCol && row >= firtsRow && row <= lastRow, err } // getSharedForumula find a cell contains the same formula as another cell, diff --git a/cell_test.go b/cell_test.go index 12efc17..7b1381f 100644 --- a/cell_test.go +++ b/cell_test.go @@ -17,8 +17,9 @@ func TestCheckCellInArea(t *testing.T) { for _, expectedTrueCellInArea := range expectedTrueCellInAreaList { cell := expectedTrueCellInArea[0] area := expectedTrueCellInArea[1] - - assert.Truef(t, checkCellInArea(cell, area), + ok, err := checkCellInArea(cell, area) + assert.NoError(t, err) + assert.Truef(t, ok, "Expected cell %v to be in area %v, got false\n", cell, area) } @@ -31,14 +32,15 @@ func TestCheckCellInArea(t *testing.T) { for _, expectedFalseCellInArea := range expectedFalseCellInAreaList { cell := expectedFalseCellInArea[0] area := expectedFalseCellInArea[1] - - assert.Falsef(t, checkCellInArea(cell, area), + ok, err := checkCellInArea(cell, area) + assert.NoError(t, err) + assert.Falsef(t, ok, "Expected cell %v not to be inside of area %v, but got true\n", cell, area) } - assert.Panics(t, func() { - checkCellInArea("AA0", "Z0:AB1") - }) + ok, err := checkCellInArea("AA0", "Z0:AB1") + assert.EqualError(t, err, `cannot convert cell "AA0" to coordinates: invalid cell name "AA0"`) + assert.False(t, ok) } func TestSetCellFloat(t *testing.T) { @@ -47,20 +49,28 @@ func TestSetCellFloat(t *testing.T) { f := NewFile() f.SetCellFloat(sheet, "A1", 123.0, -1, 64) f.SetCellFloat(sheet, "A2", 123.0, 1, 64) - assert.Equal(t, "123", f.GetCellValue(sheet, "A1"), "A1 should be 123") - assert.Equal(t, "123.0", f.GetCellValue(sheet, "A2"), "A2 should be 123.0") + val, err := f.GetCellValue(sheet, "A1") + assert.NoError(t, err) + assert.Equal(t, "123", val, "A1 should be 123") + val, err = f.GetCellValue(sheet, "A2") + assert.NoError(t, err) + assert.Equal(t, "123.0", val, "A2 should be 123.0") }) t.Run("with a decimal and precision limit", func(t *testing.T) { f := NewFile() f.SetCellFloat(sheet, "A1", 123.42, 1, 64) - assert.Equal(t, "123.4", f.GetCellValue(sheet, "A1"), "A1 should be 123.4") + val, err := f.GetCellValue(sheet, "A1") + assert.NoError(t, err) + assert.Equal(t, "123.4", val, "A1 should be 123.4") }) t.Run("with a decimal and no limit", func(t *testing.T) { f := NewFile() f.SetCellFloat(sheet, "A1", 123.42, -1, 64) - assert.Equal(t, "123.42", f.GetCellValue(sheet, "A1"), "A1 should be 123.42") + val, err := f.GetCellValue(sheet, "A1") + assert.NoError(t, err) + assert.Equal(t, "123.42", val, "A1 should be 123.42") }) } @@ -68,6 +78,7 @@ func ExampleFile_SetCellFloat() { f := NewFile() var x = 3.14159265 f.SetCellFloat("Sheet1", "A1", x, 2, 64) - fmt.Println(f.GetCellValue("Sheet1", "A1")) + val, _ := f.GetCellValue("Sheet1", "A1") + fmt.Println(val) // Output: 3.14 } @@ -456,7 +456,10 @@ func (f *File) AddChart(sheet, cell, format string) error { drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML) drawingRID := f.addDrawingRelationships(drawingID, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") - f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format) + err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format) + if err != nil { + return err + } f.addChart(formatSet) f.addContentTypePart(chartID, "chart") f.addContentTypePart(drawingID, "drawings") @@ -1239,8 +1242,11 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) { // addDrawingChart provides a function to add chart graphic frame by given // sheet, drawingXML, cell, width, height, relationship index and format sets. -func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, formatSet *formatPicture) { - col, row := MustCellNameToCoordinates(cell) +func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, formatSet *formatPicture) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } colIdx := col - 1 rowIdx := row - 1 @@ -1290,4 +1296,5 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings[drawingXML] = content + return err } @@ -22,33 +22,39 @@ const ( // worksheet name and column name. For example, get visible state of column D // in Sheet1: // -// xlsx.GetColVisible("Sheet1", "D") +// visiable, err := xlsx.GetColVisible("Sheet1", "D") // -func (f *File) GetColVisible(sheet, col string) bool { - colNum := MustColumnNameToNumber(col) +func (f *File) GetColVisible(sheet, col string) (bool, error) { + visible := true + colNum, err := ColumnNameToNumber(col) + if err != nil { + return visible, err + } xlsx := f.workSheetReader(sheet) if xlsx.Cols == nil { - return true + return visible, err } - visible := true for c := range xlsx.Cols.Col { colData := &xlsx.Cols.Col[c] if colData.Min <= colNum && colNum <= colData.Max { visible = !colData.Hidden } } - return visible + return visible, err } // SetColVisible provides a function to set visible of a single column by given // worksheet name and column name. For example, hide column D in Sheet1: // -// xlsx.SetColVisible("Sheet1", "D", false) +// err := xlsx.SetColVisible("Sheet1", "D", false) // -func (f *File) SetColVisible(sheet, col string, visible bool) { - colNum := MustColumnNameToNumber(col) +func (f *File) SetColVisible(sheet, col string, visible bool) error { + colNum, err := ColumnNameToNumber(col) + if err != nil { + return err + } colData := xlsxCol{ Min: colNum, Max: colNum, @@ -60,7 +66,7 @@ func (f *File) SetColVisible(sheet, col string, visible bool) { cols := xlsxCols{} cols.Col = append(cols.Col, colData) xlsx.Cols = &cols - return + return err } for v := range xlsx.Cols.Col { if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max { @@ -72,20 +78,24 @@ func (f *File) SetColVisible(sheet, col string, visible bool) { colData.Hidden = !visible colData.CustomWidth = true xlsx.Cols.Col = append(xlsx.Cols.Col, colData) + return err } // GetColOutlineLevel provides a function to get outline level of a single // column by given worksheet name and column name. For example, get outline // level of column D in Sheet1: // -// xlsx.GetColOutlineLevel("Sheet1", "D") +// level, err := xlsx.GetColOutlineLevel("Sheet1", "D") // -func (f *File) GetColOutlineLevel(sheet, col string) uint8 { - colNum := MustColumnNameToNumber(col) - xlsx := f.workSheetReader(sheet) +func (f *File) GetColOutlineLevel(sheet, col string) (uint8, error) { level := uint8(0) + colNum, err := ColumnNameToNumber(col) + if err != nil { + return level, err + } + xlsx := f.workSheetReader(sheet) if xlsx.Cols == nil { - return level + return level, err } for c := range xlsx.Cols.Col { colData := &xlsx.Cols.Col[c] @@ -93,17 +103,20 @@ func (f *File) GetColOutlineLevel(sheet, col string) uint8 { level = colData.OutlineLevel } } - return level + return level, err } // SetColOutlineLevel provides a function to set outline level of a single // column by given worksheet name and column name. For example, set outline // level of column D in Sheet1 to 2: // -// xlsx.SetColOutlineLevel("Sheet1", "D", 2) +// err := xlsx.SetColOutlineLevel("Sheet1", "D", 2) // -func (f *File) SetColOutlineLevel(sheet, col string, level uint8) { - colNum := MustColumnNameToNumber(col) +func (f *File) SetColOutlineLevel(sheet, col string, level uint8) error { + colNum, err := ColumnNameToNumber(col) + if err != nil { + return err + } colData := xlsxCol{ Min: colNum, Max: colNum, @@ -115,7 +128,7 @@ func (f *File) SetColOutlineLevel(sheet, col string, level uint8) { cols := xlsxCols{} cols.Col = append(cols.Col, colData) xlsx.Cols = &cols - return + return err } for v := range xlsx.Cols.Col { if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max { @@ -127,21 +140,24 @@ func (f *File) SetColOutlineLevel(sheet, col string, level uint8) { colData.OutlineLevel = level colData.CustomWidth = true xlsx.Cols.Col = append(xlsx.Cols.Col, colData) + return err } // SetColWidth provides a function to set the width of a single column or // multiple columns. For example: // // xlsx := excelize.NewFile() -// xlsx.SetColWidth("Sheet1", "A", "H", 20) -// err := xlsx.Save() -// if err != nil { -// fmt.Println(err) -// } +// err := xlsx.SetColWidth("Sheet1", "A", "H", 20) // -func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) { - min := MustColumnNameToNumber(startcol) - max := MustColumnNameToNumber(endcol) +func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) error { + min, err := ColumnNameToNumber(startcol) + if err != nil { + return err + } + max, err := ColumnNameToNumber(endcol) + if err != nil { + return err + } if min > max { min, max = max, min } @@ -160,6 +176,7 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) { cols.Col = append(cols.Col, col) xlsx.Cols = &cols } + return err } // positionObjectPixels calculate the vertices that define the position of a @@ -289,8 +306,11 @@ func (f *File) getColWidth(sheet string, col int) int { // GetColWidth provides a function to get column width by given worksheet name // and column index. -func (f *File) GetColWidth(sheet, col string) float64 { - colNum := MustColumnNameToNumber(col) +func (f *File) GetColWidth(sheet, col string) (float64, error) { + colNum, err := ColumnNameToNumber(col) + if err != nil { + return defaultColWidthPixels, err + } xlsx := f.workSheetReader(sheet) if xlsx.Cols != nil { var width float64 @@ -300,39 +320,39 @@ func (f *File) GetColWidth(sheet, col string) float64 { } } if width != 0 { - return width + return width, err } } // Optimisation for when the column widths haven't changed. - return defaultColWidthPixels + return defaultColWidthPixels, err } // InsertCol provides a function to insert a new column before given column // index. For example, create a new column before column C in Sheet1: // -// xlsx.InsertCol("Sheet1", "C") +// err := xlsx.InsertCol("Sheet1", "C") // -func (f *File) InsertCol(sheet, col string) { +func (f *File) InsertCol(sheet, col string) error { num, err := ColumnNameToNumber(col) if err != nil { - panic(err) + return err } - f.adjustHelper(sheet, columns, num, 1) + return f.adjustHelper(sheet, columns, num, 1) } // RemoveCol provides a function to remove single column by given worksheet // name and column index. For example, remove column C in Sheet1: // -// xlsx.RemoveCol("Sheet1", "C") +// err := xlsx.RemoveCol("Sheet1", "C") // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. -func (f *File) RemoveCol(sheet, col string) { +func (f *File) RemoveCol(sheet, col string) error { num, err := ColumnNameToNumber(col) if err != nil { - panic(err) // Fail fast to avoid possible future side effects! + return err } xlsx := f.workSheetReader(sheet) @@ -346,7 +366,7 @@ func (f *File) RemoveCol(sheet, col string) { } } } - f.adjustHelper(sheet, columns, num, -1) + return f.adjustHelper(sheet, columns, num, -1) } // convertColWidthToPixels provieds function to convert the width of a cell @@ -72,7 +72,7 @@ func (f *File) getSheetComments(sheetID int) string { // author length is 255 and the max text length is 32512. For example, add a // comment in Sheet1!$A$30: // -// xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`) +// err := xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`) // func (f *File) AddComment(sheet, cell, format string) error { formatSet, err := parseFormatCommentsSet(format) @@ -107,15 +107,21 @@ func (f *File) AddComment(sheet, cell, format string) error { colCount = ll } } - f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount) + err = f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount) + if err != nil { + return err + } f.addContentTypePart(commentID, "comments") return err } // addDrawingVML provides a function to create comment as // xl/drawings/vmlDrawing%d.vml by given commit ID and cell. -func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) { - col, row := MustCellNameToCoordinates(cell) +func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } yAxis := col - 1 xAxis := row - 1 vml := f.VMLDrawing[drawingVML] @@ -206,6 +212,7 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, } vml.Shape = append(vml.Shape, shape) f.VMLDrawing[drawingVML] = vml + return err } // addComment provides a function to create chart as xl/comments%d.xml by @@ -10,6 +10,7 @@ package excelize import ( + "errors" "math" "time" ) @@ -25,18 +26,18 @@ var ( ) // timeToExcelTime provides a function to convert time to Excel time. -func timeToExcelTime(t time.Time) float64 { +func timeToExcelTime(t time.Time) (float64, error) { // TODO in future this should probably also handle date1904 and like TimeFromExcelTime // Force user to explicit convet passed value to UTC time. // Because for example 1900-01-01 00:00:00 +0300 MSK converts to 1900-01-01 00:00:00 +0230 LMT // probably due to daylight saving. if t.Location() != time.UTC { - panic("only UTC time expected") + return 0.0, errors.New("only UTC time expected") } if t.Before(excelMinTime1900) { - return 0.0 + return 0.0, nil } tt := t @@ -60,7 +61,7 @@ func timeToExcelTime(t time.Time) float64 { if t.After(excelBuggyPeriodStart) { result += 1.0 } - return result + return result, nil } // shiftJulianToNoon provides a function to process julian date to noon. diff --git a/date_test.go b/date_test.go index 3ec0b69..63cb19e 100644 --- a/date_test.go +++ b/date_test.go @@ -31,7 +31,9 @@ var trueExpectedDateList = []dateTest{ func TestTimeToExcelTime(t *testing.T) { for i, test := range trueExpectedDateList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { - assert.Equalf(t, test.ExcelValue, timeToExcelTime(test.GoValue), + excelTime, err := timeToExcelTime(test.GoValue) + assert.NoError(t, err) + assert.Equalf(t, test.ExcelValue, excelTime, "Time: %s", test.GoValue.String()) }) } @@ -44,9 +46,8 @@ func TestTimeToExcelTime_Timezone(t *testing.T) { } for i, test := range trueExpectedDateList { t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) { - assert.Panics(t, func() { - timeToExcelTime(test.GoValue.In(location)) - }, "Time: %s", test.GoValue.String()) + _, err := timeToExcelTime(test.GoValue.In(location)) + assert.EqualError(t, err, "only UTC time expected") }) } } diff --git a/excelize.go b/excelize.go index 7a50460..857f3ac 100644 --- a/excelize.go +++ b/excelize.go @@ -98,11 +98,16 @@ func OpenReader(r io.Reader) (*File, error) { // setDefaultTimeStyle provides a function to set default numbers format for // time.Time type cell value by given worksheet name, cell coordinates and // number format code. -func (f *File) setDefaultTimeStyle(sheet, axis string, format int) { - if f.GetCellStyle(sheet, axis) == 0 { +func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error { + s, err := f.GetCellStyle(sheet, axis) + if err != nil { + return err + } + if s == 0 { style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`) f.SetCellStyle(sheet, axis, axis, style) } + return err } // workSheetReader provides a function to get the pointer to the structure @@ -218,7 +223,8 @@ func (f *File) GetMergeCells(sheet string) []MergeCell { for i := range xlsx.MergeCells.Cells { ref := xlsx.MergeCells.Cells[i].Ref axis := strings.Split(ref, ":")[0] - mergeCells = append(mergeCells, []string{ref, f.GetCellValue(sheet, axis)}) + val, _ := f.GetCellValue(sheet, axis) + mergeCells = append(mergeCells, []string{ref, val}) } } diff --git a/excelize_test.go b/excelize_test.go index 9671130..ab5e17b 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -27,7 +27,8 @@ func TestOpenFile(t *testing.T) { // Test get all the rows in a not exists worksheet. xlsx.GetRows("Sheet4") // Test get all the rows in a worksheet. - rows := xlsx.GetRows("Sheet2") + rows, err := xlsx.GetRows("Sheet2") + assert.NoError(t, err) for _, row := range rows { for _, cell := range row { t.Log(cell, "\t") @@ -40,16 +41,13 @@ func TestOpenFile(t *testing.T) { xlsx.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)) // Test set cell value with illegal row number. - assert.Panics(t, func() { - xlsx.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)) - }) + assert.EqualError(t, xlsx.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64)), + `cannot convert cell "A" to coordinates: invalid cell name "A"`) xlsx.SetCellInt("Sheet2", "A1", 100) // Test set cell integer value with illegal row number. - assert.Panics(t, func() { - xlsx.SetCellInt("Sheet2", "A", 100) - }) + assert.EqualError(t, xlsx.SetCellInt("Sheet2", "A", 100), `cannot convert cell "A" to coordinates: invalid cell name "A"`) xlsx.SetCellStr("Sheet2", "C11", "Knowns") // Test max characters in a cell. @@ -62,35 +60,31 @@ func TestOpenFile(t *testing.T) { xlsx.SetCellStr("Sheet10", "b230", "10") // Test set cell string value with illegal row number. - assert.Panics(t, func() { - xlsx.SetCellStr("Sheet10", "A", "10") - }) + assert.EqualError(t, xlsx.SetCellStr("Sheet10", "A", "10"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) xlsx.SetActiveSheet(2) // Test get cell formula with given rows number. - xlsx.GetCellFormula("Sheet1", "B19") + _, err = xlsx.GetCellFormula("Sheet1", "B19") + assert.NoError(t, err) // Test get cell formula with illegal worksheet name. - xlsx.GetCellFormula("Sheet2", "B20") - xlsx.GetCellFormula("Sheet1", "B20") + _, err = xlsx.GetCellFormula("Sheet2", "B20") + assert.NoError(t, err) + _, err = xlsx.GetCellFormula("Sheet1", "B20") + assert.NoError(t, err) // Test get cell formula with illegal rows number. - assert.Panics(t, func() { - xlsx.GetCellFormula("Sheet1", "B") - }) - + _, err = xlsx.GetCellFormula("Sheet1", "B") + assert.EqualError(t, err, `cannot convert cell "B" to coordinates: invalid cell name "B"`) // Test get shared cell formula xlsx.GetCellFormula("Sheet2", "H11") xlsx.GetCellFormula("Sheet2", "I11") getSharedForumula(&xlsxWorksheet{}, "") // Test read cell value with given illegal rows number. - assert.Panics(t, func() { - xlsx.GetCellValue("Sheet2", "a-1") - }) - - assert.Panics(t, func() { - xlsx.GetCellValue("Sheet2", "A") - }) + _, err = xlsx.GetCellValue("Sheet2", "a-1") + assert.EqualError(t, err, `cannot convert cell "A-1" to coordinates: invalid cell name "A-1"`) + _, err = xlsx.GetCellValue("Sheet2", "A") + assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) // Test read cell value with given lowercase column number. xlsx.GetCellValue("Sheet2", "a5") @@ -127,14 +121,14 @@ func TestOpenFile(t *testing.T) { } for _, test := range booltest { xlsx.SetCellValue("Sheet2", "F16", test.value) - assert.Equal(t, test.expected, xlsx.GetCellValue("Sheet2", "F16")) + val, err := xlsx.GetCellValue("Sheet2", "F16") + assert.NoError(t, err) + assert.Equal(t, test.expected, val) } xlsx.SetCellValue("Sheet2", "G2", nil) - assert.Panics(t, func() { - xlsx.SetCellValue("Sheet2", "G4", time.Now()) - }) + assert.EqualError(t, xlsx.SetCellValue("Sheet2", "G4", time.Now()), "only UTC time expected") xlsx.SetCellValue("Sheet2", "G4", time.Now().UTC()) // 02:46:40 @@ -320,19 +314,15 @@ func TestSetCellHyperLink(t *testing.T) { t.Log(err) } // Test set cell hyperlink in a work sheet already have hyperlinks. - xlsx.SetCellHyperLink("Sheet1", "B19", "https://github.com/360EntSecGroup-Skylar/excelize", "External") + assert.NoError(t, xlsx.SetCellHyperLink("Sheet1", "B19", "https://github.com/360EntSecGroup-Skylar/excelize", "External")) // Test add first hyperlink in a work sheet. - xlsx.SetCellHyperLink("Sheet2", "C1", "https://github.com/360EntSecGroup-Skylar/excelize", "External") + assert.NoError(t, xlsx.SetCellHyperLink("Sheet2", "C1", "https://github.com/360EntSecGroup-Skylar/excelize", "External")) // Test add Location hyperlink in a work sheet. - xlsx.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location") + assert.NoError(t, xlsx.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location")) - assert.Panics(t, func() { - xlsx.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", "") - }) + assert.EqualError(t, xlsx.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", ""), `invalid link type ""`) - assert.Panics(t, func() { - xlsx.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location") - }) + assert.EqualError(t, xlsx.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location"), `invalid cell name ""`) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx"))) } @@ -343,15 +333,17 @@ func TestGetCellHyperLink(t *testing.T) { t.FailNow() } - assert.Panics(t, func() { - xlsx.GetCellHyperLink("Sheet1", "") - }) + link, target, err := xlsx.GetCellHyperLink("Sheet1", "") + assert.EqualError(t, err, `invalid cell name ""`) - link, target := xlsx.GetCellHyperLink("Sheet1", "A22") + link, target, err = xlsx.GetCellHyperLink("Sheet1", "A22") + assert.NoError(t, err) t.Log(link, target) - link, target = xlsx.GetCellHyperLink("Sheet2", "D6") + link, target, err = xlsx.GetCellHyperLink("Sheet2", "D6") + assert.NoError(t, err) t.Log(link, target) - link, target = xlsx.GetCellHyperLink("Sheet3", "H3") + link, target, err = xlsx.GetCellHyperLink("Sheet3", "H3") + assert.NoError(t, err) t.Log(link, target) } @@ -365,9 +357,7 @@ func TestSetCellFormula(t *testing.T) { xlsx.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)") // Test set cell formula with illegal rows number. - assert.Panics(t, func() { - xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)") - }) + assert.EqualError(t, xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)"), `cannot convert cell "C" to coordinates: invalid cell name "C"`) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx"))) @@ -496,21 +486,16 @@ func TestSetCellStyleAlignment(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A22", "A22", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A22", "A22", style)) // Test set cell style with given illegal rows number. - assert.Panics(t, func() { - xlsx.SetCellStyle("Sheet1", "A", "A22", style) - }) - - assert.Panics(t, func() { - xlsx.SetCellStyle("Sheet1", "A22", "A", style) - }) + assert.EqualError(t, xlsx.SetCellStyle("Sheet1", "A", "A22", style), `cannot convert cell "A" to coordinates: invalid cell name "A"`) + assert.EqualError(t, xlsx.SetCellStyle("Sheet1", "A22", "A", style), `cannot convert cell "A" to coordinates: invalid cell name "A"`) // Test get cell style with given illegal rows number. - assert.Panics(t, func() { - xlsx.GetCellStyle("Sheet1", "A") - }) + index, err := xlsx.GetCellStyle("Sheet1", "A") + assert.Equal(t, 0, index) + assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleAlignment.xlsx"))) } @@ -528,19 +513,19 @@ func TestSetCellStyleBorder(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "J21", "L25", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "J21", "L25", style)) style, err = xlsx.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":1}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "M28", "K24", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "M28", "K24", style)) style, err = xlsx.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":4}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "M28", "K24", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "M28", "K24", style)) // Test set border and solid style pattern fill for a single cell. style, err = xlsx.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":8},{"type":"top","color":"00FF00","style":9},{"type":"bottom","color":"FFFF00","style":10},{"type":"right","color":"FF0000","style":11},{"type":"diagonalDown","color":"A020F0","style":12},{"type":"diagonalUp","color":"A020F0","style":13}],"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`) @@ -548,7 +533,7 @@ func TestSetCellStyleBorder(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "O22", "O22", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "O22", "O22", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleBorder.xlsx"))) } @@ -596,7 +581,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", c, c, style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", c, c, style)) t.Log(xlsx.GetCellValue("Sheet2", c)) } } @@ -605,7 +590,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "L33", "L33", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "L33", "L33", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx"))) } @@ -625,13 +610,13 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A1", "A1", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = xlsx.NewStyle(`{"number_format": 188, "decimal_places": 31, "negred": true}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A2", "A2", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook3.xlsx"))) }) @@ -654,19 +639,19 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A1", "A1", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = xlsx.NewStyle(`{"number_format": 31, "lang": "ko-kr"}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A2", "A2", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A2", "A2", style)) style, err = xlsx.NewStyle(`{"number_format": 71, "lang": "th-th"}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "A2", "A2", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx"))) }) @@ -680,12 +665,12 @@ func TestSetCellStyleCustomNumberFormat(t *testing.T) { if err != nil { t.Log(err) } - xlsx.SetCellStyle("Sheet1", "A1", "A1", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A1", "A1", style)) style, err = xlsx.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) if err != nil { t.Log(err) } - xlsx.SetCellStyle("Sheet1", "A2", "A2", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx"))) } @@ -702,25 +687,25 @@ func TestSetCellStyleFill(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "O23", "O23", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "O23", "O23", style)) style, err = xlsx.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF"],"shading":1}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "O23", "O23", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "O23", "O23", style)) style, err = xlsx.NewStyle(`{"fill":{"type":"pattern","color":[],"pattern":1}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "O23", "O23", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "O23", "O23", style)) style, err = xlsx.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":19}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet1", "O23", "O23", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet1", "O23", "O23", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx"))) } @@ -737,35 +722,35 @@ func TestSetCellStyleFont(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A1", "A1", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A1", "A1", style)) style, err = xlsx.NewStyle(`{"font":{"italic":true,"underline":"double"}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A2", "A2", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A2", "A2", style)) style, err = xlsx.NewStyle(`{"font":{"bold":true}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A3", "A3", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A3", "A3", style)) style, err = xlsx.NewStyle(`{"font":{"bold":true,"family":"","size":0,"color":"","underline":""}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A4", "A4", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A4", "A4", style)) style, err = xlsx.NewStyle(`{"font":{"color":"#777777"}}`) if !assert.NoError(t, err) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A5", "A5", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A5", "A5", style)) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleFont.xlsx"))) } @@ -782,7 +767,7 @@ func TestSetCellStyleProtection(t *testing.T) { t.FailNow() } - xlsx.SetCellStyle("Sheet2", "A6", "A6", style) + assert.NoError(t, xlsx.SetCellStyle("Sheet2", "A6", "A6", style)) err = xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx")) if !assert.NoError(t, err) { t.FailNow() @@ -818,7 +803,8 @@ func TestGetPicture(t *testing.T) { t.FailNow() } - file, raw := xlsx.GetPicture("Sheet1", "F21") + file, raw, err := xlsx.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)) { @@ -826,12 +812,14 @@ func TestGetPicture(t *testing.T) { } // Try to get picture from a worksheet that doesn't contain any images. - file, raw = xlsx.GetPicture("Sheet3", "I9") + file, raw, err = xlsx.GetPicture("Sheet3", "I9") + assert.NoError(t, err) assert.Empty(t, file) assert.Empty(t, raw) // Try to get picture from a cell that doesn't contain an image. - file, raw = xlsx.GetPicture("Sheet2", "A2") + file, raw, err = xlsx.GetPicture("Sheet2", "A2") + assert.NoError(t, err) assert.Empty(t, file) assert.Empty(t, raw) @@ -850,7 +838,8 @@ func TestGetPicture(t *testing.T) { t.FailNow() } - file, raw = xlsx.GetPicture("Sheet1", "F21") + file, raw, err = xlsx.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)) { @@ -858,7 +847,8 @@ func TestGetPicture(t *testing.T) { } // Try to get picture from a local storage file that doesn't contain an image. - file, raw = xlsx.GetPicture("Sheet1", "F22") + file, raw, err = xlsx.GetPicture("Sheet1", "F22") + assert.NoError(t, err) assert.Empty(t, file) assert.Empty(t, raw) } @@ -913,7 +903,9 @@ func TestCopySheet(t *testing.T) { } xlsx.SetCellValue("Sheet4", "F1", "Hello") - assert.NotEqual(t, "Hello", xlsx.GetCellValue("Sheet1", "F1")) + val, err := xlsx.GetCellValue("Sheet1", "F1") + assert.NoError(t, err) + assert.NotEqual(t, "Hello", val) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestCopySheet.xlsx"))) } @@ -1101,7 +1093,7 @@ func TestInsertCol(t *testing.T) { t.FailNow() } - xlsx.InsertCol(sheet1, "A") + assert.NoError(t, xlsx.InsertCol(sheet1, "A")) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertCol.xlsx"))) } @@ -1118,8 +1110,8 @@ func TestRemoveCol(t *testing.T) { xlsx.MergeCell(sheet1, "A1", "B1") xlsx.MergeCell(sheet1, "A2", "B2") - xlsx.RemoveCol(sheet1, "A") - xlsx.RemoveCol(sheet1, "A") + assert.NoError(t, xlsx.RemoveCol(sheet1, "A")) + assert.NoError(t, xlsx.RemoveCol(sheet1, "A")) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx"))) } @@ -1242,18 +1234,11 @@ func TestSetSheetRow(t *testing.T) { xlsx.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()}) - assert.Panics(t, func() { - xlsx.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}) - }) - - assert.Panics(t, func() { - xlsx.SetSheetRow("Sheet1", "B27", []interface{}{}) - }) - - assert.Panics(t, func() { - xlsx.SetSheetRow("Sheet1", "B27", &xlsx) - }) + assert.EqualError(t, xlsx.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}), + `cannot convert cell "" to coordinates: invalid cell name ""`) + assert.EqualError(t, xlsx.SetSheetRow("Sheet1", "B27", []interface{}{}), `pointer to slice expected`) + assert.EqualError(t, xlsx.SetSheetRow("Sheet1", "B27", &xlsx), `pointer to slice expected`) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx"))) } @@ -1267,16 +1252,15 @@ func TestOutlineLevel(t *testing.T) { xlsx.SetColOutlineLevel("Sheet2", "B", 2) xlsx.SetRowOutlineLevel("Sheet1", 2, 250) - assert.Panics(t, func() { - xlsx.SetRowOutlineLevel("Sheet1", 0, 1) - }) + assert.EqualError(t, xlsx.SetRowOutlineLevel("Sheet1", 0, 1), "invalid row number 0") + level, err := xlsx.GetRowOutlineLevel("Sheet1", 2) + assert.NoError(t, err) + assert.Equal(t, uint8(250), level) - assert.Equal(t, uint8(250), xlsx.GetRowOutlineLevel("Sheet1", 2)) + _, err = xlsx.GetRowOutlineLevel("Sheet1", 0) + assert.EqualError(t, err, `invalid row number 0`) - assert.Panics(t, func() { - xlsx.GetRowOutlineLevel("Sheet1", 0) - }) - err := xlsx.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx")) + err = xlsx.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx")) if !assert.NoError(t, err) { t.FailNow() } @@ -1418,7 +1402,7 @@ func prepareTestBook4() (*File, error) { func fillCells(xlsx *File, sheet string, colCount, rowCount int) { for col := 1; col <= colCount; col++ { for row := 1; row <= rowCount; row++ { - cell := MustCoordinatesToCellName(col, row) + cell, _ := CoordinatesToCellName(col, row) xlsx.SetCellStr(sheet, cell, cell) } } @@ -135,22 +135,6 @@ func ColumnNameToNumber(name string) (int, error) { return col, nil } -// MustColumnNameToNumber provides a function to convert Excel sheet column -// name to int. Column name case insencitive. -// Function returns error if column name incorrect. -// -// Example: -// -// excelize.MustColumnNameToNumber("AK") // returns 37 -// -func MustColumnNameToNumber(name string) int { - n, err := ColumnNameToNumber(name) - if err != nil { - panic(err) - } - return n -} - // ColumnNumberToName provides a function to convert integer // to Excel sheet column title. // @@ -174,8 +158,9 @@ func ColumnNumberToName(num int) (string, error) { // to [X, Y] coordinates or retrusn an error. // // Example: -// CellCoordinates("A1") // returns 1, 1, nil -// CellCoordinates("Z3") // returns 26, 3, nil +// +// CellCoordinates("A1") // returns 1, 1, nil +// CellCoordinates("Z3") // returns 26, 3, nil // func CellNameToCoordinates(cell string) (int, int, error) { const msg = "cannot convert cell %q to coordinates: %v" @@ -193,25 +178,12 @@ func CellNameToCoordinates(cell string) (int, int, error) { return col, row, nil } -// MustCellNameToCoordinates converts alpha-numeric cell name -// to [X, Y] coordinates or panics. +// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell +// name or returns an error. // // Example: -// MustCellNameToCoordinates("A1") // returns 1, 1 -// MustCellNameToCoordinates("Z3") // returns 26, 3 // -func MustCellNameToCoordinates(cell string) (int, int) { - c, r, err := CellNameToCoordinates(cell) - if err != nil { - panic(err) - } - return c, r -} - -// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell name or returns an error. -// -// Example: -// CoordinatesToCellName(1, 1) // returns "A1", nil +// CoordinatesToCellName(1, 1) // returns "A1", nil // func CoordinatesToCellName(col, row int) (string, error) { if col < 1 || row < 1 { @@ -224,19 +196,6 @@ func CoordinatesToCellName(col, row int) (string, error) { return fmt.Sprintf("%s%d", colname, row), nil } -// MustCoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell name or panics. -// -// Example: -// MustCoordinatesToCellName(1, 1) // returns "A1" -// -func MustCoordinatesToCellName(col, row int) string { - n, err := CoordinatesToCellName(col, row) - if err != nil { - panic(err) - } - return n -} - // boolPtr returns a pointer to a bool with the given value. func boolPtr(b bool) *bool { return &b } diff --git a/lib_test.go b/lib_test.go index 4c19f73..1c30c0e 100644 --- a/lib_test.go +++ b/lib_test.go @@ -71,9 +71,6 @@ func TestColumnNameToNumber_Error(t *testing.T) { if assert.Errorf(t, err, msg, col.Name) { assert.Equalf(t, col.Num, out, msg, col.Name) } - assert.Panicsf(t, func() { - MustColumnNameToNumber(col.Name) - }, msg, col.Name) } } @@ -174,9 +171,6 @@ func TestCellNameToCoordinates_Error(t *testing.T) { assert.Equalf(t, -1, c, msg, cell) assert.Equalf(t, -1, r, msg, cell) } - assert.Panicsf(t, func() { - MustCellNameToCoordinates(cell) - }, msg, cell) } } @@ -199,9 +193,6 @@ func TestCoordinatesToCellName_Error(t *testing.T) { if assert.Errorf(t, err, msg, col, row) { assert.Equalf(t, "", cell, msg, col, row) } - assert.Panicsf(t, func() { - MustCoordinatesToCellName(col, row) - }, msg, col, row) } for _, col := range invalidIndexes { @@ -162,7 +162,10 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, } drawingHyperlinkRID = f.addDrawingRelationships(drawingID, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) } - f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet) + err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet) + if err != nil { + return err + } f.addMedia(file, ext) f.addContentTypePart(drawingID, "drawings") return err @@ -270,8 +273,11 @@ func (f *File) countDrawings() int { // addDrawingPicture provides a function to add picture by given sheet, // drawingXML, cell, file name, width, height relationship index and format // sets. -func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, height, rID, hyperlinkRID int, formatSet *formatPicture) { - col, row := MustCellNameToCoordinates(cell) +func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, height, rID, hyperlinkRID int, formatSet *formatPicture) error { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return err + } width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) col-- @@ -315,6 +321,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings[drawingXML] = content + return err } // addDrawingRelationships provides a function to add image part relationships @@ -468,7 +475,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { // fmt.Println(err) // return // } -// file, raw := xlsx.GetPicture("Sheet1", "A2") +// file, raw, err := xlsx.GetPicture("Sheet1", "A2") // if file == "" { // return // } @@ -477,13 +484,16 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { // fmt.Println(err) // } // -func (f *File) GetPicture(sheet, cell string) (string, []byte) { - col, row := MustCellNameToCoordinates(cell) +func (f *File) GetPicture(sheet, cell string) (string, []byte, error) { + col, row, err := CellNameToCoordinates(cell) + if err != nil { + return "", []byte{}, err + } col-- row-- xlsx := f.workSheetReader(sheet) if xlsx.Drawing == nil { - return "", []byte{} + return "", []byte{}, err } target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID) @@ -503,7 +513,7 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte) { if ok { return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, - "..", "xl", -1)]) + "..", "xl", -1)]), err } } } @@ -511,7 +521,7 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte) { _, ok := f.XLSX[drawingXML] if !ok { - return "", nil + return "", nil, err } decodeWsDr := decodeWsDr{} _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(drawingXML)), &decodeWsDr) @@ -523,12 +533,12 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte) { xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed) _, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)] if ok { - return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)]) + return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)]), err } } } } - return "", []byte{} + return "", []byte{}, err } // getDrawingRelationships provides a function to get drawing relationships diff --git a/picture_test.go b/picture_test.go index 97d3cd9..8c8d2e4 100644 --- a/picture_test.go +++ b/picture_test.go @@ -4,17 +4,18 @@ import ( "fmt" _ "image/png" "io/ioutil" + "path/filepath" "testing" ) func BenchmarkAddPictureFromBytes(b *testing.B) { f := NewFile() - imgFile, err := ioutil.ReadFile("logo.png") + imgFile, err := ioutil.ReadFile(filepath.Join("test", "images", "excel.png")) if err != nil { - panic("unable to load image for benchmark") + b.Error("unable to load image for benchmark") } b.ResetTimer() for i := 1; i <= b.N; i++ { - f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "logo", ".png", imgFile) + f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "excel", ".png", imgFile) } } @@ -21,17 +21,18 @@ import ( // GetRows return all the rows in a sheet by given worksheet name (case // sensitive). For example: // -// for _, row := range xlsx.GetRows("Sheet1") { +// rows, err := xlsx.GetRows("Sheet1") +// for _, row := range rows { // for _, colCell := range row { // fmt.Print(colCell, "\t") // } // fmt.Println() // } // -func (f *File) GetRows(sheet string) [][]string { +func (f *File) GetRows(sheet string) ([][]string, error) { name, ok := f.sheetMap[trimSheetName(sheet)] if !ok { - return nil + return nil, nil } xlsx := f.workSheetReader(sheet) @@ -47,7 +48,10 @@ func (f *File) GetRows(sheet string) [][]string { rowData xlsxRow ) - rowCount, colCount := f.getTotalRowsCols(name) + rowCount, colCount, err := f.getTotalRowsCols(name) + if err != nil { + return nil, nil + } rows := make([][]string, rowCount) for i := range rows { rows[i] = make([]string, colCount+1) @@ -68,7 +72,10 @@ func (f *File) GetRows(sheet string) [][]string { _ = decoder.DecodeElement(&rowData, &startElement) cr := rowData.R - 1 for _, colCell := range rowData.C { - col, _ := MustCellNameToCoordinates(colCell.R) + col, _, err := CellNameToCoordinates(colCell.R) + if err != nil { + return nil, err + } val, _ := colCell.getValueFrom(f, d) rows[cr][col-1] = val if val != "" { @@ -79,7 +86,7 @@ func (f *File) GetRows(sheet string) [][]string { default: } } - return rows[:row] + return rows[:row], nil } // Rows defines an iterator to a sheet @@ -117,9 +124,9 @@ func (rows *Rows) Error() error { } // Columns return the current row's column values -func (rows *Rows) Columns() []string { +func (rows *Rows) Columns() ([]string, error) { if rows.token == nil { - return []string{} + return []string{}, nil } startElement := rows.token.(xml.StartElement) r := xlsxRow{} @@ -127,11 +134,14 @@ func (rows *Rows) Columns() []string { d := rows.f.sharedStringsReader() columns := make([]string, len(r.C)) for _, colCell := range r.C { - col, _ := MustCellNameToCoordinates(colCell.R) + col, _, err := CellNameToCoordinates(colCell.R) + if err != nil { + return columns, err + } val, _ := colCell.getValueFrom(rows.f, d) columns[col-1] = val } - return columns + return columns, nil } // ErrSheetNotExist defines an error of sheet is not exist @@ -147,7 +157,8 @@ func (err ErrSheetNotExist) Error() string { // // rows, err := xlsx.Rows("Sheet1") // for rows.Next() { -// for _, colCell := range rows.Columns() { +// row, err := rows.Columns() +// for _, colCell := range row { // fmt.Print(colCell, "\t") // } // fmt.Println() @@ -171,7 +182,7 @@ func (f *File) Rows(sheet string) (*Rows, error) { // getTotalRowsCols provides a function to get total columns and rows in a // worksheet. -func (f *File) getTotalRowsCols(name string) (int, int) { +func (f *File) getTotalRowsCols(name string) (int, int, error) { decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name))) var inElement string var r xlsxRow @@ -189,7 +200,10 @@ func (f *File) getTotalRowsCols(name string) (int, int) { _ = decoder.DecodeElement(&r, &startElement) tr = r.R for _, colCell := range r.C { - col, _ := MustCellNameToCoordinates(colCell.R) + col, _, err := CellNameToCoordinates(colCell.R) + if err != nil { + return tr, tc, err + } if col > tc { tc = col } @@ -198,17 +212,17 @@ func (f *File) getTotalRowsCols(name string) (int, int) { default: } } - return tr, tc + return tr, tc, nil } // SetRowHeight provides a function to set the height of a single row. For // example, set the height of the first row in Sheet1: // -// xlsx.SetRowHeight("Sheet1", 1, 50) +// err := xlsx.SetRowHeight("Sheet1", 1, 50) // -func (f *File) SetRowHeight(sheet string, row int, height float64) { +func (f *File) SetRowHeight(sheet string, row int, height float64) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) @@ -218,6 +232,7 @@ func (f *File) SetRowHeight(sheet string, row int, height float64) { rowIdx := row - 1 xlsx.SheetData.Row[rowIdx].Ht = height xlsx.SheetData.Row[rowIdx].CustomHeight = true + return nil } // getRowHeight provides a function to get row height in pixels by given sheet @@ -236,24 +251,24 @@ func (f *File) getRowHeight(sheet string, row int) int { // GetRowHeight provides a function to get row height by given worksheet name // and row index. For example, get the height of the first row in Sheet1: // -// xlsx.GetRowHeight("Sheet1", 1) +// height, err := xlsx.GetRowHeight("Sheet1", 1) // -func (f *File) GetRowHeight(sheet string, row int) float64 { +func (f *File) GetRowHeight(sheet string, row int) (float64, error) { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return defaultRowHeightPixels, newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) if row > len(xlsx.SheetData.Row) { - return defaultRowHeightPixels // it will be better to use 0, but we take care with BC + return defaultRowHeightPixels, nil // it will be better to use 0, but we take care with BC } for _, v := range xlsx.SheetData.Row { if v.R == row && v.Ht != 0 { - return v.Ht + return v.Ht, nil } } // Optimisation for when the row heights haven't changed. - return defaultRowHeightPixels + return defaultRowHeightPixels, nil } // sharedStringsReader provides a function to get the pointer to the structure @@ -299,138 +314,140 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) { // SetRowVisible provides a function to set visible of a single row by given // worksheet name and Excel row number. For example, hide row 2 in Sheet1: // -// xlsx.SetRowVisible("Sheet1", 2, false) +// err := xlsx.SetRowVisible("Sheet1", 2, false) // -func (f *File) SetRowVisible(sheet string, row int, visible bool) { +func (f *File) SetRowVisible(sheet string, row int, visible bool) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) prepareSheetXML(xlsx, 0, row) xlsx.SheetData.Row[row-1].Hidden = !visible + return nil } // GetRowVisible provides a function to get visible of a single row by given // worksheet name and Excel row number. For example, get visible state of row // 2 in Sheet1: // -// xlsx.GetRowVisible("Sheet1", 2) +// visible, err := xlsx.GetRowVisible("Sheet1", 2) // -func (f *File) GetRowVisible(sheet string, row int) bool { +func (f *File) GetRowVisible(sheet string, row int) (bool, error) { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return false, newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) if row > len(xlsx.SheetData.Row) { - return false + return false, nil } - return !xlsx.SheetData.Row[row-1].Hidden + return !xlsx.SheetData.Row[row-1].Hidden, nil } // SetRowOutlineLevel provides a function to set outline level number of a // single row by given worksheet name and Excel row number. For example, // outline row 2 in Sheet1 to level 1: // -// xlsx.SetRowOutlineLevel("Sheet1", 2, 1) +// err := xlsx.SetRowOutlineLevel("Sheet1", 2, 1) // -func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) { +func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) prepareSheetXML(xlsx, 0, row) xlsx.SheetData.Row[row-1].OutlineLevel = level + return nil } // GetRowOutlineLevel provides a function to get outline level number of a // single row by given worksheet name and Excel row number. For example, get // outline number of row 2 in Sheet1: // -// xlsx.GetRowOutlineLevel("Sheet1", 2) +// level, err := xlsx.GetRowOutlineLevel("Sheet1", 2) // -func (f *File) GetRowOutlineLevel(sheet string, row int) uint8 { +func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return 0, newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) if row > len(xlsx.SheetData.Row) { - return 0 + return 0, nil } - return xlsx.SheetData.Row[row-1].OutlineLevel + return xlsx.SheetData.Row[row-1].OutlineLevel, nil } // RemoveRow provides a function to remove single row by given worksheet name // and Excel row number. For example, remove row 3 in Sheet1: // -// xlsx.RemoveRow("Sheet1", 3) +// err := xlsx.RemoveRow("Sheet1", 3) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. -func (f *File) RemoveRow(sheet string, row int) { +func (f *File) RemoveRow(sheet string, row int) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) if row > len(xlsx.SheetData.Row) { - return + return nil } for rowIdx := range xlsx.SheetData.Row { if xlsx.SheetData.Row[rowIdx].R == row { xlsx.SheetData.Row = append(xlsx.SheetData.Row[:rowIdx], xlsx.SheetData.Row[rowIdx+1:]...)[:len(xlsx.SheetData.Row)-1] - f.adjustHelper(sheet, rows, row, -1) - return + return f.adjustHelper(sheet, rows, row, -1) } } + return nil } // InsertRow provides a function to insert a new row after given Excel row // number starting from 1. For example, create a new row before row 3 in // Sheet1: // -// xlsx.InsertRow("Sheet1", 3) +// err := elsx.InsertRow("Sheet1", 3) // -func (f *File) InsertRow(sheet string, row int) { +func (f *File) InsertRow(sheet string, row int) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } - f.adjustHelper(sheet, rows, row, 1) + return f.adjustHelper(sheet, rows, row, 1) } // DuplicateRow inserts a copy of specified row (by it Excel row number) below // -// xlsx.DuplicateRow("Sheet1", 2) +// err := xlsx.DuplicateRow("Sheet1", 2) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. -func (f *File) DuplicateRow(sheet string, row int) { - f.DuplicateRowTo(sheet, row, row+1) +func (f *File) DuplicateRow(sheet string, row int) error { + return f.DuplicateRowTo(sheet, row, row+1) } // DuplicateRowTo inserts a copy of specified row by it Excel number // to specified row position moving down exists rows after target position // -// xlsx.DuplicateRowTo("Sheet1", 2, 7) +// err := xlsx.DuplicateRowTo("Sheet1", 2, 7) // // Use this method with caution, which will affect changes in references such // as formulas, charts, and so on. If there is any referenced value of the // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. -func (f *File) DuplicateRowTo(sheet string, row, row2 int) { +func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { if row < 1 { - panic(newInvalidRowNumberError(row)) // Fail fast to avoid possible future side effects! + return newInvalidRowNumberError(row) } xlsx := f.workSheetReader(sheet) if row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 { - return + return nil } var ok bool @@ -444,10 +461,12 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) { } } if !ok { - return + return nil } - f.adjustHelper(sheet, rows, row2, 1) + if err := f.adjustHelper(sheet, rows, row2, 1); err != nil { + return err + } idx2 := -1 for i, r := range xlsx.SheetData.Row { @@ -457,7 +476,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) { } } if idx2 == -1 && len(xlsx.SheetData.Row) >= row2 { - return + return nil } rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) @@ -468,6 +487,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) { } else { xlsx.SheetData.Row = append(xlsx.SheetData.Row, rowCopy) } + return nil } // checkRow provides a function to check and fill each column element for all @@ -494,7 +514,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) { // // Noteice: this method could be very slow for large spreadsheets (more than // 3000 rows one sheet). -func checkRow(xlsx *xlsxWorksheet) { +func checkRow(xlsx *xlsxWorksheet) error { for rowIdx := range xlsx.SheetData.Row { rowData := &xlsx.SheetData.Row[rowIdx] @@ -502,7 +522,10 @@ func checkRow(xlsx *xlsxWorksheet) { if colCount == 0 { continue } - lastCol, _ := MustCellNameToCoordinates(rowData.C[colCount-1].R) + lastCol, _, err := CellNameToCoordinates(rowData.C[colCount-1].R) + if err != nil { + return err + } if colCount < lastCol { oldList := rowData.C @@ -511,18 +534,26 @@ func checkRow(xlsx *xlsxWorksheet) { rowData.C = xlsx.SheetData.Row[rowIdx].C[:0] for colIdx := 0; colIdx < lastCol; colIdx++ { - newlist = append(newlist, xlsxC{R: MustCoordinatesToCellName(colIdx+1, rowIdx+1)}) + cellName, err := CoordinatesToCellName(colIdx+1, rowIdx+1) + if err != nil { + return err + } + newlist = append(newlist, xlsxC{R: cellName}) } rowData.C = newlist for colIdx := range oldList { colData := &oldList[colIdx] - colNum, _ := MustCellNameToCoordinates(colData.R) + colNum, _, err := CellNameToCoordinates(colData.R) + if err != nil { + return err + } xlsx.SheetData.Row[rowIdx].C[colNum-1] = *colData } } } + return nil } // convertRowHeightToPixels provides a function to convert the height of a diff --git a/rows_test.go b/rows_test.go index 50e26dd..53c0677 100644 --- a/rows_test.go +++ b/rows_test.go @@ -23,13 +23,16 @@ func TestRows(t *testing.T) { collectedRows := make([][]string, 0) for rows.Next() { - collectedRows = append(collectedRows, trimSliceSpace(rows.Columns())) + columns, err := rows.Columns() + assert.NoError(t, err) + collectedRows = append(collectedRows, trimSliceSpace(columns)) } if !assert.NoError(t, rows.Error()) { t.FailNow() } - returnedRows := xlsx.GetRows(sheet2) + returnedRows, err := xlsx.GetRows(sheet2) + assert.NoError(t, err) for i := range returnedRows { returnedRows[i] = trimSliceSpace(returnedRows[i]) } @@ -54,21 +57,22 @@ func TestRowHeight(t *testing.T) { xlsx := NewFile() sheet1 := xlsx.GetSheetName(1) - assert.Panics(t, func() { - xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0) - }) + assert.EqualError(t, xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0), "invalid row number 0") - assert.Panics(t, func() { - xlsx.GetRowHeight("Sheet1", 0) - }) + height, err := xlsx.GetRowHeight("Sheet1", 0) + assert.EqualError(t, err, "invalid row number 0") - xlsx.SetRowHeight(sheet1, 1, 111.0) - assert.Equal(t, 111.0, xlsx.GetRowHeight(sheet1, 1)) + assert.NoError(t, xlsx.SetRowHeight(sheet1, 1, 111.0)) + height, err = xlsx.GetRowHeight(sheet1, 1) + assert.NoError(t, err) + assert.Equal(t, 111.0, height) - xlsx.SetRowHeight(sheet1, 4, 444.0) - assert.Equal(t, 444.0, xlsx.GetRowHeight(sheet1, 4)) + assert.NoError(t, xlsx.SetRowHeight(sheet1, 4, 444.0)) + height, err = xlsx.GetRowHeight(sheet1, 4) + assert.NoError(t, err) + assert.Equal(t, 444.0, height) - err := xlsx.SaveAs(filepath.Join("test", "TestRowHeight.xlsx")) + err = xlsx.SaveAs(filepath.Join("test", "TestRowHeight.xlsx")) if !assert.NoError(t, err) { t.FailNow() } @@ -86,13 +90,11 @@ func TestRowVisibility(t *testing.T) { xlsx.SetRowVisible("Sheet3", 2, true) xlsx.GetRowVisible("Sheet3", 2) - assert.Panics(t, func() { - xlsx.SetRowVisible("Sheet3", 0, true) - }) + assert.EqualError(t, xlsx.SetRowVisible("Sheet3", 0, true), "invalid row number 0") - assert.Panics(t, func() { - xlsx.GetRowVisible("Sheet3", 0) - }) + visible, err := xlsx.GetRowVisible("Sheet3", 0) + assert.Equal(t, false, visible) + assert.EqualError(t, err, "invalid row number 0") assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx"))) } @@ -110,27 +112,23 @@ func TestRemoveRow(t *testing.T) { xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External") - assert.Panics(t, func() { - xlsx.RemoveRow(sheet1, -1) - }) + assert.EqualError(t, xlsx.RemoveRow(sheet1, -1), "invalid row number -1") - assert.Panics(t, func() { - xlsx.RemoveRow(sheet1, 0) - }) + assert.EqualError(t, xlsx.RemoveRow(sheet1, 0), "invalid row number 0") - xlsx.RemoveRow(sheet1, 4) + assert.NoError(t, xlsx.RemoveRow(sheet1, 4)) if !assert.Len(t, r.SheetData.Row, rowCount-1) { t.FailNow() } xlsx.MergeCell(sheet1, "B3", "B5") - xlsx.RemoveRow(sheet1, 2) + assert.NoError(t, xlsx.RemoveRow(sheet1, 2)) if !assert.Len(t, r.SheetData.Row, rowCount-2) { t.FailNow() } - xlsx.RemoveRow(sheet1, 4) + assert.NoError(t, xlsx.RemoveRow(sheet1, 4)) if !assert.Len(t, r.SheetData.Row, rowCount-3) { t.FailNow() } @@ -140,17 +138,17 @@ func TestRemoveRow(t *testing.T) { t.FailNow() } - xlsx.RemoveRow(sheet1, 1) + assert.NoError(t, xlsx.RemoveRow(sheet1, 1)) if !assert.Len(t, r.SheetData.Row, rowCount-4) { t.FailNow() } - xlsx.RemoveRow(sheet1, 2) + assert.NoError(t, xlsx.RemoveRow(sheet1, 2)) if !assert.Len(t, r.SheetData.Row, rowCount-5) { t.FailNow() } - xlsx.RemoveRow(sheet1, 1) + assert.NoError(t, xlsx.RemoveRow(sheet1, 1)) if !assert.Len(t, r.SheetData.Row, rowCount-6) { t.FailNow() } @@ -171,20 +169,16 @@ func TestInsertRow(t *testing.T) { xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External") - assert.Panics(t, func() { - xlsx.InsertRow(sheet1, -1) - }) + assert.EqualError(t, xlsx.InsertRow(sheet1, -1), "invalid row number -1") - assert.Panics(t, func() { - xlsx.InsertRow(sheet1, 0) - }) + assert.EqualError(t, xlsx.InsertRow(sheet1, 0), "invalid row number 0") - xlsx.InsertRow(sheet1, 1) + assert.NoError(t, xlsx.InsertRow(sheet1, 1)) if !assert.Len(t, r.SheetData.Row, rowCount+1) { t.FailNow() } - xlsx.InsertRow(sheet1, 4) + assert.NoError(t, xlsx.InsertRow(sheet1, 4)) if !assert.Len(t, r.SheetData.Row, rowCount+2) { t.FailNow() } @@ -198,11 +192,11 @@ func TestInsertRowInEmptyFile(t *testing.T) { xlsx := NewFile() sheet1 := xlsx.GetSheetName(1) r := xlsx.workSheetReader(sheet1) - xlsx.InsertRow(sheet1, 1) + assert.NoError(t, xlsx.InsertRow(sheet1, 1)) assert.Len(t, r.SheetData.Row, 0) - xlsx.InsertRow(sheet1, 2) + assert.NoError(t, xlsx.InsertRow(sheet1, 2)) assert.Len(t, r.SheetData.Row, 0) - xlsx.InsertRow(sheet1, 99) + assert.NoError(t, xlsx.InsertRow(sheet1, 99)) assert.Len(t, r.SheetData.Row, 0) assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx"))) } @@ -234,7 +228,7 @@ func TestDuplicateRow(t *testing.T) { xlsx.SetCellStr(sheet, "A1", cells["A1"]) xlsx.SetCellStr(sheet, "B1", cells["B1"]) - xlsx.DuplicateRow(sheet, 1) + assert.NoError(t, xlsx.DuplicateRow(sheet, 1)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_1"))) { t.FailNow() } @@ -243,12 +237,14 @@ func TestDuplicateRow(t *testing.T) { "A2": cells["A1"], "B2": cells["B1"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } - xlsx.DuplicateRow(sheet, 2) + assert.NoError(t, xlsx.DuplicateRow(sheet, 2)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_2"))) { t.FailNow() } @@ -258,7 +254,9 @@ func TestDuplicateRow(t *testing.T) { "A3": cells["A1"], "B3": cells["B1"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -269,7 +267,7 @@ func TestDuplicateRow(t *testing.T) { xlsx.SetCellStr(sheet, "A1", cells["A1"]) xlsx.SetCellStr(sheet, "B1", cells["B1"]) - xlsx.DuplicateRow(sheet, 1) + assert.NoError(t, xlsx.DuplicateRow(sheet, 1)) xlsx.SetCellStr(sheet, "A2", cells["A2"]) xlsx.SetCellStr(sheet, "B2", cells["B2"]) @@ -282,7 +280,9 @@ func TestDuplicateRow(t *testing.T) { "A2": cells["A2"], "B2": cells["B2"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -291,7 +291,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("FirstOfMultipleRows", func(t *testing.T) { xlsx := newFileWithDefaults() - xlsx.DuplicateRow(sheet, 1) + assert.NoError(t, xlsx.DuplicateRow(sheet, 1)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FirstOfMultipleRows"))) { t.FailNow() @@ -303,7 +303,9 @@ func TestDuplicateRow(t *testing.T) { "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -312,26 +314,35 @@ func TestDuplicateRow(t *testing.T) { t.Run("ZeroWithNoRows", func(t *testing.T) { xlsx := NewFile() - assert.Panics(t, func() { - xlsx.DuplicateRow(sheet, 0) - }) + assert.EqualError(t, xlsx.DuplicateRow(sheet, 0), "invalid row number 0") if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.ZeroWithNoRows"))) { t.FailNow() } - assert.Equal(t, "", xlsx.GetCellValue(sheet, "A1")) - assert.Equal(t, "", xlsx.GetCellValue(sheet, "B1")) - assert.Equal(t, "", xlsx.GetCellValue(sheet, "A2")) - assert.Equal(t, "", xlsx.GetCellValue(sheet, "B2")) - + val, err := xlsx.GetCellValue(sheet, "A1") + assert.NoError(t, err) + assert.Equal(t, "", val) + val, err = xlsx.GetCellValue(sheet, "B1") + assert.NoError(t, err) + assert.Equal(t, "", val) + val, err = xlsx.GetCellValue(sheet, "A2") + assert.NoError(t, err) + assert.Equal(t, "", val) + val, err = xlsx.GetCellValue(sheet, "B2") + assert.NoError(t, err) + assert.Equal(t, "", val) + + assert.NoError(t, err) expect := map[string]string{ "A1": "", "B1": "", "A2": "", "B2": "", } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -340,7 +351,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("MiddleRowOfEmptyFile", func(t *testing.T) { xlsx := NewFile() - xlsx.DuplicateRow(sheet, 99) + assert.NoError(t, xlsx.DuplicateRow(sheet, 99)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.MiddleRowOfEmptyFile"))) { t.FailNow() @@ -351,7 +362,9 @@ func TestDuplicateRow(t *testing.T) { "A100": "", } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -360,7 +373,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) { xlsx := newFileWithDefaults() - xlsx.DuplicateRowTo(sheet, 1, 3) + assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 3)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.WithLargeOffsetToMiddleOfData"))) { t.FailNow() @@ -372,7 +385,9 @@ func TestDuplicateRow(t *testing.T) { "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -381,7 +396,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) { xlsx := newFileWithDefaults() - xlsx.DuplicateRowTo(sheet, 1, 7) + assert.NoError(t, xlsx.DuplicateRowTo(sheet, 1, 7)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.WithLargeOffsetToEmptyRows"))) { t.FailNow() @@ -393,7 +408,9 @@ func TestDuplicateRow(t *testing.T) { "A7": cells["A1"], "B7": cells["B1"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -402,7 +419,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("InsertBefore", func(t *testing.T) { xlsx := newFileWithDefaults() - xlsx.DuplicateRowTo(sheet, 2, 1) + assert.NoError(t, xlsx.DuplicateRowTo(sheet, 2, 1)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBefore"))) { t.FailNow() @@ -415,7 +432,9 @@ func TestDuplicateRow(t *testing.T) { "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v, cell) { t.FailNow() } } @@ -424,7 +443,7 @@ func TestDuplicateRow(t *testing.T) { t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) { xlsx := newFileWithDefaults() - xlsx.DuplicateRowTo(sheet, 3, 1) + assert.NoError(t, xlsx.DuplicateRowTo(sheet, 3, 1)) if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.InsertBeforeWithLargeOffset"))) { t.FailNow() @@ -437,7 +456,9 @@ func TestDuplicateRow(t *testing.T) { "A4": cells["A3"], "B4": cells["B3"], } for cell, val := range expect { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell)) { + v, err := xlsx.GetCellValue(sheet, cell) + assert.NoError(t, err) + if !assert.Equal(t, val, v) { t.FailNow() } } @@ -467,12 +488,12 @@ func TestDuplicateRowInvalidRownum(t *testing.T) { xlsx.SetCellStr(sheet, col, val) } - assert.Panics(t, func() { - xlsx.DuplicateRow(sheet, row) - }) + assert.EqualError(t, xlsx.DuplicateRow(sheet, row), fmt.Sprintf("invalid row number %d", row)) for col, val := range cells { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) { + v, err := xlsx.GetCellValue(sheet, col) + assert.NoError(t, err) + if !assert.Equal(t, val, v) { t.FailNow() } } @@ -489,12 +510,12 @@ func TestDuplicateRowInvalidRownum(t *testing.T) { xlsx.SetCellStr(sheet, col, val) } - assert.Panics(t, func() { - xlsx.DuplicateRowTo(sheet, row1, row2) - }) + assert.EqualError(t, xlsx.DuplicateRowTo(sheet, row1, row2), fmt.Sprintf("invalid row number %d", row1)) for col, val := range cells { - if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) { + v, err := xlsx.GetCellValue(sheet, col) + assert.NoError(t, err) + if !assert.Equal(t, val, v) { t.FailNow() } } @@ -275,15 +275,21 @@ func (f *File) AddShape(sheet, cell, format string) error { rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) } - f.addDrawingShape(sheet, drawingXML, cell, formatSet) + err = f.addDrawingShape(sheet, drawingXML, cell, formatSet) + if err != nil { + return err + } f.addContentTypePart(drawingID, "drawings") return err } // addDrawingShape provides a function to add preset geometry by given sheet, // drawingXMLand format sets. -func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) { - fromCol, fromRow := MustCellNameToCoordinates(cell) +func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) error { + fromCol, fromRow, err := CellNameToCoordinates(cell) + if err != nil { + return err + } colIdx := fromCol - 1 rowIdx := fromRow - 1 @@ -421,6 +427,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings[drawingXML] = content + return err } // setShapeRef provides a function to set color with hex model by given actual @@ -275,11 +275,11 @@ func (f *File) GetActiveSheetIndex() int { return 0 } -// SetSheetName provides a function to set the worksheet name be given old and new -// worksheet name. Maximum 31 characters are allowed in sheet title and this -// function only changes the name of the sheet and will not update the sheet -// name in the formula or reference associated with the cell. So there may be -// problem formula error or reference missing. +// SetSheetName provides a function to set the worksheet name be given old and +// new worksheet name. Maximum 31 characters are allowed in sheet title and +// this function only changes the name of the sheet and will not update the +// sheet name in the formula or reference associated with the cell. So there +// may be problem formula error or reference missing. func (f *File) SetSheetName(oldName, newName string) { oldName = trimSheetName(oldName) newName = trimSheetName(newName) @@ -665,14 +665,14 @@ func (f *File) GetSheetVisible(name string) bool { // // An example of search the coordinates of the value of "100" on Sheet1: // -// xlsx.SearchSheet("Sheet1", "100") +// result, err := xlsx.SearchSheet("Sheet1", "100") // // An example of search the coordinates where the numerical value in the range // of "0-9" of Sheet1 is described: // -// xlsx.SearchSheet("Sheet1", "[0-9]", true) +// result, err := xlsx.SearchSheet("Sheet1", "[0-9]", true) // -func (f *File) SearchSheet(sheet, value string, reg ...bool) []string { +func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { var regSearch bool for _, r := range reg { regSearch = r @@ -683,7 +683,7 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string { ) name, ok := f.sheetMap[trimSheetName(sheet)] if !ok { - return result + return result, nil } if xlsx != nil { output, _ := xml.Marshal(f.Sheet[name]) @@ -718,14 +718,21 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string { } } - cellCol, _ := MustCellNameToCoordinates(colCell.R) - result = append(result, MustCoordinatesToCellName(cellCol, r.R)) + cellCol, _, err := CellNameToCoordinates(colCell.R) + if err != nil { + return result, err + } + cellName, err := CoordinatesToCellName(cellCol, r.R) + if err != nil { + return result, err + } + result = append(result, cellName) } } default: } } - return result + return result, nil } // ProtectSheet provides a function to prevent other users from accidentally @@ -1880,8 +1880,8 @@ func parseFormatStyleSet(style string) (*formatStyle, error) { // // xlsx := excelize.NewFile() // xlsx.SetCellValue("Sheet1", "A6", 42920.5) -// style, _ := xlsx.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) -// xlsx.SetCellStyle("Sheet1", "A6", "A6", style) +// style, err := xlsx.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) +// err = xlsx.SetCellStyle("Sheet1", "A6", "A6", style) // // Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 // @@ -2265,10 +2265,13 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a // GetCellStyle provides a function to get cell style index by given worksheet // name and cell coordinates. -func (f *File) GetCellStyle(sheet, axis string) int { +func (f *File) GetCellStyle(sheet, axis string) (int, error) { xlsx := f.workSheetReader(sheet) - cellData, col, _ := f.prepareCell(xlsx, sheet, axis) - return f.prepareCellStyle(xlsx, col, cellData.S) + cellData, col, _, err := f.prepareCell(xlsx, sheet, axis) + if err != nil { + return 0, err + } + return f.prepareCellStyle(xlsx, col, cellData.S), err } // SetCellStyle provides a function to add style attribute for cells by given @@ -2282,7 +2285,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Set gradient fill with vertical variants shading styles for cell H9 on // Sheet1: @@ -2291,7 +2294,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Set solid style pattern fill for cell H9 on Sheet1: // @@ -2299,7 +2302,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Set alignment style for cell H9 on Sheet1: // @@ -2307,7 +2310,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Dates and times in Excel are represented by real numbers, for example "Apr 7 // 2017 12:00 PM" is represented by the number 42920.5. Set date and time format @@ -2318,7 +2321,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Set font style for cell H9 on Sheet1: // @@ -2326,7 +2329,7 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // // Hide and lock for cell H9 on Sheet1: // @@ -2334,17 +2337,17 @@ func (f *File) GetCellStyle(sheet, axis string) int { // if err != nil { // fmt.Println(err) // } -// xlsx.SetCellStyle("Sheet1", "H9", "H9", style) +// err = xlsx.SetCellStyle("Sheet1", "H9", "H9", style) // -func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) { +func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) error { hcol, hrow, err := CellNameToCoordinates(hcell) if err != nil { - panic(err) + return err } vcol, vrow, err := CellNameToCoordinates(vcell) if err != nil { - panic(err) + return err } // Normalize the coordinate area, such correct C1:B3 to B1:C3. @@ -2370,6 +2373,7 @@ func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) { xlsx.SheetData.Row[r].C[k].S = styleID } } + return nil } // SetConditionalFormat provides a function to create conditional formatting @@ -33,11 +33,11 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) { // name, coordinate area and format set. For example, create a table of A1:D5 // on Sheet1: // -// xlsx.AddTable("Sheet1", "A1", "D5", ``) +// err := xlsx.AddTable("Sheet1", "A1", "D5", ``) // // Create a table of F2:H6 on Sheet2 with format set: // -// xlsx.AddTable("Sheet2", "F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`) +// err := xlsx.AddTable("Sheet2", "F2", "H6", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`) // // Note that the table at least two lines include string type header. Multiple // tables coordinate areas can't have an intersection. @@ -56,8 +56,14 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error { return err } // Coordinate conversion, convert C1:B3 to 2,0,1,2. - hcol, hrow := MustCellNameToCoordinates(hcell) - vcol, vrow := MustCellNameToCoordinates(vcell) + hcol, hrow, err := CellNameToCoordinates(hcell) + if err != nil { + return err + } + vcol, vrow, err := CellNameToCoordinates(vcell) + if err != nil { + return err + } if vcol < hcol { vcol, hcol = hcol, vcol @@ -73,7 +79,10 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error { // Add first table for given sheet. rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "") f.addSheetTable(sheet, rID) - f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet) + err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet) + if err != nil { + return err + } f.addContentTypePart(tableID, "table") return err } @@ -106,24 +115,33 @@ func (f *File) addSheetTable(sheet string, rID int) { // addTable provides a function to add table by given worksheet name, // coordinate area and format set. -func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, formatSet *formatTable) { +func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, formatSet *formatTable) error { // Correct the minimum number of rows, the table at least two lines. if hrow == vrow { vrow++ } // Correct table reference coordinate area, such correct C1:B3 to B1:C3. - ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow) + hcell, err := CoordinatesToCellName(hcol, hrow) + if err != nil { + return err + } + vcell, err := CoordinatesToCellName(vcol, vrow) + if err != nil { + return err + } + ref := hcell + ":" + vcell - var ( - tableColumn []*xlsxTableColumn - ) + var tableColumn []*xlsxTableColumn idx := 0 for i := hcol; i <= vcol; i++ { idx++ - cell := MustCoordinatesToCellName(i, hrow) - name := f.GetCellValue(sheet, cell) + cell, err := CoordinatesToCellName(i, hrow) + if err != nil { + return err + } + name, _ := f.GetCellValue(sheet, cell) if _, err := strconv.Atoi(name); err == nil { f.SetCellStr(sheet, cell, name) } @@ -163,6 +181,7 @@ func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, f } table, _ := xml.Marshal(t) f.saveFileList(tableXML, table) + return nil } // parseAutoFilterSet provides a function to parse the settings of the auto @@ -244,8 +263,14 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) { // Price < 2000 // func (f *File) AutoFilter(sheet, hcell, vcell, format string) error { - hcol, hrow := MustCellNameToCoordinates(hcell) - vcol, vrow := MustCellNameToCoordinates(vcell) + hcol, hrow, err := CellNameToCoordinates(hcell) + if err != nil { + return err + } + vcol, vrow, err := CellNameToCoordinates(vcell) + if err != nil { + return err + } if vcol < hcol { vcol, hcol = hcol, vcol @@ -256,7 +281,17 @@ func (f *File) AutoFilter(sheet, hcell, vcell, format string) error { } formatSet, _ := parseAutoFilterSet(format) - ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow) + + var cellStart, cellEnd string + cellStart, err = CoordinatesToCellName(hcol, hrow) + if err != nil { + return err + } + cellEnd, err = CoordinatesToCellName(vcol, vrow) + if err != nil { + return err + } + ref := cellStart + ":" + cellEnd refRange := vcol - hcol return f.autoFilter(sheet, ref, refRange, hcol, formatSet) } @@ -277,7 +312,10 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *forma return nil } - fsCol := MustColumnNameToNumber(formatSet.Column) + fsCol, err := ColumnNameToNumber(formatSet.Column) + if err != nil { + return err + } offset := fsCol - col if offset < 0 || offset > refRange { return fmt.Errorf("incorrect index of column '%s'", formatSet.Column) |