summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVeniamin Albaev <albenik@gmail.com>2019-03-19 19:14:41 +0300
committerxuri <xuri.me@gmail.com>2019-03-20 00:14:41 +0800
commitdc01264562e6e88d77a28042408029770ea32df4 (patch)
treef3d8fd1627fb71676bab59fe2fa1c9b076b360d8
parent092f16c744c40e85be5cf6128dfb35c96e7df78b (diff)
Huge refactorig for consistent col/row numbering (#356)
* Huge refactorig for consistent col/row numbering Started from simply changing ToALphaString()/TitleToNumber() logic and related fixes. But have to go deeper, do fixes, after do related fixes and again and again. Major improvements: 1. Tests made stronger again (But still be weak). 2. "Empty" returns for incorrect input replaces with panic. 3. Check for correct col/row/cell naming & addressing by default. 4. Removed huge amount of duplicated code. 5. Removed ToALphaString(), TitleToNumber() and it helpers functions at all, and replaced with SplitCellName(), JoinCellName(), ColumnNameToNumber(), ColumnNumberToName(), CellNameToCoordinates(), CoordinatesToCellName(). 6. Minor fixes for internal variable naming for code readability (ex. col, row for input params, colIdx, rowIdx for slice indexes etc). * Formatting fixes
-rw-r--r--.gitignore1
-rw-r--r--adjust.go229
-rw-r--r--cell.go706
-rw-r--r--cell_test.go9
-rw-r--r--chart.go12
-rw-r--r--chart_test.go48
-rw-r--r--col.go166
-rw-r--r--comment.go5
-rw-r--r--date.go60
-rw-r--r--date_test.go43
-rw-r--r--errors.go17
-rw-r--r--errors_test.go21
-rw-r--r--excelize.go229
-rw-r--r--excelize_test.go347
-rw-r--r--lib.go234
-rw-r--r--lib_test.go228
-rw-r--r--picture.go35
-rw-r--r--rows.go197
-rw-r--r--rows_test.go174
-rw-r--r--shape.go36
-rw-r--r--sheet.go48
-rw-r--r--styles.go50
-rw-r--r--table.go86
23 files changed, 1647 insertions, 1334 deletions
diff --git a/.gitignore b/.gitignore
index ef972f6..29e0f2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
+~$*.xlsx
test/Test*.xlsx
diff --git a/adjust.go b/adjust.go
new file mode 100644
index 0000000..ee2065f
--- /dev/null
+++ b/adjust.go
@@ -0,0 +1,229 @@
+package excelize
+
+import (
+ "strings"
+)
+
+type adjustDirection bool
+
+const (
+ columns adjustDirection = false
+ rows adjustDirection = true
+)
+
+// adjustHelper provides a function to adjust rows and columns dimensions,
+// hyperlinks, merged cells and auto filter when inserting or deleting rows or
+// columns.
+//
+// sheet: Worksheet name that we're editing
+// column: Index number of the column we're inserting/deleting before
+// row: Index number of the row we're inserting/deleting before
+// offset: Number of rows/column to insert/delete negative values indicate deletion
+//
+// TODO: adjustCalcChain, adjustPageBreaks, adjustComments,
+// adjustDataValidations, adjustProtectedCells
+//
+func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) {
+ xlsx := f.workSheetReader(sheet)
+
+ if dir == rows {
+ f.adjustRowDimensions(xlsx, num, offset)
+ } else {
+ f.adjustColDimensions(xlsx, num, offset)
+ }
+ f.adjustHyperlinks(xlsx, sheet, dir, num, offset)
+ f.adjustMergeCells(xlsx, dir, num, offset)
+ f.adjustAutoFilter(xlsx, dir, num, offset)
+
+ checkSheet(xlsx)
+ checkRow(xlsx)
+}
+
+// adjustColDimensions provides a function to update column dimensions when
+// inserting or deleting rows or columns.
+func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, col, offset int) {
+ for rowIdx := range xlsx.SheetData.Row {
+ for colIdx, v := range xlsx.SheetData.Row[rowIdx].C {
+ cellCol, cellRow, _ := CellNameToCoordinates(v.R)
+ if col <= cellCol {
+ if newCol := cellCol + offset; newCol > 0 {
+ xlsx.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
+ }
+ }
+ }
+ }
+}
+
+// adjustRowDimensions provides a function to update row dimensions when
+// inserting or deleting rows or columns.
+func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, row, offset int) {
+ for i, r := range xlsx.SheetData.Row {
+ if newRow := r.R + offset; r.R >= row && newRow > 0 {
+ f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], newRow)
+ }
+ }
+}
+
+// ajustSingleRowDimensions provides a function to ajust single row dimensions.
+func (f *File) ajustSingleRowDimensions(r *xlsxRow, num int) {
+ r.R = num
+ for i, col := range r.C {
+ colName, _, _ := SplitCellName(col.R)
+ r.C[i].R, _ = JoinCellName(colName, num)
+ }
+}
+
+// adjustHyperlinks provides a function to update hyperlinks when inserting or
+// deleting rows or columns.
+func (f *File) adjustHyperlinks(xlsx *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
+ // short path
+ if xlsx.Hyperlinks == nil || len(xlsx.Hyperlinks.Hyperlink) == 0 {
+ return
+ }
+
+ // order is important
+ if offset < 0 {
+ for rowIdx, linkData := range xlsx.Hyperlinks.Hyperlink {
+ colNum, rowNum, _ := CellNameToCoordinates(linkData.Ref)
+
+ if (dir == rows && num == rowNum) || (dir == columns && num == colNum) {
+ f.deleteSheetRelationships(sheet, linkData.RID)
+ if len(xlsx.Hyperlinks.Hyperlink) > 1 {
+ xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:rowIdx],
+ xlsx.Hyperlinks.Hyperlink[rowIdx+1:]...)
+ } else {
+ xlsx.Hyperlinks = nil
+ }
+ }
+ }
+ }
+
+ if xlsx.Hyperlinks == nil {
+ return
+ }
+
+ for i := range xlsx.Hyperlinks.Hyperlink {
+ link := &xlsx.Hyperlinks.Hyperlink[i] // get reference
+ colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
+
+ if dir == rows {
+ if rowNum >= num {
+ link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
+ }
+ } else {
+ if colNum >= num {
+ link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
+ }
+ }
+ }
+}
+
+// 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) {
+ if xlsx.AutoFilter == nil {
+ return
+ }
+
+ rng := strings.Split(xlsx.AutoFilter.Ref, ":")
+ firstCell := rng[0]
+ lastCell := rng[1]
+
+ firstCol, firstRow, err := CellNameToCoordinates(firstCell)
+ if err != nil {
+ panic(err)
+ }
+
+ lastCol, lastRow, err := CellNameToCoordinates(lastCell)
+ if err != nil {
+ panic(err)
+ }
+
+ if (dir == rows && firstRow == num && offset < 0) || (dir == columns && firstCol == num && lastCol == num) {
+ xlsx.AutoFilter = nil
+ for rowIdx := range xlsx.SheetData.Row {
+ rowData := &xlsx.SheetData.Row[rowIdx]
+ if rowData.R > firstRow && rowData.R <= lastRow {
+ rowData.Hidden = false
+ }
+ }
+ return
+ }
+
+ if dir == rows {
+ if firstRow >= num {
+ firstCell, _ = CoordinatesToCellName(firstCol, firstRow+offset)
+ }
+ if lastRow >= num {
+ lastCell, _ = CoordinatesToCellName(lastCol, lastRow+offset)
+ }
+ } else {
+ if lastCol >= num {
+ lastCell, _ = CoordinatesToCellName(lastCol+offset, lastRow)
+ }
+ }
+
+ xlsx.AutoFilter.Ref = firstCell + ":" + lastCell
+}
+
+// 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) {
+ if xlsx.MergeCells == nil {
+ return
+ }
+
+ for i, areaData := range xlsx.MergeCells.Cells {
+ rng := strings.Split(areaData.Ref, ":")
+ firstCell := rng[0]
+ lastCell := rng[1]
+
+ firstCol, firstRow, err := CellNameToCoordinates(firstCell)
+ if err != nil {
+ panic(err)
+ }
+
+ lastCol, lastRow, err := CellNameToCoordinates(lastCell)
+ if err != nil {
+ panic(err)
+ }
+
+ adjust := func(v int) int {
+ if v >= num {
+ v += offset
+ if v < 1 {
+ return 1
+ }
+ return v
+ }
+ return v
+ }
+
+ if dir == rows {
+ firstRow = adjust(firstRow)
+ lastRow = adjust(lastRow)
+ } else {
+ firstCol = adjust(firstCol)
+ lastCol = adjust(lastCol)
+ }
+
+ if firstCol == lastCol && firstRow == lastRow {
+ if len(xlsx.MergeCells.Cells) > 1 {
+ xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
+ xlsx.MergeCells.Count = len(xlsx.MergeCells.Cells)
+ } else {
+ xlsx.MergeCells = nil
+ }
+ }
+
+ if firstCell, err = CoordinatesToCellName(firstCol, firstRow); err != nil {
+ panic(err)
+ }
+
+ if lastCell, err = CoordinatesToCellName(lastCol, lastRow); err != nil {
+ panic(err)
+ }
+
+ areaData.Ref = firstCell + ":" + lastCell
+ }
+}
diff --git a/cell.go b/cell.go
index 3cf880a..126b1ff 100644
--- a/cell.go
+++ b/cell.go
@@ -11,6 +11,7 @@ package excelize
import (
"encoding/xml"
+ "errors"
"fmt"
"reflect"
"strconv"
@@ -29,18 +30,18 @@ const (
STCellFormulaTypeShared = "shared"
)
-// mergeCellsParser provides a function to check merged cells in worksheet by
-// given axis.
-func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
- 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) {
- axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
- }
+// GetCellValue provides a function to get formatted value from cell by given
+// 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) {
+ val, err := c.getValueFrom(f, f.sharedStringsReader())
+ if err != nil {
+ panic(err) // Fail fast to avoid future side effects!
}
- }
- return axis
+ return val, true
+ })
}
// SetCellValue provides a function to set value of a cell. The following
@@ -68,256 +69,169 @@ func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, 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{}) {
- switch t := value.(type) {
- case float32:
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float32)), 'f', -1, 32))
- case float64:
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float64)), 'f', -1, 64))
- case string:
- f.SetCellStr(sheet, axis, t)
- case []byte:
- f.SetCellStr(sheet, axis, string(t))
- case time.Duration:
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(time.Duration).Seconds()/86400), 'f', -1, 32))
- f.setDefaultTimeStyle(sheet, axis, 21)
- case time.Time:
- f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 64))
- f.setDefaultTimeStyle(sheet, axis, 22)
- case nil:
- f.SetCellStr(sheet, axis, "")
- case bool:
- f.SetCellBool(sheet, axis, bool(value.(bool)))
- default:
- f.setCellIntValue(sheet, axis, value)
- }
-}
-
-// setCellIntValue provides a function to set int value of a cell.
-func (f *File) setCellIntValue(sheet, axis string, value interface{}) {
- switch value.(type) {
+ switch v := value.(type) {
case int:
- f.SetCellInt(sheet, axis, value.(int))
+ f.SetCellInt(sheet, axis, v)
case int8:
- f.SetCellInt(sheet, axis, int(value.(int8)))
+ f.SetCellInt(sheet, axis, int(v))
case int16:
- f.SetCellInt(sheet, axis, int(value.(int16)))
+ f.SetCellInt(sheet, axis, int(v))
case int32:
- f.SetCellInt(sheet, axis, int(value.(int32)))
+ f.SetCellInt(sheet, axis, int(v))
case int64:
- f.SetCellInt(sheet, axis, int(value.(int64)))
+ f.SetCellInt(sheet, axis, int(v))
case uint:
- f.SetCellInt(sheet, axis, int(value.(uint)))
+ f.SetCellInt(sheet, axis, int(v))
case uint8:
- f.SetCellInt(sheet, axis, int(value.(uint8)))
+ f.SetCellInt(sheet, axis, int(v))
case uint16:
- f.SetCellInt(sheet, axis, int(value.(uint16)))
+ f.SetCellInt(sheet, axis, int(v))
case uint32:
- f.SetCellInt(sheet, axis, int(value.(uint32)))
+ f.SetCellInt(sheet, axis, int(v))
case uint64:
- f.SetCellInt(sheet, axis, int(value.(uint64)))
+ f.SetCellInt(sheet, axis, int(v))
+ case float32:
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(v), 'f', -1, 32))
+ case float64:
+ f.SetCellDefault(sheet, axis, strconv.FormatFloat(v, 'f', -1, 64))
+ case string:
+ f.SetCellStr(sheet, axis, v)
+ case []byte:
+ f.SetCellStr(sheet, axis, string(v))
+ case time.Duration:
+ 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))
+ f.setDefaultTimeStyle(sheet, axis, 22)
+ } else {
+ f.SetCellStr(sheet, axis, v.Format(time.RFC3339Nano))
+ }
+ case bool:
+ f.SetCellBool(sheet, axis, v)
+ case nil:
+ f.SetCellStr(sheet, axis, "")
default:
f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
}
}
-// SetCellBool provides a function to set bool type value of a cell by given
+// SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell coordinates and cell value.
-func (f *File) SetCellBool(sheet, axis string, value bool) {
+func (f *File) SetCellInt(sheet, axis string, value int) {
xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return
- }
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
+ cellData.T = ""
+ cellData.V = strconv.Itoa(value)
+}
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
- xlsx.SheetData.Row[xAxis].C[yAxis].T = "b"
+// 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) {
+ xlsx := f.workSheetReader(sheet)
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
+ cellData.T = "b"
if value {
- xlsx.SheetData.Row[xAxis].C[yAxis].V = "1"
+ cellData.V = "1"
} else {
- xlsx.SheetData.Row[xAxis].C[yAxis].V = "0"
+ cellData.V = "0"
}
}
-// GetCellValue provides a function to get formatted value from cell by given
-// 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 {
+// 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) {
xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return ""
- }
- xAxis := row - 1
- rows := len(xlsx.SheetData.Row)
- if rows > 1 {
- lastRow := xlsx.SheetData.Row[rows-1].R
- if lastRow >= rows {
- rows = lastRow
- }
- }
- if rows < xAxis {
- return ""
- }
- for k := range xlsx.SheetData.Row {
- if xlsx.SheetData.Row[k].R == row {
- for i := range xlsx.SheetData.Row[k].C {
- if axis == xlsx.SheetData.Row[k].C[i].R {
- val, _ := xlsx.SheetData.Row[k].C[i].getValueFrom(f, f.sharedStringsReader())
- return val
- }
- }
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
+
+ // Leading space(s) character detection.
+ if len(value) > 0 && value[0] == 32 {
+ cellData.XMLSpace = xml.Attr{
+ Name: xml.Name{Space: NameSpaceXML, Local: "space"},
+ Value: "preserve",
}
}
- return ""
-}
-// formattedValue provides a function to returns a value after formatted. If
-// it is possible to apply a format to the cell value, it will do so, if not
-// then an error will be returned, along with the raw value of the cell.
-func (f *File) formattedValue(s int, v string) string {
- if s == 0 {
- return v
- }
- styleSheet := f.stylesReader()
- ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
- if ok != nil {
- return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
- }
- return v
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
+ cellData.T = "str"
+ cellData.V = value
}
-// GetCellStyle provides a function to get cell style index by given worksheet
-// name and cell coordinates.
-func (f *File) GetCellStyle(sheet, axis string) int {
+// 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) {
xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return 0
- }
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
-
- return f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
+ cellData.S = f.prepareCellStyle(xlsx, col, cellData.S)
+ cellData.T = ""
+ cellData.V = value
}
// 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 {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return ""
- }
- xAxis := row - 1
- rows := len(xlsx.SheetData.Row)
- if rows > 1 {
- lastRow := xlsx.SheetData.Row[rows-1].R
- if lastRow >= rows {
- rows = lastRow
- }
- }
- if rows < xAxis {
- return ""
- }
- for k := range xlsx.SheetData.Row {
- if xlsx.SheetData.Row[k].R == row {
- for i := range xlsx.SheetData.Row[k].C {
- if axis == xlsx.SheetData.Row[k].C[i].R {
- if xlsx.SheetData.Row[k].C[i].F == nil {
- continue
- }
- if xlsx.SheetData.Row[k].C[i].F.T == STCellFormulaTypeShared {
- return getSharedForumula(xlsx, xlsx.SheetData.Row[k].C[i].F.Si)
- }
- return xlsx.SheetData.Row[k].C[i].F.Content
- }
- }
+ return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool) {
+ if c.F == nil {
+ return "", false
}
- }
- return ""
-}
-
-// getSharedForumula find a cell contains the same formula as another cell,
-// the "shared" value can be used for the t attribute and the si attribute can
-// be used to refer to the cell containing the formula. Two formulas are
-// considered to be the same when their respective representations in
-// R1C1-reference notation, are the same.
-//
-// Note that this function not validate ref tag to check the cell if or not in
-// allow area, and always return origin shared formula.
-func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
- for k := range xlsx.SheetData.Row {
- for i := range xlsx.SheetData.Row[k].C {
- if xlsx.SheetData.Row[k].C[i].F == nil {
- continue
- }
- if xlsx.SheetData.Row[k].C[i].F.T != STCellFormulaTypeShared {
- continue
- }
- if xlsx.SheetData.Row[k].C[i].F.Si != si {
- continue
- }
- if xlsx.SheetData.Row[k].C[i].F.Ref != "" {
- return xlsx.SheetData.Row[k].C[i].F.Content
- }
+ if c.F.T == STCellFormulaTypeShared {
+ return getSharedForumula(x, c.F.Si), true
}
- }
- return ""
+ return c.F.Content, true
+ })
}
// SetCellFormula provides a function to set cell formula by given string and
// worksheet name.
func (f *File) SetCellFormula(sheet, axis, formula string) {
xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return
- }
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
+ cellData, _, _ := f.prepareCell(xlsx, sheet, axis)
if formula == "" {
- xlsx.SheetData.Row[xAxis].C[yAxis].F = nil
+ cellData.F = nil
f.deleteCalcChain(axis)
return
}
- if xlsx.SheetData.Row[xAxis].C[yAxis].F != nil {
- xlsx.SheetData.Row[xAxis].C[yAxis].F.Content = formula
+
+ if cellData.F != nil {
+ cellData.F.Content = formula
} else {
- f := xlsxF{
- Content: formula,
+ cellData.F = &xlsxF{Content: formula}
+ }
+}
+
+// GetCellHyperLink provides a function to get cell hyperlink by given
+// worksheet name and axis. Boolean type value link will be ture if the cell
+// has a hyperlink and the target is the address of the hyperlink. Otherwise,
+// 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")
+//
+func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
+ // Check for correct cell name
+ if _, _, err := SplitCellName(axis); err != nil {
+ panic(err) // Fail fast to avoid possible future side effects
+ }
+
+ xlsx := f.workSheetReader(sheet)
+ axis = f.mergeCellsParser(xlsx, axis)
+
+ 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, link.Location
+ }
}
- xlsx.SheetData.Row[xAxis].C[yAxis].F = &f
}
+ return false, ""
}
// SetCellHyperLink provides a function to set cell hyperlink by given
@@ -335,53 +249,36 @@ func (f *File) SetCellFormula(sheet, axis, formula string) {
// xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
//
func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- linkTypes := map[string]xlsxHyperlink{
- "External": {},
- "Location": {Location: link},
+ // Check for correct cell name
+ if _, _, err := SplitCellName(axis); err != nil {
+ panic(err) // Fail fast to avoid possible future side effects
}
- hyperlink, ok := linkTypes[linkType]
- if !ok || axis == "" {
- return
- }
- hyperlink.Ref = axis
- if linkType == "External" {
- rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
- hyperlink.RID = "rId" + strconv.Itoa(rID)
- }
- if xlsx.Hyperlinks == nil {
- xlsx.Hyperlinks = &xlsxHyperlinks{}
- }
- xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, hyperlink)
-}
-// GetCellHyperLink provides a function to get cell hyperlink by given
-// worksheet name and axis. Boolean type value link will be ture if the cell
-// has a hyperlink and the target is the address of the hyperlink. Otherwise,
-// 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")
-//
-func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
- var link bool
- var target string
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
- if xlsx.Hyperlinks == nil || axis == "" {
- return link, target
- }
- for h := range xlsx.Hyperlinks.Hyperlink {
- if xlsx.Hyperlinks.Hyperlink[h].Ref == axis {
- link = true
- target = xlsx.Hyperlinks.Hyperlink[h].Location
- if xlsx.Hyperlinks.Hyperlink[h].RID != "" {
- target = f.getSheetRelationshipsTargetByID(sheet, xlsx.Hyperlinks.Hyperlink[h].RID)
- }
+
+ var linkData xlsxHyperlink
+
+ switch linkType {
+ case "External":
+ linkData = xlsxHyperlink{
+ Ref: axis,
}
+ rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
+ linkData.RID = "rId" + strconv.Itoa(rID)
+ case "Location":
+ linkData = xlsxHyperlink{
+ Ref: axis,
+ Location: link,
+ }
+ default:
+ panic(fmt.Errorf("invalid link type %q", linkType))
+ }
+
+ if xlsx.Hyperlinks == nil {
+ xlsx.Hyperlinks = new(xlsxHyperlinks)
}
- return link, target
+ xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, linkData)
}
// MergeCell provides a function to merge cells by given coordinate area and
@@ -392,213 +289,214 @@ func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
// 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) {
- if hcell == vcell {
- return
+ hcol, hrow, err := CellNameToCoordinates(hcell)
+ if err != nil {
+ panic(err)
}
- hcell = strings.ToUpper(hcell)
- vcell = strings.ToUpper(vcell)
-
- // Coordinate conversion, convert C1:B3 to 2,0,1,2.
- hcol := string(strings.Map(letterOnlyMapF, hcell))
- hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
- hyAxis := hrow - 1
- hxAxis := TitleToNumber(hcol)
+ vcol, vrow, err := CellNameToCoordinates(vcell)
+ if err != nil {
+ panic(err)
+ }
- vcol := string(strings.Map(letterOnlyMapF, vcell))
- vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
- vyAxis := vrow - 1
- vxAxis := TitleToNumber(vcol)
+ if hcol == vcol && hrow == vrow {
+ return
+ }
- if vxAxis < hxAxis {
- hcell, vcell = vcell, hcell
- vxAxis, hxAxis = hxAxis, vxAxis
+ if vcol < hcol {
+ hcol, vcol = vcol, hcol
}
- if vyAxis < hyAxis {
- hcell, vcell = vcell, hcell
- vyAxis, hyAxis = hyAxis, vyAxis
+ if vrow < hrow {
+ hrow, vrow = vrow, hrow
}
+ hcell, _ = CoordinatesToCellName(hcol, hrow)
+ vcell, _ = CoordinatesToCellName(vcol, vrow)
+
xlsx := f.workSheetReader(sheet)
if xlsx.MergeCells != nil {
- mergeCell := xlsxMergeCell{}
- // Correct the coordinate area, such correct C1:B3 to B1:C3.
- mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
+ ref := hcell + ":" + vcell
+ cells := make([]*xlsxMergeCell, 0, len(xlsx.MergeCells.Cells))
+
// Delete the merged cells of the overlapping area.
- for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
- if checkCellInArea(hcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0], mergeCell.Ref) {
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
- } else if checkCellInArea(vcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[1], mergeCell.Ref) {
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
+ for _, cellData := range xlsx.MergeCells.Cells {
+ cc := strings.Split(cellData.Ref, ":")
+ if len(cc) != 2 {
+ panic(fmt.Errorf("invalid area %q", cellData.Ref))
+ }
+
+ if !checkCellInArea(hcell, cellData.Ref) && !checkCellInArea(vcell, cellData.Ref) &&
+ !checkCellInArea(cc[0], ref) && !checkCellInArea(cc[1], ref) {
+ cells = append(cells, cellData)
}
}
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &mergeCell)
+ cells = append(xlsx.MergeCells.Cells, &xlsxMergeCell{Ref: ref})
+ xlsx.MergeCells.Cells = cells
} else {
- mergeCell := xlsxMergeCell{}
- // Correct the coordinate area, such correct C1:B3 to B1:C3.
- mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
- mergeCells := xlsxMergeCells{}
- mergeCells.Cells = append(mergeCells.Cells, &mergeCell)
- xlsx.MergeCells = &mergeCells
+ xlsx.MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: hcell + ":" + vcell}}}
}
}
-// 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) {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+// 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})
+//
+func (f *File) SetSheetRow(sheet, axis string, slice interface{}) {
+ col, row, err := CellNameToCoordinates(axis)
if err != nil {
- return
+ panic(err) // Fail fast to avoid future side effects!
}
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
- xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
- xlsx.SheetData.Row[xAxis].C[yAxis].V = strconv.Itoa(value)
-}
+ // 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!
+ }
+ v = v.Elem()
-// prepareCellStyle provides a function to prepare style index of cell in
-// worksheet by given column index and style index.
-func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
- if xlsx.Cols != nil && style == 0 {
- for _, v := range xlsx.Cols.Col {
- if v.Min <= col && col <= v.Max {
- style = v.Style
- }
+ for i := 0; i < v.Len(); i++ {
+ cell, err := CoordinatesToCellName(col+i, row)
+ // 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!
}
+ f.SetCellValue(sheet, cell, v.Index(i).Interface())
}
- return style
}
-// 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) {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- if len(value) > 32767 {
- value = value[0:32767]
- }
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+// 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)
+
+ col, row, err := CellNameToCoordinates(cell)
if err != nil {
- return
+ panic(err) // Fail fast and prevent future side effects
}
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
+ prepareSheetXML(xlsx, col, row)
- // Leading space(s) character detection.
- if len(value) > 0 {
- if value[0] == 32 {
- xlsx.SheetData.Row[xAxis].C[yAxis].XMLSpace = xml.Attr{
- Name: xml.Name{Space: NameSpaceXML, Local: "space"},
- Value: "preserve",
- }
- }
- }
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
- xlsx.SheetData.Row[xAxis].C[yAxis].T = "str"
- xlsx.SheetData.Row[xAxis].C[yAxis].V = value
+ return &xlsx.SheetData.Row[row-1].C[col-1], col, row
}
-// 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) {
+// 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 {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
+
+ _, row, err := CellNameToCoordinates(axis)
if err != nil {
- return
+ panic(err) // Fail fast to avoid future side effects!
}
- xAxis := row - 1
- yAxis := TitleToNumber(col)
- rows := xAxis + 1
- cell := yAxis + 1
+ lastRowNum := 0
+ if l := len(xlsx.SheetData.Row); l > 0 {
+ lastRowNum = xlsx.SheetData.Row[l-1].R
+ }
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
+ // keep in mind: row starts from 1
+ if row > lastRowNum {
+ return ""
+ }
- xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
- xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
- xlsx.SheetData.Row[xAxis].C[yAxis].V = value
+ for rowIdx := range xlsx.SheetData.Row {
+ rowData := &xlsx.SheetData.Row[rowIdx]
+ if rowData.R != row {
+ continue
+ }
+ for colIdx := range rowData.C {
+ colData := &rowData.C[colIdx]
+ if axis != colData.R {
+ continue
+ }
+ if val, ok := fn(xlsx, colData); ok {
+ return val
+ }
+ }
+ }
+ return ""
}
-// 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})
-//
-func (f *File) SetSheetRow(sheet, axis string, slice interface{}) {
- xlsx := f.workSheetReader(sheet)
- axis = f.mergeCellsParser(xlsx, axis)
- col := string(strings.Map(letterOnlyMapF, axis))
- row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- if err != nil {
- return
- }
- // Make sure 'slice' is a Ptr to Slice
- v := reflect.ValueOf(slice)
- if v.Kind() != reflect.Ptr {
- return
+// formattedValue provides a function to returns a value after formatted. If
+// it is possible to apply a format to the cell value, it will do so, if not
+// then an error will be returned, along with the raw value of the cell.
+func (f *File) formattedValue(s int, v string) string {
+ if s == 0 {
+ return v
}
- v = v.Elem()
- if v.Kind() != reflect.Slice {
- return
+ styleSheet := f.stylesReader()
+ ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
+ if ok != nil {
+ return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
}
+ return v
+}
- xAxis := row - 1
- yAxis := TitleToNumber(col)
-
- rows := xAxis + 1
- cell := yAxis + 1
-
- completeRow(xlsx, rows, cell)
- completeCol(xlsx, rows, cell)
+// prepareCellStyle provides a function to prepare style index of cell in
+// worksheet by given column index and style index.
+func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
+ if xlsx.Cols != nil && style == 0 {
+ for _, c := range xlsx.Cols.Col {
+ if c.Min <= col && col <= c.Max {
+ style = c.Style
+ }
+ }
+ }
+ return style
+}
- idx := 0
- for i := cell - 1; i < v.Len()+cell-1; i++ {
- c := ToAlphaString(i) + strconv.Itoa(row)
- f.SetCellValue(sheet, c, v.Index(idx).Interface())
- idx++
+// mergeCellsParser provides a function to check merged cells in worksheet by
+// given axis.
+func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
+ 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) {
+ axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
+ }
+ }
}
+ return axis
}
// checkCellInArea provides a function to determine if a given coordinate is
// within an area.
func checkCellInArea(cell, area string) bool {
- cell = strings.ToUpper(cell)
- area = strings.ToUpper(area)
+ col, row, err := CellNameToCoordinates(cell)
+ if err != nil {
+ panic(err)
+ }
- ref := strings.Split(area, ":")
- if len(ref) < 2 {
+ rng := strings.Split(area, ":")
+ if len(rng) != 2 {
return false
}
- from := ref[0]
- to := ref[1]
+ firstCol, firtsRow, _ := CellNameToCoordinates(rng[0])
+ lastCol, lastRow, _ := CellNameToCoordinates(rng[1])
- col, row := getCellColRow(cell)
- fromCol, fromRow := getCellColRow(from)
- toCol, toRow := getCellColRow(to)
+ return col >= firstCol && col <= lastCol && row >= firtsRow && row <= lastRow
+}
- return axisLowerOrEqualThan(fromCol, col) && axisLowerOrEqualThan(col, toCol) && axisLowerOrEqualThan(fromRow, row) && axisLowerOrEqualThan(row, toRow)
+// getSharedForumula find a cell contains the same formula as another cell,
+// the "shared" value can be used for the t attribute and the si attribute can
+// be used to refer to the cell containing the formula. Two formulas are
+// considered to be the same when their respective representations in
+// R1C1-reference notation, are the same.
+//
+// Note that this function not validate ref tag to check the cell if or not in
+// allow area, and always return origin shared formula.
+func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
+ for _, r := range xlsx.SheetData.Row {
+ for _, c := range r.C {
+ if c.F != nil && c.F.Ref != "" && c.F.T == STCellFormulaTypeShared && c.F.Si == si {
+ return c.F.Content
+ }
+ }
+ }
+ return ""
}
diff --git a/cell_test.go b/cell_test.go
index cb3d80e..d388c7f 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -9,7 +9,6 @@ import (
func TestCheckCellInArea(t *testing.T) {
expectedTrueCellInAreaList := [][2]string{
{"c2", "A1:AAZ32"},
- {"AA0", "Z0:AB1"},
{"B9", "A1:B9"},
{"C2", "C2:C2"},
}
@@ -18,7 +17,7 @@ func TestCheckCellInArea(t *testing.T) {
cell := expectedTrueCellInArea[0]
area := expectedTrueCellInArea[1]
- assert.True(t, checkCellInArea(cell, area),
+ assert.Truef(t, checkCellInArea(cell, area),
"Expected cell %v to be in area %v, got false\n", cell, area)
}
@@ -32,7 +31,11 @@ func TestCheckCellInArea(t *testing.T) {
cell := expectedFalseCellInArea[0]
area := expectedFalseCellInArea[1]
- assert.False(t, checkCellInArea(cell, area),
+ assert.Falsef(t, checkCellInArea(cell, area),
"Expected cell %v not to be inside of area %v, but got true\n", cell, area)
}
+
+ assert.Panics(t, func() {
+ checkCellInArea("AA0", "Z0:AB1")
+ })
}
diff --git a/chart.go b/chart.go
index f11fd55..c31995e 100644
--- a/chart.go
+++ b/chart.go
@@ -1240,14 +1240,14 @@ 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) {
- cell = strings.ToUpper(cell)
- fromCol := string(strings.Map(letterOnlyMapF, cell))
- fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
- row := fromRow - 1
- col := TitleToNumber(fromCol)
+ col, row := MustCellNameToCoordinates(cell)
+ colIdx := col - 1
+ rowIdx := row - 1
+
width = int(float64(width) * formatSet.XScale)
height = int(float64(height) * formatSet.YScale)
- colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
+ colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
+ f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height)
content, cNvPrID := f.drawingParser(drawingXML)
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = formatSet.Positioning
diff --git a/chart_test.go b/chart_test.go
index f3d7bdf..1dfc468 100644
--- a/chart_test.go
+++ b/chart_test.go
@@ -9,19 +9,46 @@ import (
)
func TestChartSize(t *testing.T) {
+ xlsx := NewFile()
+ sheet1 := xlsx.GetSheetName(1)
- var buffer bytes.Buffer
+ categories := map[string]string{
+ "A2": "Small",
+ "A3": "Normal",
+ "A4": "Large",
+ "B1": "Apple",
+ "C1": "Orange",
+ "D1": "Pear",
+ }
+ for cell, v := range categories {
+ xlsx.SetCellValue(sheet1, cell, v)
+ }
- categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
- values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
- xlsx := NewFile()
- for k, v := range categories {
- xlsx.SetCellValue("Sheet1", k, v)
+ values := map[string]int{
+ "B2": 2,
+ "C2": 3,
+ "D2": 3,
+ "B3": 5,
+ "C3": 2,
+ "D3": 4,
+ "B4": 6,
+ "C4": 7,
+ "D4": 8,
}
- for k, v := range values {
- xlsx.SetCellValue("Sheet1", k, v)
+ for cell, v := range values {
+ xlsx.SetCellValue(sheet1, cell, v)
}
- xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
+
+ xlsx.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+
+ `"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+
+ `{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+
+ `{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+
+ `"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
+
+ var (
+ buffer bytes.Buffer
+ )
+
// Save xlsx file by the given path.
err := xlsx.Write(&buffer)
if !assert.NoError(t, err) {
@@ -51,7 +78,8 @@ func TestChartSize(t *testing.T) {
t.FailNow()
}
- err = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+workdir.TwoCellAnchor[0].Content+"</decodeTwoCellAnchor>"), &anchor)
+ err = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+
+ workdir.TwoCellAnchor[0].Content+"</decodeTwoCellAnchor>"), &anchor)
if !assert.NoError(t, err) {
t.FailNow()
}
diff --git a/col.go b/col.go
index 1130c3a..131af1e 100644
--- a/col.go
+++ b/col.go
@@ -10,10 +10,7 @@
package excelize
import (
- "bytes"
"math"
- "strconv"
- "strings"
)
// Define the default cell size and EMU unit of measurement.
@@ -29,16 +26,19 @@ const (
//
// xlsx.GetColVisible("Sheet1", "D")
//
-func (f *File) GetColVisible(sheet, column string) bool {
+func (f *File) GetColVisible(sheet, col string) bool {
+ colNum := MustColumnNameToNumber(col)
+
xlsx := f.workSheetReader(sheet)
- col := TitleToNumber(strings.ToUpper(column)) + 1
- visible := true
if xlsx.Cols == nil {
- return visible
+ return true
}
+
+ visible := true
for c := range xlsx.Cols.Col {
- if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
- visible = !xlsx.Cols.Col[c].Hidden
+ colData := &xlsx.Cols.Col[c]
+ if colData.Min <= colNum && colNum <= colData.Max {
+ visible = !colData.Hidden
}
}
return visible
@@ -49,31 +49,31 @@ func (f *File) GetColVisible(sheet, column string) bool {
//
// xlsx.SetColVisible("Sheet1", "D", false)
//
-func (f *File) SetColVisible(sheet, column string, visible bool) {
- xlsx := f.workSheetReader(sheet)
- c := TitleToNumber(strings.ToUpper(column)) + 1
- col := xlsxCol{
- Min: c,
- Max: c,
+func (f *File) SetColVisible(sheet, col string, visible bool) {
+ colNum := MustColumnNameToNumber(col)
+ colData := xlsxCol{
+ Min: colNum,
+ Max: colNum,
Hidden: !visible,
CustomWidth: true,
}
+ xlsx := f.workSheetReader(sheet)
if xlsx.Cols == nil {
cols := xlsxCols{}
- cols.Col = append(cols.Col, col)
+ cols.Col = append(cols.Col, colData)
xlsx.Cols = &cols
return
}
for v := range xlsx.Cols.Col {
- if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
- col = xlsx.Cols.Col[v]
+ if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
+ colData = xlsx.Cols.Col[v]
}
}
- col.Min = c
- col.Max = c
- col.Hidden = !visible
- col.CustomWidth = true
- xlsx.Cols.Col = append(xlsx.Cols.Col, col)
+ colData.Min = colNum
+ colData.Max = colNum
+ colData.Hidden = !visible
+ colData.CustomWidth = true
+ xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
}
// GetColOutlineLevel provides a function to get outline level of a single
@@ -82,16 +82,17 @@ func (f *File) SetColVisible(sheet, column string, visible bool) {
//
// xlsx.GetColOutlineLevel("Sheet1", "D")
//
-func (f *File) GetColOutlineLevel(sheet, column string) uint8 {
+func (f *File) GetColOutlineLevel(sheet, col string) uint8 {
+ colNum := MustColumnNameToNumber(col)
xlsx := f.workSheetReader(sheet)
- col := TitleToNumber(strings.ToUpper(column)) + 1
level := uint8(0)
if xlsx.Cols == nil {
return level
}
for c := range xlsx.Cols.Col {
- if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
- level = xlsx.Cols.Col[c].OutlineLevel
+ colData := &xlsx.Cols.Col[c]
+ if colData.Min <= colNum && colNum <= colData.Max {
+ level = colData.OutlineLevel
}
}
return level
@@ -103,31 +104,31 @@ func (f *File) GetColOutlineLevel(sheet, column string) uint8 {
//
// xlsx.SetColOutlineLevel("Sheet1", "D", 2)
//
-func (f *File) SetColOutlineLevel(sheet, column string, level uint8) {
- xlsx := f.workSheetReader(sheet)
- c := TitleToNumber(strings.ToUpper(column)) + 1
- col := xlsxCol{
- Min: c,
- Max: c,
+func (f *File) SetColOutlineLevel(sheet, col string, level uint8) {
+ colNum := MustColumnNameToNumber(col)
+ colData := xlsxCol{
+ Min: colNum,
+ Max: colNum,
OutlineLevel: level,
CustomWidth: true,
}
+ xlsx := f.workSheetReader(sheet)
if xlsx.Cols == nil {
cols := xlsxCols{}
- cols.Col = append(cols.Col, col)
+ cols.Col = append(cols.Col, colData)
xlsx.Cols = &cols
return
}
for v := range xlsx.Cols.Col {
- if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
- col = xlsx.Cols.Col[v]
+ if xlsx.Cols.Col[v].Min <= colNum && colNum <= xlsx.Cols.Col[v].Max {
+ colData = xlsx.Cols.Col[v]
}
}
- col.Min = c
- col.Max = c
- col.OutlineLevel = level
- col.CustomWidth = true
- xlsx.Cols.Col = append(xlsx.Cols.Col, col)
+ colData.Min = colNum
+ colData.Max = colNum
+ colData.OutlineLevel = level
+ colData.CustomWidth = true
+ xlsx.Cols.Col = append(xlsx.Cols.Col, colData)
}
// SetColWidth provides a function to set the width of a single column or
@@ -141,11 +142,12 @@ func (f *File) SetColOutlineLevel(sheet, column string, level uint8) {
// }
//
func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
- min := TitleToNumber(strings.ToUpper(startcol)) + 1
- max := TitleToNumber(strings.ToUpper(endcol)) + 1
+ min := MustColumnNameToNumber(startcol)
+ max := MustColumnNameToNumber(endcol)
if min > max {
min, max = max, min
}
+
xlsx := f.workSheetReader(sheet)
col := xlsxCol{
Min: min,
@@ -214,38 +216,38 @@ func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
// xAbs # Absolute distance to left side of object.
// yAbs # Absolute distance to top side of object.
//
-func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
+func (f *File) positionObjectPixels(sheet string, col, row, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
xAbs := 0
yAbs := 0
// Calculate the absolute x offset of the top-left vertex.
- for colID := 1; colID <= colStart; colID++ {
+ for colID := 1; colID <= col; colID++ {
xAbs += f.getColWidth(sheet, colID)
}
xAbs += x1
// Calculate the absolute y offset of the top-left vertex.
// Store the column change to allow optimisations.
- for rowID := 1; rowID <= rowStart; rowID++ {
+ for rowID := 1; rowID <= row; rowID++ {
yAbs += f.getRowHeight(sheet, rowID)
}
yAbs += y1
// Adjust start column for offsets that are greater than the col width.
- for x1 >= f.getColWidth(sheet, colStart) {
- x1 -= f.getColWidth(sheet, colStart)
- colStart++
+ for x1 >= f.getColWidth(sheet, col) {
+ x1 -= f.getColWidth(sheet, col)
+ col++
}
// Adjust start row for offsets that are greater than the row height.
- for y1 >= f.getRowHeight(sheet, rowStart) {
- y1 -= f.getRowHeight(sheet, rowStart)
- rowStart++
+ for y1 >= f.getRowHeight(sheet, row) {
+ y1 -= f.getRowHeight(sheet, row)
+ row++
}
// Initialise end cell to the same as the start cell.
- colEnd := colStart
- rowEnd := rowStart
+ colEnd := col
+ rowEnd := row
width += x1
height += y1
@@ -265,7 +267,7 @@ func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, wi
// The end vertices are whatever is left from the width and height.
x2 := width
y2 := height
- return colStart, rowStart, xAbs, yAbs, colEnd, rowEnd, x2, y2
+ return col, row, xAbs, yAbs, colEnd, rowEnd, x2, y2
}
// getColWidth provides a function to get column width in pixels by given
@@ -289,13 +291,13 @@ 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, column string) float64 {
- col := TitleToNumber(strings.ToUpper(column)) + 1
+func (f *File) GetColWidth(sheet, col string) float64 {
+ colNum := MustColumnNameToNumber(col)
xlsx := f.workSheetReader(sheet)
if xlsx.Cols != nil {
var width float64
for _, v := range xlsx.Cols.Col {
- if v.Min <= col && col <= v.Max {
+ if v.Min <= colNum && colNum <= v.Max {
width = v.Width
}
}
@@ -312,9 +314,12 @@ func (f *File) GetColWidth(sheet, column string) float64 {
//
// xlsx.InsertCol("Sheet1", "C")
//
-func (f *File) InsertCol(sheet, column string) {
- col := TitleToNumber(strings.ToUpper(column))
- f.adjustHelper(sheet, col, -1, 1)
+func (f *File) InsertCol(sheet, col string) {
+ num, err := ColumnNameToNumber(col)
+ if err != nil {
+ panic(err)
+ }
+ f.adjustHelper(sheet, columns, num, 1)
}
// RemoveCol provides a function to remove single column by given worksheet
@@ -326,38 +331,23 @@ func (f *File) InsertCol(sheet, column string) {
// 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, column string) {
- xlsx := f.workSheetReader(sheet)
- for r := range xlsx.SheetData.Row {
- for k, v := range xlsx.SheetData.Row[r].C {
- axis := v.R
- col := string(strings.Map(letterOnlyMapF, axis))
- if col == column {
- xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C[:k], xlsx.SheetData.Row[r].C[k+1:]...)
- }
- }
+func (f *File) RemoveCol(sheet, col string) {
+ num, err := ColumnNameToNumber(col)
+ if err != nil {
+ panic(err) // Fail fast to avoid possible future side effects!
}
- col := TitleToNumber(strings.ToUpper(column))
- f.adjustHelper(sheet, col, -1, -1)
-}
-// completeCol provieds function to completion column element tags of XML in a
-// sheet.
-func completeCol(xlsx *xlsxWorksheet, row, cell int) {
- buffer := bytes.Buffer{}
- for r := range xlsx.SheetData.Row {
- if len(xlsx.SheetData.Row[r].C) < cell {
- start := len(xlsx.SheetData.Row[r].C)
- for iii := start; iii < cell; iii++ {
- buffer.WriteString(ToAlphaString(iii))
- buffer.WriteString(strconv.Itoa(r + 1))
- xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
+ xlsx := f.workSheetReader(sheet)
+ for rowIdx := range xlsx.SheetData.Row {
+ rowData := xlsx.SheetData.Row[rowIdx]
+ for colIdx, cellData := range rowData.C {
+ colName, _, _ := SplitCellName(cellData.R)
+ if colName == col {
+ rowData.C = append(rowData.C[:colIdx], rowData.C[colIdx+1:]...)
}
}
}
+ f.adjustHelper(sheet, columns, num, -1)
}
// convertColWidthToPixels provieds function to convert the width of a cell
diff --git a/comment.go b/comment.go
index 7fb1739..a94194d 100644
--- a/comment.go
+++ b/comment.go
@@ -115,10 +115,9 @@ func (f *File) AddComment(sheet, cell, format string) error {
// 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 := string(strings.Map(letterOnlyMapF, cell))
- row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
+ col, row := MustCellNameToCoordinates(cell)
+ yAxis := col - 1
xAxis := row - 1
- yAxis := TitleToNumber(col)
vml := f.VMLDrawing[drawingVML]
if vml == nil {
vml = &vmlDrawing{
diff --git a/date.go b/date.go
index 7dc5ef8..e550feb 100644
--- a/date.go
+++ b/date.go
@@ -14,31 +14,53 @@ import (
"time"
)
-// timeLocationUTC defined the UTC time location.
-var timeLocationUTC, _ = time.LoadLocation("UTC")
+const (
+ dayNanoseconds = 24 * time.Hour
+ maxDuration = 290 * 364 * dayNanoseconds
+)
-// timeToUTCTime provides a function to convert time to UTC time.
-func timeToUTCTime(t time.Time) time.Time {
- return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
-}
+var (
+ excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
+ excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
+)
// timeToExcelTime provides a function to convert time to Excel time.
func timeToExcelTime(t time.Time) float64 {
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime
- var excelTime float64
- var deltaDays int64
- excelTime = 0
- deltaDays = 290 * 364
- // check if UnixNano would be out of int64 range
- for t.Unix() > deltaDays*24*60*60 {
- // reduce by aprox. 290 years, which is max for int64 nanoseconds
- delta := time.Duration(deltaDays) * 24 * time.Hour
- excelTime = excelTime + float64(deltaDays)
- t = t.Add(-delta)
+
+ // 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")
+ }
+
+ if t.Before(excelMinTime1900) {
+ return 0.0
+ }
+
+ tt := t
+ diff := t.Sub(excelMinTime1900)
+ result := float64(0)
+
+ for diff >= maxDuration {
+ result += float64(maxDuration / dayNanoseconds)
+ tt = tt.Add(-maxDuration)
+ diff = tt.Sub(excelMinTime1900)
+ }
+
+ rem := diff % dayNanoseconds
+ result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
+
+ // Excel dates after 28th February 1900 are actually one day out.
+ // Excel behaves as though the date 29th February 1900 existed, which it didn't.
+ // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
+ // program that had the majority market share at the time; Lotus 1-2-3.
+ // https://www.myonlinetraininghub.com/excel-date-and-time
+ if t.After(excelBuggyPeriodStart) {
+ result += 1.0
}
- // finally add remainder of UnixNano to keep nano precision
- // and 25569 which is days between 1900 and 1970
- return excelTime + float64(t.UnixNano())/8.64e13 + 25569.0
+ return result
}
// shiftJulianToNoon provides a function to process julian date to noon.
diff --git a/date_test.go b/date_test.go
index a1bfbf4..709fb00 100644
--- a/date_test.go
+++ b/date_test.go
@@ -13,17 +13,40 @@ type dateTest struct {
GoValue time.Time
}
+var trueExpectedDateList = []dateTest{
+ {0.0000000000000000, time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)},
+ {25569.000000000000, time.Unix(0, 0).UTC()},
+
+ // Expected values extracted from real xlsx file
+ {1.0000000000000000, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
+ {1.0000115740740740, time.Date(1900, time.January, 1, 0, 0, 1, 0, time.UTC)},
+ {1.0006944444444446, time.Date(1900, time.January, 1, 0, 1, 0, 0, time.UTC)},
+ {1.0416666666666667, time.Date(1900, time.January, 1, 1, 0, 0, 0, time.UTC)},
+ {2.0000000000000000, time.Date(1900, time.January, 2, 0, 0, 0, 0, time.UTC)},
+ {43269.000000000000, time.Date(2018, time.June, 18, 0, 0, 0, 0, time.UTC)},
+ {43542.611111111109, time.Date(2019, time.March, 18, 14, 40, 0, 0, time.UTC)},
+ {401769.00000000000, time.Date(3000, time.January, 1, 0, 0, 0, 0, time.UTC)},
+}
+
func TestTimeToExcelTime(t *testing.T) {
- trueExpectedInputList := []dateTest{
- {0.0, time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)},
- {25569.0, time.Unix(0, 0)},
- {43269.0, time.Date(2018, 6, 18, 0, 0, 0, 0, time.UTC)},
- {401769.0, time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)},
+ 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),
+ "Time: %s", test.GoValue.String())
+ })
}
+}
- for i, test := range trueExpectedInputList {
+func TestTimeToExcelTime_Timezone(t *testing.T) {
+ msk, err := time.LoadLocation("Europe/Moscow")
+ if !assert.NoError(t, err) {
+ t.FailNow()
+ }
+ for i, test := range trueExpectedDateList {
t.Run(fmt.Sprintf("TestData%d", i+1), func(t *testing.T) {
- assert.Equal(t, test.ExcelValue, timeToExcelTime(test.GoValue))
+ assert.Panics(t, func() {
+ timeToExcelTime(test.GoValue.In(msk))
+ }, "Time: %s", test.GoValue.String())
})
}
}
@@ -43,3 +66,9 @@ func TestTimeFromExcelTime(t *testing.T) {
})
}
}
+
+func TestTimeFromExcelTime_1904(t *testing.T) {
+ shiftJulianToNoon(1, -0.6)
+ timeFromExcelTime(61, true)
+ timeFromExcelTime(62, true)
+}
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..4dec815
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,17 @@
+package excelize
+
+import (
+ "fmt"
+)
+
+func newInvalidColumnNameError(col string) error {
+ return fmt.Errorf("invalid column name %q", col)
+}
+
+func newInvalidRowNumberError(row int) error {
+ return fmt.Errorf("invalid row number %d", row)
+}
+
+func newInvalidCellNameError(cell string) error {
+ return fmt.Errorf("invalid cell name %q", cell)
+}
diff --git a/errors_test.go b/errors_test.go
new file mode 100644
index 0000000..89d241c
--- /dev/null
+++ b/errors_test.go
@@ -0,0 +1,21 @@
+package excelize
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewInvalidColNameError(t *testing.T) {
+ assert.EqualError(t, newInvalidColumnNameError("A"), "invalid column name \"A\"")
+ assert.EqualError(t, newInvalidColumnNameError(""), "invalid column name \"\"")
+}
+
+func TestNewInvalidRowNumberError(t *testing.T) {
+ assert.EqualError(t, newInvalidRowNumberError(0), "invalid row number 0")
+}
+
+func TestNewInvalidCellNameError(t *testing.T) {
+ assert.EqualError(t, newInvalidCellNameError("A"), "invalid cell name \"A\"")
+ assert.EqualError(t, newInvalidCellNameError(""), "invalid cell name \"\"")
+}
diff --git a/excelize.go b/excelize.go
index feb41cb..7a50460 100644
--- a/excelize.go
+++ b/excelize.go
@@ -206,235 +206,16 @@ func (f *File) UpdateLinkedValue() {
}
}
-// adjustHelper provides a function to adjust rows and columns dimensions,
-// hyperlinks, merged cells and auto filter when inserting or deleting rows or
-// columns.
-//
-// sheet: Worksheet name that we're editing
-// column: Index number of the column we're inserting/deleting before
-// row: Index number of the row we're inserting/deleting before
-// offset: Number of rows/column to insert/delete negative values indicate deletion
-//
-// TODO: adjustCalcChain, adjustPageBreaks, adjustComments,
-// adjustDataValidations, adjustProtectedCells
-//
-func (f *File) adjustHelper(sheet string, column, row, offset int) {
- xlsx := f.workSheetReader(sheet)
- f.adjustRowDimensions(xlsx, row, offset)
- f.adjustColDimensions(xlsx, column, offset)
- f.adjustHyperlinks(sheet, column, row, offset)
- f.adjustMergeCells(xlsx, column, row, offset)
- f.adjustAutoFilter(xlsx, column, row, offset)
- checkSheet(xlsx)
- checkRow(xlsx)
-}
-
-// adjustColDimensions provides a function to update column dimensions when
-// inserting or deleting rows or columns.
-func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, column, offset int) {
- for i, r := range xlsx.SheetData.Row {
- for k, v := range r.C {
- axis := v.R
- col := string(strings.Map(letterOnlyMapF, axis))
- row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- yAxis := TitleToNumber(col)
- if yAxis >= column && column != -1 {
- xlsx.SheetData.Row[i].C[k].R = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
- }
- }
- }
-}
-
-// adjustRowDimensions provides a function to update row dimensions when
-// inserting or deleting rows or columns.
-func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, rowIndex, offset int) {
- if rowIndex == -1 {
- return
- }
- for i, r := range xlsx.SheetData.Row {
- if r.R >= rowIndex {
- f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], r.R+offset)
- }
- }
-}
-
-// ajustSingleRowDimensions provides a function to ajust single row dimensions.
-func (f *File) ajustSingleRowDimensions(r *xlsxRow, row int) {
- r.R = row
- for i, col := range r.C {
- r.C[i].R = string(strings.Map(letterOnlyMapF, col.R)) + strconv.Itoa(r.R)
- }
-}
-
-// adjustHyperlinks provides a function to update hyperlinks when inserting or
-// deleting rows or columns.
-func (f *File) adjustHyperlinks(sheet string, column, rowIndex, offset int) {
+// GetMergeCells provides a function to get all merged cells from a worksheet currently.
+func (f *File) GetMergeCells(sheet string) []MergeCell {
xlsx := f.workSheetReader(sheet)
- // order is important
- if xlsx.Hyperlinks != nil && offset < 0 {
- for i, v := range xlsx.Hyperlinks.Hyperlink {
- axis := v.Ref
- col := string(strings.Map(letterOnlyMapF, axis))
- row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- yAxis := TitleToNumber(col)
- if row == rowIndex || yAxis == column {
- f.deleteSheetRelationships(sheet, v.RID)
- if len(xlsx.Hyperlinks.Hyperlink) > 1 {
- xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:i], xlsx.Hyperlinks.Hyperlink[i+1:]...)
- } else {
- xlsx.Hyperlinks = nil
- }
- }
- }
- }
+ var mergeCells []MergeCell
- if xlsx.Hyperlinks != nil {
- for i, v := range xlsx.Hyperlinks.Hyperlink {
- axis := v.Ref
- col := string(strings.Map(letterOnlyMapF, axis))
- row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
- xAxis := row + offset
- yAxis := TitleToNumber(col)
- if rowIndex != -1 && row >= rowIndex {
- xlsx.Hyperlinks.Hyperlink[i].Ref = col + strconv.Itoa(xAxis)
- }
- if column != -1 && yAxis >= column {
- xlsx.Hyperlinks.Hyperlink[i].Ref = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
- }
- }
- }
-}
-
-// adjustMergeCellsHelper provides a function to update merged cells when
-// inserting or deleting rows or columns.
-func (f *File) adjustMergeCellsHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
if xlsx.MergeCells != nil {
- for k, v := range xlsx.MergeCells.Cells {
- beg := strings.Split(v.Ref, ":")[0]
- end := strings.Split(v.Ref, ":")[1]
-
- begcol := string(strings.Map(letterOnlyMapF, beg))
- begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
- begxAxis := begrow + offset
- begyAxis := TitleToNumber(begcol)
-
- endcol := string(strings.Map(letterOnlyMapF, end))
- endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
- endxAxis := endrow + offset
- endyAxis := TitleToNumber(endcol)
-
- if rowIndex != -1 {
- if begrow > 1 && begrow >= rowIndex {
- beg = begcol + strconv.Itoa(begxAxis)
- }
- if endrow > 1 && endrow >= rowIndex {
- end = endcol + strconv.Itoa(endxAxis)
- }
- }
-
- if column != -1 {
- if begyAxis >= column {
- beg = ToAlphaString(begyAxis+offset) + strconv.Itoa(endrow)
- }
- if endyAxis >= column {
- end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
- }
- }
-
- xlsx.MergeCells.Cells[k].Ref = beg + ":" + end
- }
- }
-}
-
-// adjustMergeCells provides a function to update merged cells when inserting
-// or deleting rows or columns.
-func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
- f.adjustMergeCellsHelper(xlsx, column, rowIndex, offset)
-
- if xlsx.MergeCells != nil && offset < 0 {
- for k, v := range xlsx.MergeCells.Cells {
- beg := strings.Split(v.Ref, ":")[0]
- end := strings.Split(v.Ref, ":")[1]
- if beg == end {
- xlsx.MergeCells.Count += offset
- if len(xlsx.MergeCells.Cells) > 1 {
- xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:k], xlsx.MergeCells.Cells[k+1:]...)
- } else {
- xlsx.MergeCells = nil
- }
- }
- }
- }
-}
-
-// adjustAutoFilter provides a function to update the auto filter when
-// inserting or deleting rows or columns.
-func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
- f.adjustAutoFilterHelper(xlsx, column, rowIndex, offset)
-
- if xlsx.AutoFilter != nil {
- beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
- end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
+ mergeCells = make([]MergeCell, 0, len(xlsx.MergeCells.Cells))
- begcol := string(strings.Map(letterOnlyMapF, beg))
- begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
- begxAxis := begrow + offset
-
- endcol := string(strings.Map(letterOnlyMapF, end))
- endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
- endxAxis := endrow + offset
- endyAxis := TitleToNumber(endcol)
-
- if rowIndex != -1 {
- if begrow >= rowIndex {
- beg = begcol + strconv.Itoa(begxAxis)
- }
- if endrow >= rowIndex {
- end = endcol + strconv.Itoa(endxAxis)
- }
- }
-
- if column != -1 && endyAxis >= column {
- end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
- }
- xlsx.AutoFilter.Ref = beg + ":" + end
- }
-}
-
-// adjustAutoFilterHelper provides a function to update the auto filter when
-// inserting or deleting rows or columns.
-func (f *File) adjustAutoFilterHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
- if xlsx.AutoFilter != nil {
- beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
- end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
-
- begcol := string(strings.Map(letterOnlyMapF, beg))
- begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
- begyAxis := TitleToNumber(begcol)
-
- endcol := string(strings.Map(letterOnlyMapF, end))
- endyAxis := TitleToNumber(endcol)
- endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
-
- if (begrow == rowIndex && offset < 0) || (column == begyAxis && column == endyAxis) {
- xlsx.AutoFilter = nil
- for i, r := range xlsx.SheetData.Row {
- if begrow < r.R && r.R <= endrow {
- xlsx.SheetData.Row[i].Hidden = false
- }
- }
- }
- }
-}
-
-// GetMergeCells provides a function to get all merged cells from a worksheet currently.
-func (f *File) GetMergeCells(sheet string) []MergeCell {
- mergeCells := []MergeCell{}
-
- xlsx := f.workSheetReader(sheet)
- if xlsx.MergeCells != nil {
- for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
+ 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)})
diff --git a/excelize_test.go b/excelize_test.go
index 47b9561..694f505 100644
--- a/excelize_test.go
+++ b/excelize_test.go
@@ -35,13 +35,22 @@ func TestOpenFile(t *testing.T) {
t.Log("\r\n")
}
xlsx.UpdateLinkedValue()
+
xlsx.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(100.1588), 'f', -1, 32))
xlsx.SetCellDefault("Sheet2", "A1", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64))
+
// Test set cell value with illegal row number.
- xlsx.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64))
+ assert.Panics(t, func() {
+ xlsx.SetCellDefault("Sheet2", "A", strconv.FormatFloat(float64(-100.1588), 'f', -1, 64))
+ })
+
xlsx.SetCellInt("Sheet2", "A1", 100)
+
// Test set cell integer value with illegal row number.
- xlsx.SetCellInt("Sheet2", "A", 100)
+ assert.Panics(t, func() {
+ xlsx.SetCellInt("Sheet2", "A", 100)
+ })
+
xlsx.SetCellStr("Sheet2", "C11", "Knowns")
// Test max characters in a cell.
xlsx.SetCellStr("Sheet2", "D11", strings.Repeat("c", 32769))
@@ -51,23 +60,38 @@ func TestOpenFile(t *testing.T) {
xlsx.SetCellInt("Sheet3", "A23", 10)
xlsx.SetCellStr("Sheet3", "b230", "10")
xlsx.SetCellStr("Sheet10", "b230", "10")
+
// Test set cell string value with illegal row number.
- xlsx.SetCellStr("Sheet10", "A", "10")
+ assert.Panics(t, func() {
+ xlsx.SetCellStr("Sheet10", "A", "10")
+ })
+
xlsx.SetActiveSheet(2)
// Test get cell formula with given rows number.
xlsx.GetCellFormula("Sheet1", "B19")
// Test get cell formula with illegal worksheet name.
xlsx.GetCellFormula("Sheet2", "B20")
- // Test get cell formula with illegal rows number.
xlsx.GetCellFormula("Sheet1", "B20")
- xlsx.GetCellFormula("Sheet1", "B")
+
+ // Test get cell formula with illegal rows number.
+ assert.Panics(t, func() {
+ xlsx.GetCellFormula("Sheet1", "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.
- xlsx.GetCellValue("Sheet2", "a-1")
- xlsx.GetCellValue("Sheet2", "A")
+ assert.Panics(t, func() {
+ xlsx.GetCellValue("Sheet2", "a-1")
+ })
+
+ assert.Panics(t, func() {
+ xlsx.GetCellValue("Sheet2", "A")
+ })
+
// Test read cell value with given lowercase column number.
xlsx.GetCellValue("Sheet2", "a5")
xlsx.GetCellValue("Sheet2", "C11")
@@ -92,10 +116,7 @@ func TestOpenFile(t *testing.T) {
xlsx.SetCellValue("Sheet2", "F15", uint64(1<<32-1))
xlsx.SetCellValue("Sheet2", "F16", true)
xlsx.SetCellValue("Sheet2", "F17", complex64(5+10i))
- t.Log(letterOnlyMapF('x'))
- shiftJulianToNoon(1, -0.6)
- timeFromExcelTime(61, true)
- timeFromExcelTime(62, true)
+
// Test boolean write
booltest := []struct {
value bool
@@ -108,8 +129,14 @@ func TestOpenFile(t *testing.T) {
xlsx.SetCellValue("Sheet2", "F16", test.value)
assert.Equal(t, test.expected, xlsx.GetCellValue("Sheet2", "F16"))
}
+
xlsx.SetCellValue("Sheet2", "G2", nil)
- xlsx.SetCellValue("Sheet2", "G4", time.Now())
+
+ assert.Panics(t, func() {
+ xlsx.SetCellValue("Sheet2", "G4", time.Now())
+ })
+
+ xlsx.SetCellValue("Sheet2", "G4", time.Now().UTC())
// 02:46:40
xlsx.SetCellValue("Sheet2", "G5", time.Duration(1e13))
// Test completion column.
@@ -298,8 +325,15 @@ func TestSetCellHyperLink(t *testing.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")
- xlsx.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", "")
- xlsx.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location")
+
+ assert.Panics(t, func() {
+ xlsx.SetCellHyperLink("Sheet2", "C3", "Sheet1!D8", "")
+ })
+
+ assert.Panics(t, func() {
+ xlsx.SetCellHyperLink("Sheet2", "", "Sheet1!D60", "Location")
+ })
+
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellHyperLink.xlsx")))
}
@@ -309,9 +343,11 @@ func TestGetCellHyperLink(t *testing.T) {
t.FailNow()
}
- link, target := xlsx.GetCellHyperLink("Sheet1", "")
- t.Log(link, target)
- link, target = xlsx.GetCellHyperLink("Sheet1", "A22")
+ assert.Panics(t, func() {
+ xlsx.GetCellHyperLink("Sheet1", "")
+ })
+
+ link, target := xlsx.GetCellHyperLink("Sheet1", "A22")
t.Log(link, target)
link, target = xlsx.GetCellHyperLink("Sheet2", "D6")
t.Log(link, target)
@@ -327,8 +363,12 @@ func TestSetCellFormula(t *testing.T) {
xlsx.SetCellFormula("Sheet1", "B19", "SUM(Sheet2!D2,Sheet2!D11)")
xlsx.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)")
+
// Test set cell formula with illegal rows number.
- xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)")
+ assert.Panics(t, func() {
+ xlsx.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)")
+ })
+
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx")))
xlsx, err = OpenFile(filepath.Join("test", "CalcChain.xlsx"))
@@ -408,51 +448,39 @@ func TestGetMergeCells(t *testing.T) {
value string
start string
end string
- }{
- {
- value: "A1",
- start: "A1",
- end: "B1",
- },
- {
- value: "A2",
- start: "A2",
- end: "A3",
- },
- {
- value: "A4",
- start: "A4",
- end: "B5",
- },
- {
- value: "A7",
- start: "A7",
- end: "C10",
- },
- }
+ }{{
+ value: "A1",
+ start: "A1",
+ end: "B1",
+ }, {
+ value: "A2",
+ start: "A2",
+ end: "A3",
+ }, {
+ value: "A4",
+ start: "A4",
+ end: "B5",
+ }, {
+ value: "A7",
+ start: "A7",
+ end: "C10",
+ }}
xlsx, err := OpenFile(filepath.Join("test", "MergeCell.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
+ sheet1 := xlsx.GetSheetName(1)
- mergeCells := xlsx.GetMergeCells("Sheet1")
- if len(mergeCells) != len(wants) {
- t.Fatalf("Expected count of merge cells %d, but got %d\n", len(wants), len(mergeCells))
+ mergeCells := xlsx.GetMergeCells(sheet1)
+ if !assert.Len(t, mergeCells, len(wants)) {
+ t.FailNow()
}
for i, m := range mergeCells {
- if wants[i].value != m.GetCellValue() {
- t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].value, m.GetCellValue())
- }
-
- if wants[i].start != m.GetStartAxis() {
- t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].start, m.GetStartAxis())
- }
-
- if wants[i].end != m.GetEndAxis() {
- t.Fatalf("Expected merged cell value %s, but got %s\n", wants[i].end, m.GetEndAxis())
- }
+ assert.Equal(t, wants[i].value, m.GetCellValue())
+ assert.Equal(t, wants[i].start, m.GetStartAxis())
+ assert.Equal(t, wants[i].end, m.GetEndAxis())
}
}
@@ -469,11 +497,20 @@ func TestSetCellStyleAlignment(t *testing.T) {
}
xlsx.SetCellStyle("Sheet1", "A22", "A22", style)
+
// Test set cell style with given illegal rows number.
- xlsx.SetCellStyle("Sheet1", "A", "A22", style)
- xlsx.SetCellStyle("Sheet1", "A22", "A", style)
+ assert.Panics(t, func() {
+ xlsx.SetCellStyle("Sheet1", "A", "A22", style)
+ })
+
+ assert.Panics(t, func() {
+ xlsx.SetCellStyle("Sheet1", "A22", "A", style)
+ })
+
// Test get cell style with given illegal rows number.
- xlsx.GetCellStyle("Sheet1", "A")
+ assert.Panics(t, func() {
+ xlsx.GetCellStyle("Sheet1", "A")
+ })
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetCellStyleAlignment.xlsx")))
}
@@ -782,46 +819,48 @@ func TestGetPicture(t *testing.T) {
}
file, raw := xlsx.GetPicture("Sheet1", "F21")
- if file == "" {
- err = ioutil.WriteFile(file, raw, 0644)
- if !assert.NoError(t, err) {
- t.FailNow()
- }
+ if !assert.NotEmpty(t, file) || !assert.NotEmpty(t, raw) ||
+ !assert.NoError(t, ioutil.WriteFile(file, raw, 0644)) {
+
+ t.FailNow()
}
// Try to get picture from a worksheet that doesn't contain any images.
file, raw = xlsx.GetPicture("Sheet3", "I9")
- if file != "" {
- err = ioutil.WriteFile(file, raw, 0644)
- if !assert.NoError(t, err) {
- t.FailNow()
- }
- }
+ 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")
- t.Log(file, len(raw))
+ assert.Empty(t, file)
+ assert.Empty(t, raw)
+
xlsx.getDrawingRelationships("xl/worksheets/_rels/sheet1.xml.rels", "rId8")
xlsx.getDrawingRelationships("", "")
xlsx.getSheetRelationshipsTargetByID("", "")
xlsx.deleteSheetRelationships("", "")
// Try to get picture from a local storage file.
- assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx")))
+ if !assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) {
+ t.FailNow()
+ }
+
xlsx, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
+
file, raw = xlsx.GetPicture("Sheet1", "F21")
- if file == "" {
- err = ioutil.WriteFile(file, raw, 0644)
- if !assert.NoError(t, err) {
- t.FailNow()
- }
+ if !assert.NotEmpty(t, file) || !assert.NotEmpty(t, raw) ||
+ !assert.NoError(t, ioutil.WriteFile(file, raw, 0644)) {
+
+ t.FailNow()
}
// Try to get picture from a local storage file that doesn't contain an image.
file, raw = xlsx.GetPicture("Sheet1", "F22")
- t.Log(file, len(raw))
+ assert.Empty(t, file)
+ assert.Empty(t, raw)
}
func TestSheetVisibility(t *testing.T) {
@@ -838,21 +877,6 @@ func TestSheetVisibility(t *testing.T) {
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSheetVisibility.xlsx")))
}
-func TestRowVisibility(t *testing.T) {
- xlsx, err := prepareTestBook1()
- if !assert.NoError(t, err) {
- t.FailNow()
- }
-
- xlsx.SetRowVisible("Sheet3", 2, false)
- xlsx.SetRowVisible("Sheet3", 2, true)
- xlsx.SetRowVisible("Sheet3", 0, true)
- xlsx.GetRowVisible("Sheet3", 2)
- xlsx.GetRowVisible("Sheet3", 0)
-
- assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
-}
-
func TestColumnVisibility(t *testing.T) {
t.Run("TestBook1", func(t *testing.T) {
xlsx, err := prepareTestBook1()
@@ -1065,38 +1089,37 @@ func TestAddChart(t *testing.T) {
func TestInsertCol(t *testing.T) {
xlsx := NewFile()
- for j := 1; j <= 10; j++ {
- for i := 0; i <= 10; i++ {
- axis := ToAlphaString(i) + strconv.Itoa(j)
- xlsx.SetCellStr("Sheet1", axis, axis)
- }
- }
- xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
- xlsx.MergeCell("Sheet1", "A1", "C3")
- err := xlsx.AutoFilter("Sheet1", "A2", "B2", `{"column":"B","expression":"x != blanks"}`)
+ sheet1 := xlsx.GetSheetName(1)
+
+ fillCells(xlsx, sheet1, 10, 10)
+
+ xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
+ xlsx.MergeCell(sheet1, "A1", "C3")
+
+ err := xlsx.AutoFilter(sheet1, "A2", "B2", `{"column":"B","expression":"x != blanks"}`)
if !assert.NoError(t, err) {
t.FailNow()
}
- xlsx.InsertCol("Sheet1", "A")
+ xlsx.InsertCol(sheet1, "A")
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestInsertCol.xlsx")))
}
func TestRemoveCol(t *testing.T) {
xlsx := NewFile()
- for j := 1; j <= 10; j++ {
- for i := 0; i <= 10; i++ {
- axis := ToAlphaString(i) + strconv.Itoa(j)
- xlsx.SetCellStr("Sheet1", axis, axis)
- }
- }
- xlsx.SetCellHyperLink("Sheet1", "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
- xlsx.SetCellHyperLink("Sheet1", "C5", "https://github.com", "External")
- xlsx.MergeCell("Sheet1", "A1", "B1")
- xlsx.MergeCell("Sheet1", "A2", "B2")
- xlsx.RemoveCol("Sheet1", "A")
- xlsx.RemoveCol("Sheet1", "A")
+ sheet1 := xlsx.GetSheetName(1)
+
+ fillCells(xlsx, sheet1, 10, 15)
+
+ xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
+ xlsx.SetCellHyperLink(sheet1, "C5", "https://github.com", "External")
+
+ xlsx.MergeCell(sheet1, "A1", "B1")
+ xlsx.MergeCell(sheet1, "A2", "B2")
+
+ xlsx.RemoveCol(sheet1, "A")
+ xlsx.RemoveCol(sheet1, "A")
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveCol.xlsx")))
}
@@ -1117,11 +1140,10 @@ func TestSetPane(t *testing.T) {
func TestConditionalFormat(t *testing.T) {
xlsx := NewFile()
- for j := 1; j <= 10; j++ {
- for i := 0; i <= 15; i++ {
- xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
- }
- }
+ sheet1 := xlsx.GetSheetName(1)
+
+ fillCells(xlsx, sheet1, 10, 15)
+
var format1, format2, format3 int
var err error
// Rose format for bad conditional.
@@ -1143,31 +1165,31 @@ func TestConditionalFormat(t *testing.T) {
}
// Color scales: 2 color.
- xlsx.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
+ xlsx.SetConditionalFormat(sheet1, "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)
// Color scales: 3 color.
- xlsx.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
+ xlsx.SetConditionalFormat(sheet1, "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)
// Hightlight cells rules: between...
- xlsx.SetConditionalFormat("Sheet1", "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
+ xlsx.SetConditionalFormat(sheet1, "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))
// Hightlight cells rules: Greater Than...
- xlsx.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))
+ xlsx.SetConditionalFormat(sheet1, "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))
// Hightlight cells rules: Equal To...
- xlsx.SetConditionalFormat("Sheet1", "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))
+ xlsx.SetConditionalFormat(sheet1, "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))
// Hightlight cells rules: Not Equal To...
- xlsx.SetConditionalFormat("Sheet1", "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))
+ xlsx.SetConditionalFormat(sheet1, "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))
// Hightlight cells rules: Duplicate Values...
- xlsx.SetConditionalFormat("Sheet1", "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))
+ xlsx.SetConditionalFormat(sheet1, "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))
// Top/Bottom rules: Top 10%.
- xlsx.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
+ xlsx.SetConditionalFormat(sheet1, "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))
// Top/Bottom rules: Above Average...
- xlsx.SetConditionalFormat("Sheet1", "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))
+ xlsx.SetConditionalFormat(sheet1, "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))
// Top/Bottom rules: Below Average...
- xlsx.SetConditionalFormat("Sheet1", "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
+ xlsx.SetConditionalFormat(sheet1, "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))
// Data Bars: Gradient Fill.
- xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
// Use a formula to determine which cells to format.
- xlsx.SetConditionalFormat("Sheet1", "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))
+ xlsx.SetConditionalFormat(sheet1, "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))
// Test set invalid format set in conditional format
- xlsx.SetConditionalFormat("Sheet1", "L1:L10", "")
+ xlsx.SetConditionalFormat(sheet1, "L1:L10", "")
err = xlsx.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
if !assert.NoError(t, err) {
@@ -1175,9 +1197,9 @@ func TestConditionalFormat(t *testing.T) {
}
// Set conditional format with illegal valid type.
- xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
// Set conditional format with illegal criteria type.
- xlsx.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
+ xlsx.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)
// Set conditional format with file without dxfs element shold not return error.
xlsx, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
@@ -1193,11 +1215,9 @@ func TestConditionalFormat(t *testing.T) {
func TestConditionalFormatError(t *testing.T) {
xlsx := NewFile()
- for j := 1; j <= 10; j++ {
- for i := 0; i <= 15; i++ {
- xlsx.SetCellInt("Sheet1", ToAlphaString(i)+strconv.Itoa(j), j)
- }
- }
+ sheet1 := xlsx.GetSheetName(1)
+
+ fillCells(xlsx, sheet1, 10, 15)
// Set conditional format with illegal JSON string should return error
_, err := xlsx.NewConditionalStyle("")
@@ -1206,15 +1226,6 @@ func TestConditionalFormatError(t *testing.T) {
}
}
-func TestTitleToNumber(t *testing.T) {
- assert.Equal(t, 0, TitleToNumber("A"))
- assert.Equal(t, 25, TitleToNumber("Z"))
- assert.Equal(t, 26, TitleToNumber("AA"))
- assert.Equal(t, 36, TitleToNumber("AK"))
- assert.Equal(t, 36, TitleToNumber("ak"))
- assert.Equal(t, 51, TitleToNumber("AZ"))
-}
-
func TestSharedStrings(t *testing.T) {
xlsx, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
if !assert.NoError(t, err) {
@@ -1229,10 +1240,19 @@ func TestSetSheetRow(t *testing.T) {
t.FailNow()
}
- xlsx.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now()})
- xlsx.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2})
- xlsx.SetSheetRow("Sheet1", "B27", []interface{}{})
- xlsx.SetSheetRow("Sheet1", "B27", &xlsx)
+ 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.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx")))
}
@@ -1245,10 +1265,17 @@ func TestOutlineLevel(t *testing.T) {
xlsx.GetColOutlineLevel("Shee2", "A")
xlsx.SetColWidth("Sheet2", "A", "D", 13)
xlsx.SetColOutlineLevel("Sheet2", "B", 2)
- xlsx.SetRowOutlineLevel("Sheet1", 2, 1)
- xlsx.SetRowOutlineLevel("Sheet1", 0, 1)
- xlsx.GetRowOutlineLevel("Sheet1", 2)
- xlsx.GetRowOutlineLevel("Sheet1", 0)
+ xlsx.SetRowOutlineLevel("Sheet1", 2, 250)
+
+ assert.Panics(t, func() {
+ xlsx.SetRowOutlineLevel("Sheet1", 0, 1)
+ })
+
+ assert.Equal(t, uint8(250), xlsx.GetRowOutlineLevel("Sheet1", 2))
+
+ assert.Panics(t, func() {
+ xlsx.GetRowOutlineLevel("Sheet1", 0)
+ })
err := xlsx.SaveAs(filepath.Join("test", "TestOutlineLevel.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
@@ -1258,7 +1285,6 @@ func TestOutlineLevel(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
-
xlsx.SetColOutlineLevel("Sheet2", "B", 2)
}
@@ -1388,3 +1414,12 @@ func prepareTestBook4() (*File, error) {
return xlsx, nil
}
+
+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)
+ xlsx.SetCellStr(sheet, cell, cell)
+ }
+ }
+}
diff --git a/lib.go b/lib.go
index 30a20e0..c33c934 100644
--- a/lib.go
+++ b/lib.go
@@ -12,11 +12,11 @@ package excelize
import (
"archive/zip"
"bytes"
+ "fmt"
"io"
"log"
"strconv"
"strings"
- "unicode"
)
// ReadZipReader can be used to read an XLSX in memory without touching the
@@ -64,116 +64,188 @@ func readFile(file *zip.File) []byte {
return buff.Bytes()
}
-// ToAlphaString provides a function to convert integer to Excel sheet column
-// title. For example convert 36 to column title AK:
+// SplitCellName splits cell name to column name and row number.
//
-// excelize.ToAlphaString(36)
+// Example:
//
-func ToAlphaString(value int) string {
- if value < 0 {
- return ""
+// excelize.SplitCellName("AK74") // return "AK", 74, nil
+//
+func SplitCellName(cell string) (string, int, error) {
+ alpha := func(r rune) bool {
+ return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z')
+ }
+
+ if strings.IndexFunc(cell, alpha) == 0 {
+ i := strings.LastIndexFunc(cell, alpha)
+ if i >= 0 && i < len(cell)-1 {
+ col, rowstr := cell[:i+1], cell[i+1:]
+ if row, err := strconv.Atoi(rowstr); err == nil && row > 0 {
+ return col, row, nil
+ }
+ }
+ }
+ return "", -1, newInvalidCellNameError(cell)
+}
+
+// JoinCellName joins cell name from column name and row number
+func JoinCellName(col string, row int) (string, error) {
+ normCol := strings.Map(func(rune rune) rune {
+ switch {
+ case 'A' <= rune && rune <= 'Z':
+ return rune
+ case 'a' <= rune && rune <= 'z':
+ return rune - 32
+ }
+ return -1
+ }, col)
+ if len(col) == 0 || len(col) != len(normCol) {
+ return "", newInvalidColumnNameError(col)
}
- var ans string
- i := value + 1
- for i > 0 {
- ans = string((i-1)%26+65) + ans
- i = (i - 1) / 26
+ if row < 1 {
+ return "", newInvalidRowNumberError(row)
}
- return ans
+ return fmt.Sprintf("%s%d", normCol, row), nil
}
-// TitleToNumber provides a function to convert Excel sheet column title to
-// int (this function doesn't do value check currently). For example convert
-// AK and ak to column title 36:
+// ColumnNameToNumber provides a function to convert Excel sheet
+// column name to int. Column name case insencitive
+// Function returns error if column name incorrect.
//
-// excelize.TitleToNumber("AK")
-// excelize.TitleToNumber("ak")
+// Example:
//
-func TitleToNumber(s string) int {
- weight := 1
- sum := 0
- for i := len(s) - 1; i >= 0; i-- {
- ch := s[i]
- if ch >= 'a' && ch <= 'z' {
- ch -= 32
+// excelize.ColumnNameToNumber("AK") // returns 37, nil
+//
+func ColumnNameToNumber(name string) (int, error) {
+ if len(name) == 0 {
+ return -1, newInvalidColumnNameError(name)
+ }
+ col := 0
+ multi := 1
+ for i := len(name) - 1; i >= 0; i-- {
+ r := name[i]
+ if r >= 'A' && r <= 'Z' {
+ col += int(r-'A'+1) * multi
+ } else if r >= 'a' && r <= 'z' {
+ col += int(r-'a'+1) * multi
+ } else {
+ return -1, newInvalidColumnNameError(name)
}
- sum += int(ch-'A'+1) * weight
- weight *= 26
+ multi *= 26
}
- return sum - 1
+ return col, nil
}
-// letterOnlyMapF is used in conjunction with strings.Map to return only the
-// characters A-Z and a-z in a string.
-func letterOnlyMapF(rune rune) rune {
- switch {
- case 'A' <= rune && rune <= 'Z':
- return rune
- case 'a' <= rune && rune <= 'z':
- return rune - 32
+// 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 -1
+ return n
}
-// intOnlyMapF is used in conjunction with strings.Map to return only the
-// numeric portions of a string.
-func intOnlyMapF(rune rune) rune {
- if rune >= 48 && rune < 58 {
- return rune
+// ColumnNumberToName provides a function to convert integer
+// to Excel sheet column title.
+//
+// Example:
+//
+// excelize.ToAlphaString(37) // returns "AK", nil
+//
+func ColumnNumberToName(num int) (string, error) {
+ if num < 1 {
+ return "", fmt.Errorf("incorrect column number %d", num)
}
- return -1
+ var col string
+ for num > 0 {
+ col = string((num-1)%26+65) + col
+ num = (num - 1) / 26
+ }
+ return col, nil
}
-// boolPtr returns a pointer to a bool with the given value.
-func boolPtr(b bool) *bool { return &b }
+// CellNameToCoordinates converts alpha-numeric cell name
+// to [X, Y] coordinates or retrusn an error.
+//
+// Example:
+// 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"
-// defaultTrue returns true if b is nil, or the pointed value.
-func defaultTrue(b *bool) bool {
- if b == nil {
- return true
+ colname, row, err := SplitCellName(cell)
+ if err != nil {
+ return -1, -1, fmt.Errorf(msg, cell, err)
}
- return *b
+
+ col, err := ColumnNameToNumber(colname)
+ if err != nil {
+ return -1, -1, fmt.Errorf(msg, cell, err)
+ }
+
+ return col, row, nil
}
-// axisLowerOrEqualThan returns true if axis1 <= axis2 axis1/axis2 can be
-// either a column or a row axis, e.g. "A", "AAE", "42", "1", etc.
+// MustCellNameToCoordinates converts alpha-numeric cell name
+// to [X, Y] coordinates or panics.
//
-// For instance, the following comparisons are all true:
+// Example:
+// MustCellNameToCoordinates("A1") // returns 1, 1
+// MustCellNameToCoordinates("Z3") // returns 26, 3
//
-// "A" <= "B"
-// "A" <= "AA"
-// "B" <= "AA"
-// "BC" <= "ABCD" (in a XLSX sheet, the BC col comes before the ABCD col)
-// "1" <= "2"
-// "2" <= "11" (in a XLSX sheet, the row 2 comes before the row 11)
-// and so on
-func axisLowerOrEqualThan(axis1, axis2 string) bool {
- if len(axis1) < len(axis2) {
- return true
- } else if len(axis1) > len(axis2) {
- return false
- } else {
- return axis1 <= axis2
+func MustCellNameToCoordinates(cell string) (int, int) {
+ c, r, err := CellNameToCoordinates(cell)
+ if err != nil {
+ panic(err)
}
+ return c, r
}
-// getCellColRow returns the two parts of a cell identifier (its col and row)
-// as strings
+// CoordinatesToCellName converts [X, Y] coordinates to alpha-numeric cell name or returns an error.
//
-// For instance:
+// Example:
+// CoordinatesToCellName(1, 1) // returns "A1", nil
//
-// "C220" => "C", "220"
-// "aaef42" => "aaef", "42"
-// "" => "", ""
-func getCellColRow(cell string) (col, row string) {
- for index, rune := range cell {
- if unicode.IsDigit(rune) {
- return cell[:index], cell[index:]
- }
+func CoordinatesToCellName(col, row int) (string, error) {
+ if col < 1 || row < 1 {
+ return "", fmt.Errorf("invalid cell coordinates [%d, %d]", col, row)
+ }
+ colname, err := ColumnNumberToName(col)
+ if err != nil {
+ return "", fmt.Errorf("invalid cell coordinates [%d, %d]: %v", col, row, err)
+ }
+ 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 }
- return cell, ""
+// defaultTrue returns true if b is nil, or the pointed value.
+func defaultTrue(b *bool) bool {
+ if b == nil {
+ return true
+ }
+ return *b
}
// parseFormatSet provides a method to convert format string to []byte and
@@ -208,7 +280,9 @@ func namespaceStrictToTransitional(content []byte) []byte {
// is great, numerous passwords will match the same hash. Here is the
// algorithm to create the hash value:
//
-// take the ASCII values of all characters shift left the first character 1 bit, the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits, the highest bit of the 16-bit value is always 0 [signed short])
+// take the ASCII values of all characters shift left the first character 1 bit,
+// the second 2 bits and so on (use only the lower 15 bits and rotate all higher bits,
+// the highest bit of the 16-bit value is always 0 [signed short])
// XOR all these values
// XOR the count of characters
// XOR the constant 0xCE4B
diff --git a/lib_test.go b/lib_test.go
index ef0d8f5..4c19f73 100644
--- a/lib_test.go
+++ b/lib_test.go
@@ -2,59 +2,213 @@ package excelize
import (
"fmt"
+ "strconv"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
)
-func TestAxisLowerOrEqualThanIsTrue(t *testing.T) {
- trueExpectedInputList := [][2]string{
- {"A", "B"},
- {"A", "AA"},
- {"B", "AA"},
- {"BC", "ABCD"},
- {"1", "2"},
- {"2", "11"},
+var validColumns = []struct {
+ Name string
+ Num int
+}{
+ {Name: "A", Num: 1},
+ {Name: "Z", Num: 26},
+ {Name: "AA", Num: 26 + 1},
+ {Name: "AK", Num: 26 + 11},
+ {Name: "ak", Num: 26 + 11},
+ {Name: "Ak", Num: 26 + 11},
+ {Name: "aK", Num: 26 + 11},
+ {Name: "AZ", Num: 26 + 26},
+ {Name: "ZZ", Num: 26 + 26*26},
+ {Name: "AAA", Num: 26 + 26*26 + 1},
+ {Name: "ZZZ", Num: 26 + 26*26 + 26*26*26},
+}
+
+var invalidColumns = []struct {
+ Name string
+ Num int
+}{
+ {Name: "", Num: -1},
+ {Name: " ", Num: -1},
+ {Name: "_", Num: -1},
+ {Name: "__", Num: -1},
+ {Name: "-1", Num: -1},
+ {Name: "0", Num: -1},
+ {Name: " A", Num: -1},
+ {Name: "A ", Num: -1},
+ {Name: "A1", Num: -1},
+ {Name: "1A", Num: -1},
+ {Name: " a", Num: -1},
+ {Name: "a ", Num: -1},
+ {Name: "a1", Num: -1},
+ {Name: "1a", Num: -1},
+ {Name: " _", Num: -1},
+ {Name: "_ ", Num: -1},
+ {Name: "_1", Num: -1},
+ {Name: "1_", Num: -1},
+}
+
+var invalidCells = []string{"", "A", "AA", " A", "A ", "1A", "A1A", "A1 ", " A1", "1A1", "a-1", "A-1"}
+
+var invalidIndexes = []int{-100, -2, -1, 0}
+
+func TestColumnNameToNumber_OK(t *testing.T) {
+ const msg = "Column %q"
+ for _, col := range validColumns {
+ out, err := ColumnNameToNumber(col.Name)
+ if assert.NoErrorf(t, err, msg, col.Name) {
+ assert.Equalf(t, col.Num, out, msg, col.Name)
+ }
}
+}
+
+func TestColumnNameToNumber_Error(t *testing.T) {
+ const msg = "Column %q"
+ for _, col := range invalidColumns {
+ out, err := ColumnNameToNumber(col.Name)
+ 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)
+ }
+}
- for i, trueExpectedInput := range trueExpectedInputList {
- t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
- assert.True(t, axisLowerOrEqualThan(trueExpectedInput[0], trueExpectedInput[1]))
- })
+func TestColumnNumberToName_OK(t *testing.T) {
+ const msg = "Column %q"
+ for _, col := range validColumns {
+ out, err := ColumnNumberToName(col.Num)
+ if assert.NoErrorf(t, err, msg, col.Name) {
+ assert.Equalf(t, strings.ToUpper(col.Name), out, msg, col.Name)
+ }
}
}
-func TestAxisLowerOrEqualThanIsFalse(t *testing.T) {
- falseExpectedInputList := [][2]string{
- {"B", "A"},
- {"AA", "A"},
- {"AA", "B"},
- {"ABCD", "AB"},
- {"2", "1"},
- {"11", "2"},
+func TestColumnNumberToName_Error(t *testing.T) {
+ out, err := ColumnNumberToName(-1)
+ if assert.Error(t, err) {
+ assert.Equal(t, "", out)
}
- for i, falseExpectedInput := range falseExpectedInputList {
- t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
- assert.False(t, axisLowerOrEqualThan(falseExpectedInput[0], falseExpectedInput[1]))
- })
+ out, err = ColumnNumberToName(0)
+ if assert.Error(t, err) {
+ assert.Equal(t, "", out)
}
}
-func TestGetCellColRow(t *testing.T) {
- cellExpectedColRowList := [][3]string{
- {"C220", "C", "220"},
- {"aaef42", "aaef", "42"},
- {"bonjour", "bonjour", ""},
- {"59", "", "59"},
- {"", "", ""},
+func TestSplitCellName_OK(t *testing.T) {
+ const msg = "Cell \"%s%d\""
+ for i, col := range validColumns {
+ row := i + 1
+ c, r, err := SplitCellName(col.Name + strconv.Itoa(row))
+ if assert.NoErrorf(t, err, msg, col.Name, row) {
+ assert.Equalf(t, col.Name, c, msg, col.Name, row)
+ assert.Equalf(t, row, r, msg, col.Name, row)
+ }
+ }
+}
+
+func TestSplitCellName_Error(t *testing.T) {
+ const msg = "Cell %q"
+ for _, cell := range invalidCells {
+ c, r, err := SplitCellName(cell)
+ if assert.Errorf(t, err, msg, cell) {
+ assert.Equalf(t, "", c, msg, cell)
+ assert.Equalf(t, -1, r, msg, cell)
+ }
+ }
+}
+
+func TestJoinCellName_OK(t *testing.T) {
+ const msg = "Cell \"%s%d\""
+
+ for i, col := range validColumns {
+ row := i + 1
+ cell, err := JoinCellName(col.Name, row)
+ if assert.NoErrorf(t, err, msg, col.Name, row) {
+ assert.Equalf(t, strings.ToUpper(fmt.Sprintf("%s%d", col.Name, row)), cell, msg, row)
+ }
+ }
+}
+
+func TestJoinCellName_Error(t *testing.T) {
+ const msg = "Cell \"%s%d\""
+
+ test := func(col string, row int) {
+ cell, err := JoinCellName(col, row)
+ if assert.Errorf(t, err, msg, col, row) {
+ assert.Equalf(t, "", cell, msg, col, row)
+ }
+ }
+
+ for _, col := range invalidColumns {
+ test(col.Name, 1)
+ for _, row := range invalidIndexes {
+ test("A", row)
+ test(col.Name, row)
+ }
+ }
+
+}
+
+func TestCellNameToCoordinates_OK(t *testing.T) {
+ const msg = "Cell \"%s%d\""
+ for i, col := range validColumns {
+ row := i + 1
+ c, r, err := CellNameToCoordinates(col.Name + strconv.Itoa(row))
+ if assert.NoErrorf(t, err, msg, col.Name, row) {
+ assert.Equalf(t, col.Num, c, msg, col.Name, row)
+ assert.Equalf(t, i+1, r, msg, col.Name, row)
+ }
+ }
+}
+
+func TestCellNameToCoordinates_Error(t *testing.T) {
+ const msg = "Cell %q"
+ for _, cell := range invalidCells {
+ c, r, err := CellNameToCoordinates(cell)
+ if assert.Errorf(t, err, msg, cell) {
+ assert.Equalf(t, -1, c, msg, cell)
+ assert.Equalf(t, -1, r, msg, cell)
+ }
+ assert.Panicsf(t, func() {
+ MustCellNameToCoordinates(cell)
+ }, msg, cell)
+ }
+}
+
+func TestCoordinatesToCellName_OK(t *testing.T) {
+ const msg = "Coordinates [%d, %d]"
+ for i, col := range validColumns {
+ row := i + 1
+ cell, err := CoordinatesToCellName(col.Num, row)
+ if assert.NoErrorf(t, err, msg, col.Num, row) {
+ assert.Equalf(t, strings.ToUpper(col.Name+strconv.Itoa(row)), cell, msg, col.Num, row)
+ }
+ }
+}
+
+func TestCoordinatesToCellName_Error(t *testing.T) {
+ const msg = "Coordinates [%d, %d]"
+
+ test := func(col, row int) {
+ cell, err := CoordinatesToCellName(col, row)
+ 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 i, test := range cellExpectedColRowList {
- t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {
- col, row := getCellColRow(test[0])
- assert.Equal(t, test[1], col, "Unexpected col")
- assert.Equal(t, test[2], row, "Unexpected row")
- })
+ for _, col := range invalidIndexes {
+ test(col, 1)
+ for _, row := range invalidIndexes {
+ test(1, row)
+ test(col, row)
+ }
}
}
diff --git a/picture.go b/picture.go
index 131b15c..f3463aa 100644
--- a/picture.go
+++ b/picture.go
@@ -143,7 +143,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
if err != nil {
return err
}
- image, _, err := image.DecodeConfig(bytes.NewReader(file))
+ img, _, err := image.DecodeConfig(bytes.NewReader(file))
if err != nil {
return err
}
@@ -162,7 +162,7 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string,
}
drawingHyperlinkRID = f.addDrawingRelationships(drawingID, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType)
}
- f.addDrawingPicture(sheet, drawingXML, cell, name, image.Width, image.Height, drawingRID, drawingHyperlinkRID, formatSet)
+ f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet)
f.addMedia(file, ext)
f.addContentTypePart(drawingID, "drawings")
return err
@@ -263,14 +263,11 @@ func (f *File) countDrawings() int {
// 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) {
- cell = strings.ToUpper(cell)
- fromCol := string(strings.Map(letterOnlyMapF, cell))
- fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
- row := fromRow - 1
- col := TitleToNumber(fromCol)
+ col, row := MustCellNameToCoordinates(cell)
width = int(float64(width) * formatSet.XScale)
height = int(float64(height) * formatSet.YScale)
- colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
+ colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
+ f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height)
content, cNvPrID := f.drawingParser(drawingXML)
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = formatSet.Positioning
@@ -471,30 +468,36 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
// }
//
func (f *File) GetPicture(sheet, cell string) (string, []byte) {
+ col, row := MustCellNameToCoordinates(cell)
+
xlsx := f.workSheetReader(sheet)
if xlsx.Drawing == nil {
return "", []byte{}
}
+
target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
drawingXML := strings.Replace(target, "..", "xl", -1)
- cell = strings.ToUpper(cell)
- fromCol := string(strings.Map(letterOnlyMapF, cell))
- fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
- row := fromRow - 1
- col := TitleToNumber(fromCol)
- drawingRelationships := strings.Replace(strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
+
+ drawingRelationships := strings.Replace(
+ strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
+
wsDr, _ := f.drawingParser(drawingXML)
+
for _, anchor := range wsDr.TwoCellAnchor {
if anchor.From != nil && anchor.Pic != nil {
if anchor.From.Col == col && anchor.From.Row == row {
- xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed)
+ xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships,
+ anchor.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)])
}
}
}
}
+
_, ok := f.XLSX[drawingXML]
if !ok {
return "", nil
diff --git a/rows.go b/rows.go
index 9575876..8b4f8ec 100644
--- a/rows.go
+++ b/rows.go
@@ -16,7 +16,6 @@ import (
"io"
"math"
"strconv"
- "strings"
)
// GetRows return all the rows in a sheet by given worksheet name (case
@@ -30,24 +29,30 @@ import (
// }
//
func (f *File) GetRows(sheet string) [][]string {
- xlsx := f.workSheetReader(sheet)
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
- return [][]string{}
+ return nil
}
+
+ xlsx := f.workSheetReader(sheet)
if xlsx != nil {
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
}
+
xml.NewDecoder(bytes.NewReader(f.readXML(name)))
d := f.sharedStringsReader()
- var inElement string
- var r xlsxRow
- tr, tc := f.getTotalRowsCols(name)
- rows := make([][]string, tr)
+ var (
+ inElement string
+ rowData xlsxRow
+ )
+
+ rowCount, colCount := f.getTotalRowsCols(name)
+ rows := make([][]string, rowCount)
for i := range rows {
- rows[i] = make([]string, tc+1)
+ rows[i] = make([]string, colCount+1)
}
+
var row int
decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
for {
@@ -59,15 +64,15 @@ func (f *File) GetRows(sheet string) [][]string {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
- r = xlsxRow{}
- _ = decoder.DecodeElement(&r, &startElement)
- cr := r.R - 1
- for _, colCell := range r.C {
- c := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
+ rowData = xlsxRow{}
+ _ = decoder.DecodeElement(&rowData, &startElement)
+ cr := rowData.R - 1
+ for _, colCell := range rowData.C {
+ col, _ := MustCellNameToCoordinates(colCell.R)
val, _ := colCell.getValueFrom(f, d)
- rows[cr][c] = val
+ rows[cr][col-1] = val
if val != "" {
- row = r.R
+ row = rowData.R
}
}
}
@@ -120,13 +125,13 @@ func (rows *Rows) Columns() []string {
r := xlsxRow{}
_ = rows.decoder.DecodeElement(&r, &startElement)
d := rows.f.sharedStringsReader()
- row := make([]string, len(r.C))
+ columns := make([]string, len(r.C))
for _, colCell := range r.C {
- c := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
+ col, _ := MustCellNameToCoordinates(colCell.R)
val, _ := colCell.getValueFrom(rows.f, d)
- row[c] = val
+ columns[col-1] = val
}
- return row
+ return columns
}
// ErrSheetNotExist defines an error of sheet is not exist
@@ -184,7 +189,7 @@ func (f *File) getTotalRowsCols(name string) (int, int) {
_ = decoder.DecodeElement(&r, &startElement)
tr = r.R
for _, colCell := range r.C {
- col := TitleToNumber(strings.Map(letterOnlyMapF, colCell.R))
+ col, _ := MustCellNameToCoordinates(colCell.R)
if col > tc {
tc = col
}
@@ -202,13 +207,15 @@ func (f *File) getTotalRowsCols(name string) (int, int) {
// xlsx.SetRowHeight("Sheet1", 1, 50)
//
func (f *File) SetRowHeight(sheet string, row int, height float64) {
- xlsx := f.workSheetReader(sheet)
if row < 1 {
- return
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
}
- cells := 0
+
+ xlsx := f.workSheetReader(sheet)
+
+ prepareSheetXML(xlsx, 0, row)
+
rowIdx := row - 1
- completeRow(xlsx, row, cells)
xlsx.SheetData.Row[rowIdx].Ht = height
xlsx.SheetData.Row[rowIdx].CustomHeight = true
}
@@ -232,8 +239,12 @@ func (f *File) getRowHeight(sheet string, row int) int {
// xlsx.GetRowHeight("Sheet1", 1)
//
func (f *File) GetRowHeight(sheet string, row int) float64 {
+ if row < 1 {
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
+ }
+
xlsx := f.workSheetReader(sheet)
- if row < 1 || row > len(xlsx.SheetData.Row) {
+ if row > len(xlsx.SheetData.Row) {
return defaultRowHeightPixels // it will be better to use 0, but we take care with BC
}
for _, v := range xlsx.SheetData.Row {
@@ -291,18 +302,13 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
// xlsx.SetRowVisible("Sheet1", 2, false)
//
func (f *File) SetRowVisible(sheet string, row int, visible bool) {
- xlsx := f.workSheetReader(sheet)
if row < 1 {
- return
- }
- cells := 0
- completeRow(xlsx, row, cells)
- rowIdx := row - 1
- if visible {
- xlsx.SheetData.Row[rowIdx].Hidden = false
- return
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
}
- xlsx.SheetData.Row[rowIdx].Hidden = true
+
+ xlsx := f.workSheetReader(sheet)
+ prepareSheetXML(xlsx, 0, row)
+ xlsx.SheetData.Row[row-1].Hidden = !visible
}
// GetRowVisible provides a function to get visible of a single row by given
@@ -312,14 +318,15 @@ func (f *File) SetRowVisible(sheet string, row int, visible bool) {
// xlsx.GetRowVisible("Sheet1", 2)
//
func (f *File) GetRowVisible(sheet string, row int) bool {
+ if row < 1 {
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
+ }
+
xlsx := f.workSheetReader(sheet)
- if row < 1 || row > len(xlsx.SheetData.Row) {
+ if row > len(xlsx.SheetData.Row) {
return false
}
- rowIndex := row - 1
- cells := 0
- completeRow(xlsx, row, cells)
- return !xlsx.SheetData.Row[rowIndex].Hidden
+ return !xlsx.SheetData.Row[row-1].Hidden
}
// SetRowOutlineLevel provides a function to set outline level number of a
@@ -329,12 +336,11 @@ func (f *File) GetRowVisible(sheet string, row int) bool {
// xlsx.SetRowOutlineLevel("Sheet1", 2, 1)
//
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) {
- xlsx := f.workSheetReader(sheet)
if row < 1 {
- return
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
}
- cells := 0
- completeRow(xlsx, row, cells)
+ xlsx := f.workSheetReader(sheet)
+ prepareSheetXML(xlsx, 0, row)
xlsx.SheetData.Row[row-1].OutlineLevel = level
}
@@ -345,8 +351,11 @@ func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) {
// xlsx.GetRowOutlineLevel("Sheet1", 2)
//
func (f *File) GetRowOutlineLevel(sheet string, row int) uint8 {
+ if row < 1 {
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
+ }
xlsx := f.workSheetReader(sheet)
- if row < 1 || row > len(xlsx.SheetData.Row) {
+ if row > len(xlsx.SheetData.Row) {
return 0
}
return xlsx.SheetData.Row[row-1].OutlineLevel
@@ -362,14 +371,18 @@ func (f *File) GetRowOutlineLevel(sheet string, row int) uint8 {
// 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) {
+ if row < 1 {
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
+ }
+
xlsx := f.workSheetReader(sheet)
- if row < 1 || row > len(xlsx.SheetData.Row) {
+ if row > len(xlsx.SheetData.Row) {
return
}
for i, r := range xlsx.SheetData.Row {
if r.R == row {
xlsx.SheetData.Row = append(xlsx.SheetData.Row[:i], xlsx.SheetData.Row[i+1:]...)
- f.adjustHelper(sheet, -1, row, -1)
+ f.adjustHelper(sheet, rows, row, -1)
return
}
}
@@ -383,9 +396,9 @@ func (f *File) RemoveRow(sheet string, row int) {
//
func (f *File) InsertRow(sheet string, row int) {
if row < 1 {
- return
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
}
- f.adjustHelper(sheet, -1, row, 1)
+ f.adjustHelper(sheet, rows, row, 1)
}
// DuplicateRow inserts a copy of specified row (by it Excel row number) below
@@ -410,9 +423,12 @@ func (f *File) DuplicateRow(sheet string, row int) {
// 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) {
- xlsx := f.workSheetReader(sheet)
+ if row < 1 {
+ panic(newInvalidRowNumberError(row)) // Fail fats to avoid possible future side effects!
+ }
- if row < 1 || row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
+ xlsx := f.workSheetReader(sheet)
+ if row > len(xlsx.SheetData.Row) || row2 < 1 || row == row2 {
return
}
@@ -430,7 +446,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) {
return
}
- f.adjustHelper(sheet, -1, row2, 1)
+ f.adjustHelper(sheet, rows, row2, 1)
idx2 := -1
for i, r := range xlsx.SheetData.Row {
@@ -478,62 +494,31 @@ 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) {
- buffer := bytes.Buffer{}
- for k := range xlsx.SheetData.Row {
- lenCol := len(xlsx.SheetData.Row[k].C)
- if lenCol > 0 {
- endR := string(strings.Map(letterOnlyMapF, xlsx.SheetData.Row[k].C[lenCol-1].R))
- endRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, xlsx.SheetData.Row[k].C[lenCol-1].R))
- endCol := TitleToNumber(endR) + 1
- if lenCol < endCol {
- oldRow := xlsx.SheetData.Row[k].C
- xlsx.SheetData.Row[k].C = xlsx.SheetData.Row[k].C[:0]
- var tmp []xlsxC
- for i := 0; i < endCol; i++ {
- buffer.WriteString(ToAlphaString(i))
- buffer.WriteString(strconv.Itoa(endRow))
- tmp = append(tmp, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
- }
- xlsx.SheetData.Row[k].C = tmp
- for _, y := range oldRow {
- colAxis := TitleToNumber(string(strings.Map(letterOnlyMapF, y.R)))
- xlsx.SheetData.Row[k].C[colAxis] = y
- }
- }
- }
- }
-}
+ for rowIdx := range xlsx.SheetData.Row {
+ rowData := &xlsx.SheetData.Row[rowIdx]
-// completeRow provides a function to check and fill each column element for a
-// single row and make that is continuous in a worksheet of XML by given row
-// index and axis.
-func completeRow(xlsx *xlsxWorksheet, row, cell int) {
- currentRows := len(xlsx.SheetData.Row)
- if currentRows > 1 {
- lastRow := xlsx.SheetData.Row[currentRows-1].R
- if lastRow >= row {
- row = lastRow
+ colCount := len(rowData.C)
+ if colCount == 0 {
+ continue
}
- }
- for i := currentRows; i < row; i++ {
- xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{
- R: i + 1,
- })
- }
- buffer := bytes.Buffer{}
- for ii := currentRows; ii < row; ii++ {
- start := len(xlsx.SheetData.Row[ii].C)
- if start == 0 {
- for iii := start; iii < cell; iii++ {
- buffer.WriteString(ToAlphaString(iii))
- buffer.WriteString(strconv.Itoa(ii + 1))
- xlsx.SheetData.Row[ii].C = append(xlsx.SheetData.Row[ii].C, xlsxC{
- R: buffer.String(),
- })
- buffer.Reset()
+ lastCol, _ := MustCellNameToCoordinates(rowData.C[colCount-1].R)
+
+ if colCount < lastCol {
+ oldList := rowData.C
+ newlist := make([]xlsxC, 0, lastCol)
+
+ rowData.C = xlsx.SheetData.Row[rowIdx].C[:0]
+
+ for colIdx := 0; colIdx < lastCol; colIdx++ {
+ newlist = append(newlist, xlsxC{R: MustCoordinatesToCellName(colIdx+1, rowIdx+1)})
+ }
+
+ rowData.C = newlist
+
+ for colIdx := range oldList {
+ colData := &oldList[colIdx]
+ colNum, _ := MustCellNameToCoordinates(colData.R)
+ xlsx.SheetData.Row[rowIdx].C[colNum-1] = *colData
}
}
}
diff --git a/rows_test.go b/rows_test.go
index b83d377..50e26dd 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -3,44 +3,38 @@ package excelize
import (
"fmt"
"path/filepath"
- "strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRows(t *testing.T) {
+ const sheet2 = "Sheet2"
+
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
- rows, err := xlsx.Rows("Sheet2")
+ rows, err := xlsx.Rows(sheet2)
if !assert.NoError(t, err) {
t.FailNow()
}
- rowStrs := make([][]string, 0)
- var i = 0
+ collectedRows := make([][]string, 0)
for rows.Next() {
- i++
- columns := rows.Columns()
- rowStrs = append(rowStrs, columns)
+ collectedRows = append(collectedRows, trimSliceSpace(rows.Columns()))
}
-
if !assert.NoError(t, rows.Error()) {
t.FailNow()
}
- dstRows := xlsx.GetRows("Sheet2")
- if !assert.Equal(t, len(rowStrs), len(dstRows)) {
- t.FailNow()
+ returnedRows := xlsx.GetRows(sheet2)
+ for i := range returnedRows {
+ returnedRows[i] = trimSliceSpace(returnedRows[i])
}
-
- for i := 0; i < len(rowStrs); i++ {
- if !assert.Equal(t, trimSliceSpace(dstRows[i]), trimSliceSpace(rowStrs[i])) {
- t.FailNow()
- }
+ if !assert.Equal(t, collectedRows, returnedRows) {
+ t.FailNow()
}
r := Rows{}
@@ -60,8 +54,13 @@ func TestRowHeight(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
- xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0) // should no effect
- assert.Equal(t, defaultRowHeightPixels, xlsx.GetRowHeight("Sheet1", 0))
+ assert.Panics(t, func() {
+ xlsx.SetRowHeight(sheet1, 0, defaultRowHeightPixels+1.0)
+ })
+
+ assert.Panics(t, func() {
+ xlsx.GetRowHeight("Sheet1", 0)
+ })
xlsx.SetRowHeight(sheet1, 1, 111.0)
assert.Equal(t, 111.0, xlsx.GetRowHeight(sheet1, 1))
@@ -77,32 +76,47 @@ func TestRowHeight(t *testing.T) {
convertColWidthToPixels(0)
}
+func TestRowVisibility(t *testing.T) {
+ xlsx, err := prepareTestBook1()
+ if !assert.NoError(t, err) {
+ t.FailNow()
+ }
+
+ xlsx.SetRowVisible("Sheet3", 2, false)
+ xlsx.SetRowVisible("Sheet3", 2, true)
+ xlsx.GetRowVisible("Sheet3", 2)
+
+ assert.Panics(t, func() {
+ xlsx.SetRowVisible("Sheet3", 0, true)
+ })
+
+ assert.Panics(t, func() {
+ xlsx.GetRowVisible("Sheet3", 0)
+ })
+
+ assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
+}
+
func TestRemoveRow(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
r := xlsx.workSheetReader(sheet1)
const (
- cellCount = 10
- rowCount = 10
+ colCount = 10
+ rowCount = 10
)
- for j := 1; j <= cellCount; j++ {
- for i := 1; i <= rowCount; i++ {
- axis := ToAlphaString(i) + strconv.Itoa(j)
- xlsx.SetCellStr(sheet1, axis, axis)
- }
- }
+ fillCells(xlsx, sheet1, colCount, rowCount)
+
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
- xlsx.RemoveRow(sheet1, -1)
- if !assert.Len(t, r.SheetData.Row, rowCount) {
- t.FailNow()
- }
+ assert.Panics(t, func() {
+ xlsx.RemoveRow(sheet1, -1)
+ })
- xlsx.RemoveRow(sheet1, 0)
- if !assert.Len(t, r.SheetData.Row, rowCount) {
- t.FailNow()
- }
+ assert.Panics(t, func() {
+ xlsx.RemoveRow(sheet1, 0)
+ })
xlsx.RemoveRow(sheet1, 4)
if !assert.Len(t, r.SheetData.Row, rowCount-1) {
@@ -150,26 +164,20 @@ func TestInsertRow(t *testing.T) {
r := xlsx.workSheetReader(sheet1)
const (
- cellCount = 10
- rowCount = 10
+ colCount = 10
+ rowCount = 10
)
- for j := 1; j <= cellCount; j++ {
- for i := 1; i < rowCount; i++ {
- axis := ToAlphaString(i) + strconv.Itoa(j)
- xlsx.SetCellStr(sheet1, axis, axis)
- }
- }
+ fillCells(xlsx, sheet1, colCount, rowCount)
+
xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
- xlsx.InsertRow(sheet1, -1)
- if !assert.Len(t, r.SheetData.Row, rowCount) {
- t.FailNow()
- }
+ assert.Panics(t, func() {
+ xlsx.InsertRow(sheet1, -1)
+ })
- xlsx.InsertRow(sheet1, 0)
- if !assert.Len(t, r.SheetData.Row, rowCount) {
- t.FailNow()
- }
+ assert.Panics(t, func() {
+ xlsx.InsertRow(sheet1, 0)
+ })
xlsx.InsertRow(sheet1, 1)
if !assert.Len(t, r.SheetData.Row, rowCount+1) {
@@ -304,19 +312,24 @@ func TestDuplicateRow(t *testing.T) {
t.Run("ZeroWithNoRows", func(t *testing.T) {
xlsx := NewFile()
- xlsx.DuplicateRow(sheet, 0)
+ assert.Panics(t, func() {
+ xlsx.DuplicateRow(sheet, 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"))
+
expect := map[string]string{
"A1": "", "B1": "",
"A2": "", "B2": "",
}
+
for cell, val := range expect {
if !assert.Equal(t, val, xlsx.GetCellValue(sheet, cell), cell) {
t.FailNow()
@@ -444,31 +457,19 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
"B3": "B3 Value",
}
- testRows := []int{-2, -1}
-
- testRowPairs := []struct {
- row1 int
- row2 int
- }{
- {-1, -1},
- {-1, 0},
- {-1, 1},
- {0, -1},
- {0, 0},
- {0, 1},
- {1, -1},
- {1, 1},
- {1, 0},
- }
+ invalidIndexes := []int{-100, -2, -1, 0}
- for i, row := range testRows {
- name := fmt.Sprintf("TestRow_%d", i+1)
+ for _, row := range invalidIndexes {
+ name := fmt.Sprintf("%d", row)
t.Run(name, func(t *testing.T) {
xlsx := NewFile()
for col, val := range cells {
xlsx.SetCellStr(sheet, col, val)
}
- xlsx.DuplicateRow(sheet, row)
+
+ assert.Panics(t, func() {
+ xlsx.DuplicateRow(sheet, row)
+ })
for col, val := range cells {
if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) {
@@ -479,22 +480,27 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
})
}
- for i, pair := range testRowPairs {
- name := fmt.Sprintf("TestRowPair_%d", i+1)
- t.Run(name, func(t *testing.T) {
- xlsx := NewFile()
- for col, val := range cells {
- xlsx.SetCellStr(sheet, col, val)
- }
- xlsx.DuplicateRowTo(sheet, pair.row1, pair.row2)
+ for _, row1 := range invalidIndexes {
+ for _, row2 := range invalidIndexes {
+ name := fmt.Sprintf("[%d,%d]", row1, row2)
+ t.Run(name, func(t *testing.T) {
+ xlsx := NewFile()
+ for col, val := range cells {
+ xlsx.SetCellStr(sheet, col, val)
+ }
- for col, val := range cells {
- if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) {
- t.FailNow()
+ assert.Panics(t, func() {
+ xlsx.DuplicateRowTo(sheet, row1, row2)
+ })
+
+ for col, val := range cells {
+ if !assert.Equal(t, val, xlsx.GetCellValue(sheet, col)) {
+ t.FailNow()
+ }
}
- }
- assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, name)))
- })
+ assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, name)))
+ })
+ }
}
}
diff --git a/shape.go b/shape.go
index 3cf09d8..c58038c 100644
--- a/shape.go
+++ b/shape.go
@@ -283,15 +283,37 @@ func (f *File) AddShape(sheet, cell, format string) error {
// addDrawingShape provides a function to add preset geometry by given sheet,
// drawingXMLand format sets.
func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) {
- textUnderlineType := map[string]bool{"none": true, "words": true, "sng": true, "dbl": true, "heavy": true, "dotted": true, "dottedHeavy": true, "dash": true, "dashHeavy": true, "dashLong": true, "dashLongHeavy": true, "dotDash": true, "dotDashHeavy": true, "dotDotDash": true, "dotDotDashHeavy": true, "wavy": true, "wavyHeavy": true, "wavyDbl": true}
- cell = strings.ToUpper(cell)
- fromCol := string(strings.Map(letterOnlyMapF, cell))
- fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
- row := fromRow - 1
- col := TitleToNumber(fromCol)
+ fromCol, fromRow := MustCellNameToCoordinates(cell)
+ colIdx := fromCol - 1
+ rowIdx := fromRow - 1
+
+ textUnderlineType := map[string]bool{
+ "none": true,
+ "words": true,
+ "sng": true,
+ "dbl": true,
+ "heavy": true,
+ "dotted": true,
+ "dottedHeavy": true,
+ "dash": true,
+ "dashHeavy": true,
+ "dashLong": true,
+ "dashLongHeavy": true,
+ "dotDash": true,
+ "dotDashHeavy": true,
+ "dotDotDash": true,
+ "dotDotDashHeavy": true,
+ "wavy": true,
+ "wavyHeavy": true,
+ "wavyDbl": true,
+ }
+
width := int(float64(formatSet.Width) * formatSet.Format.XScale)
height := int(float64(formatSet.Height) * formatSet.Format.YScale)
- colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.Format.OffsetX, formatSet.Format.OffsetY, width, height)
+
+ colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 :=
+ f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY,
+ width, height)
content, cNvPrID := f.drawingParser(drawingXML)
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = formatSet.Format.Positioning
diff --git a/sheet.go b/sheet.go
index 26d4b4c..ee96277 100644
--- a/sheet.go
+++ b/sheet.go
@@ -14,7 +14,6 @@ import (
"encoding/json"
"encoding/xml"
"errors"
- "fmt"
"io/ioutil"
"os"
"path"
@@ -100,16 +99,16 @@ func (f *File) workBookWriter() {
// workSheetWriter provides a function to save xl/worksheets/sheet%d.xml after
// serialize structure.
func (f *File) workSheetWriter() {
- for path, sheet := range f.Sheet {
+ for p, sheet := range f.Sheet {
if sheet != nil {
for k, v := range sheet.SheetData.Row {
- f.Sheet[path].SheetData.Row[k].C = trimCell(v.C)
+ f.Sheet[p].SheetData.Row[k].C = trimCell(v.C)
}
output, _ := xml.Marshal(sheet)
- f.saveFileList(path, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
- ok := f.checked[path]
+ f.saveFileList(p, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
+ ok := f.checked[p]
if ok {
- f.checked[path] = false
+ f.checked[p] = false
}
}
}
@@ -679,7 +678,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string {
regSearch = r
}
xlsx := f.workSheetReader(sheet)
- result := []string{}
+ var (
+ result []string
+ )
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return result
@@ -716,7 +717,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) []string {
continue
}
}
- result = append(result, fmt.Sprintf("%s%d", strings.Map(letterOnlyMapF, colCell.R), r.R))
+
+ cellCol, _ := MustCellNameToCoordinates(colCell.R)
+ result = append(result, MustCoordinatesToCellName(cellCol, r.R))
}
}
default:
@@ -775,7 +778,7 @@ func (f *File) UnprotectSheet(sheet string) {
// trimSheetName provides a function to trim invaild characters by given worksheet
// name.
func trimSheetName(name string) string {
- r := []rune{}
+ var r []rune
for _, v := range name {
switch v {
case 58, 92, 47, 63, 42, 91, 93: // replace :\/?*[]
@@ -852,7 +855,7 @@ func (p *PageLayoutPaperSize) getPageLayout(ps *xlsxPageSetUp) {
//
// Available options:
// PageLayoutOrientation(string)
-// PageLayoutPaperSize(int)
+// PageLayoutPaperSize(int)
//
// The following shows the paper size sorted by excelize index number:
//
@@ -1021,10 +1024,31 @@ func (f *File) workSheetRelsReader(path string) *xlsxWorkbookRels {
// workSheetRelsWriter provides a function to save
// xl/worksheets/_rels/sheet%d.xml.rels after serialize structure.
func (f *File) workSheetRelsWriter() {
- for path, r := range f.WorkSheetRels {
+ for p, r := range f.WorkSheetRels {
if r != nil {
v, _ := xml.Marshal(r)
- f.saveFileList(path, v)
+ f.saveFileList(p, v)
+ }
+ }
+}
+
+// fillSheetData fill missing row and cell XML data to made it continous from first cell [1, 1] to last cell [col, row]
+func prepareSheetXML(xlsx *xlsxWorksheet, col int, row int) {
+ rowCount := len(xlsx.SheetData.Row)
+ if rowCount < row {
+ // append missing rows
+ for rowIdx := rowCount; rowIdx < row; rowIdx++ {
+ xlsx.SheetData.Row = append(xlsx.SheetData.Row, xlsxRow{R: rowIdx + 1})
+ }
+ }
+ for rowIdx := range xlsx.SheetData.Row {
+ rowData := &xlsx.SheetData.Row[rowIdx] // take reference
+ cellCount := len(rowData.C)
+ if cellCount < col {
+ for colIdx := cellCount; colIdx < col; colIdx++ {
+ cellName, _ := CoordinatesToCellName(colIdx+1, rowIdx+1)
+ rowData.C = append(rowData.C, xlsxC{R: cellName})
+ }
}
}
}
diff --git a/styles.go b/styles.go
index 7ffc8ff..50b30b8 100644
--- a/styles.go
+++ b/styles.go
@@ -2263,6 +2263,14 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
return style.CellXfs.Count - 1
}
+// GetCellStyle provides a function to get cell style index by given worksheet
+// name and cell coordinates.
+func (f *File) GetCellStyle(sheet, axis string) int {
+ xlsx := f.workSheetReader(sheet)
+ cellData, col, _ := f.prepareCell(xlsx, sheet, axis)
+ return f.prepareCellStyle(xlsx, col, cellData.S)
+}
+
// SetCellStyle provides a function to add style attribute for cells by given
// worksheet name, coordinate area and style ID. Note that diagonalDown and
// diagonalUp type border should be use same color in the same coordinate
@@ -2329,42 +2337,36 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a
// xlsx.SetCellStyle("Sheet1", "H9", "H9", style)
//
func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) {
- hcell = strings.ToUpper(hcell)
- vcell = strings.ToUpper(vcell)
-
- // Coordinate conversion, convert C1:B3 to 2,0,1,2.
- hcol := string(strings.Map(letterOnlyMapF, hcell))
- hrow, err := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
+ hcol, hrow, err := CellNameToCoordinates(hcell)
if err != nil {
- return
+ panic(err)
}
- hyAxis := hrow - 1
- hxAxis := TitleToNumber(hcol)
- vcol := string(strings.Map(letterOnlyMapF, vcell))
- vrow, err := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
+ vcol, vrow, err := CellNameToCoordinates(vcell)
if err != nil {
- return
+ panic(err)
}
- vyAxis := vrow - 1
- vxAxis := TitleToNumber(vcol)
- // Correct the coordinate area, such correct C1:B3 to B1:C3.
- if vxAxis < hxAxis {
- vxAxis, hxAxis = hxAxis, vxAxis
+ // Normalize the coordinate area, such correct C1:B3 to B1:C3.
+ if vcol < hcol {
+ vcol, hcol = hcol, vcol
}
- if vyAxis < hyAxis {
- vyAxis, hyAxis = hyAxis, vyAxis
+ if vrow < hrow {
+ vrow, hrow = hrow, vrow
}
- xlsx := f.workSheetReader(sheet)
+ hcolIdx := hcol - 1
+ hrowIdx := hrow - 1
+
+ vcolIdx := vcol - 1
+ vrowIdx := vrow - 1
- completeRow(xlsx, vyAxis+1, vxAxis+1)
- completeCol(xlsx, vyAxis+1, vxAxis+1)
+ xlsx := f.workSheetReader(sheet)
+ prepareSheetXML(xlsx, vcol, vrow)
- for r := hyAxis; r <= vyAxis; r++ {
- for k := hxAxis; k <= vxAxis; k++ {
+ for r := hrowIdx; r <= vrowIdx; r++ {
+ for k := hcolIdx; k <= vcolIdx; k++ {
xlsx.SheetData.Row[r].C[k].S = styleID
}
}
diff --git a/table.go b/table.go
index 7c7e061..e33264b 100644
--- a/table.go
+++ b/table.go
@@ -55,31 +55,25 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
if err != nil {
return err
}
- hcell = strings.ToUpper(hcell)
- vcell = strings.ToUpper(vcell)
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
- hcol := string(strings.Map(letterOnlyMapF, hcell))
- hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
- hyAxis := hrow - 1
- hxAxis := TitleToNumber(hcol)
+ hcol, hrow := MustCellNameToCoordinates(hcell)
+ vcol, vrow := MustCellNameToCoordinates(vcell)
- vcol := string(strings.Map(letterOnlyMapF, vcell))
- vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
- vyAxis := vrow - 1
- vxAxis := TitleToNumber(vcol)
- if vxAxis < hxAxis {
- vxAxis, hxAxis = hxAxis, vxAxis
+ if vcol < hcol {
+ vcol, hcol = hcol, vcol
}
- if vyAxis < hyAxis {
- vyAxis, hyAxis = hyAxis, vyAxis
+
+ if vrow < hrow {
+ vrow, hrow = hrow, vrow
}
+
tableID := f.countTables() + 1
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
// Add first table for given sheet.
rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "")
f.addSheetTable(sheet, rID)
- f.addTable(sheet, tableXML, hxAxis, hyAxis, vxAxis, vyAxis, tableID, formatSet)
+ f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
f.addContentTypePart(tableID, "table")
return err
}
@@ -112,18 +106,23 @@ 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, hxAxis, hyAxis, vxAxis, vyAxis, i int, formatSet *formatTable) {
+func (f *File) addTable(sheet, tableXML string, hcol, hrow, vcol, vrow, i int, formatSet *formatTable) {
// Correct the minimum number of rows, the table at least two lines.
- if hyAxis == vyAxis {
- vyAxis++
+ if hrow == vrow {
+ vrow++
}
+
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
- ref := ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
- tableColumn := []*xlsxTableColumn{}
+ ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow)
+
+ var (
+ tableColumn []*xlsxTableColumn
+ )
+
idx := 0
- for i := hxAxis; i <= vxAxis; i++ {
+ for i := hcol; i <= vcol; i++ {
idx++
- cell := ToAlphaString(i) + strconv.Itoa(hyAxis+1)
+ cell := MustCoordinatesToCellName(i, hrow)
name := f.GetCellValue(sheet, cell)
if _, err := strconv.Atoi(name); err == nil {
f.SetCellStr(sheet, cell, name)
@@ -245,37 +244,26 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) {
// Price < 2000
//
func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
- formatSet, _ := parseAutoFilterSet(format)
+ hcol, hrow := MustCellNameToCoordinates(hcell)
+ vcol, vrow := MustCellNameToCoordinates(vcell)
- hcell = strings.ToUpper(hcell)
- vcell = strings.ToUpper(vcell)
-
- // Coordinate conversion, convert C1:B3 to 2,0,1,2.
- hcol := string(strings.Map(letterOnlyMapF, hcell))
- hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
- hyAxis := hrow - 1
- hxAxis := TitleToNumber(hcol)
-
- vcol := string(strings.Map(letterOnlyMapF, vcell))
- vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
- vyAxis := vrow - 1
- vxAxis := TitleToNumber(vcol)
-
- if vxAxis < hxAxis {
- vxAxis, hxAxis = hxAxis, vxAxis
+ if vcol < hcol {
+ vcol, hcol = hcol, vcol
}
- if vyAxis < hyAxis {
- vyAxis, hyAxis = hyAxis, vyAxis
+ if vrow < hrow {
+ vrow, hrow = hrow, vrow
}
- ref := ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
- refRange := vxAxis - hxAxis
- return f.autoFilter(sheet, ref, refRange, hxAxis, formatSet)
+
+ formatSet, _ := parseAutoFilterSet(format)
+ ref := MustCoordinatesToCellName(hcol, hrow) + ":" + MustCoordinatesToCellName(vcol, vrow)
+ refRange := vcol - hcol
+ return f.autoFilter(sheet, ref, refRange, hcol, formatSet)
}
// autoFilter provides a function to extract the tokens from the filter
// expression. The tokens are mainly non-whitespace groups.
-func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *formatAutoFilter) error {
+func (f *File) autoFilter(sheet, ref string, refRange, col int, formatSet *formatAutoFilter) error {
xlsx := f.workSheetReader(sheet)
if xlsx.SheetPr != nil {
xlsx.SheetPr.FilterMode = true
@@ -288,11 +276,13 @@ func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *fo
if formatSet.Column == "" || formatSet.Expression == "" {
return nil
}
- col := TitleToNumber(formatSet.Column)
- offset := col - hxAxis
+
+ fsCol := MustColumnNameToNumber(formatSet.Column)
+ offset := fsCol - col
if offset < 0 || offset > refRange {
return fmt.Errorf("incorrect index of column '%s'", formatSet.Column)
}
+
filter.FilterColumn = &xlsxFilterColumn{
ColID: offset,
}
@@ -315,7 +305,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, hxAxis int, formatSet *fo
func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []string) {
if len(exp) == 1 && exp[0] == 2 {
// Single equality.
- filters := []*xlsxFilter{}
+ var filters []*xlsxFilter
filters = append(filters, &xlsxFilter{Val: tokens[0]})
filter.FilterColumn.Filters = &xlsxFilters{Filter: filters}
} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {