From 0ba9ee6c87b47445d4a1c6d0148ef788373aa91b Mon Sep 17 00:00:00 2001 From: rjtee Date: Mon, 23 Oct 2023 15:01:18 +0800 Subject: [PATCH 1/4] Update adjustFormula --- adjust.go | 64 ++++++++++++++++++++++++++++++++++++-------------- adjust_test.go | 8 +++---- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/adjust.go b/adjust.go index 5f408979b6..63faaeab35 100644 --- a/adjust.go +++ b/adjust.go @@ -14,6 +14,7 @@ package excelize import ( "bytes" "encoding/xml" + "github.com/xuri/efp" "io" "strings" ) @@ -131,7 +132,7 @@ func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error { if cellCol, cellRow, _ := CellNameToCoordinates(v.R); col <= cellCol { if newCol := cellCol + offset; newCol > 0 { ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow) - _ = f.adjustFormula(ws.SheetData.Row[rowIdx].C[colIdx].F, columns, offset, false) + _ = f.adjustFormula(ws.SheetData.Row[rowIdx].C[colIdx].F, columns, offset, false, col) } } } @@ -154,6 +155,9 @@ func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) error { r := &ws.SheetData.Row[i] if newRow := r.R + offset; r.R >= row && newRow > 0 { f.adjustSingleRowDimensions(r, newRow, offset, false) + for _, col := range r.C { + _ = f.adjustFormula(col.F, rows, offset, false, row) + } } } return nil @@ -165,31 +169,57 @@ func (f *File) adjustSingleRowDimensions(r *xlsxRow, num, offset int, si bool) { for i, col := range r.C { colName, _, _ := SplitCellName(col.R) r.C[i].R, _ = JoinCellName(colName, num) - _ = f.adjustFormula(col.F, rows, offset, si) } } // adjustFormula provides a function to adjust shared formula reference. -func (f *File) adjustFormula(formula *xlsxF, dir adjustDirection, offset int, si bool) error { - if formula != nil && formula.Ref != "" { - coordinates, err := rangeRefToCoordinates(formula.Ref) - if err != nil { - return err - } - if dir == columns { - coordinates[0] += offset - coordinates[2] += offset - } else { - coordinates[1] += offset - coordinates[3] += offset - } - if formula.Ref, err = f.coordinatesToRangeRef(coordinates); err != nil { - return err +func (f *File) adjustFormula(formula *xlsxF, dir adjustDirection, offset int, si bool, positionInserted int) error { + //if formula != nil && formula.Ref != "" { + // coordinates, err := rangeRefToCoordinates(formula.Ref) + // if err != nil { + // return err + // } + // if dir == columns { + // coordinates[0] += offset + // coordinates[2] += offset + // } else { + // coordinates[1] += offset + // coordinates[3] += offset + // } + // if formula.Ref, err = f.coordinatesToRangeRef(coordinates); err != nil { + // return err + // } + // if si && formula.Si != nil { + // formula.Si = intPtr(*formula.Si + 1) + // } + //} + + if formula != nil && formula.Content != "" { + ps, formulaText := efp.ExcelParser(), "=" + for _, token := range ps.Parse(formula.Content) { + if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { + col, row, _ := CellNameToCoordinates(token.TValue) + if dir == columns && col >= positionInserted { + col += offset + } else if dir == rows && row >= positionInserted { + row += offset + } + cell, err := CoordinatesToCellName(col, row, strings.Contains(token.TValue, "$")) + if err != nil { + return err + } + formulaText += cell + continue + } + formulaText += token.TValue } + formula.Content = formulaText + if si && formula.Si != nil { formula.Si = intPtr(*formula.Si + 1) } } + return nil } diff --git a/adjust_test.go b/adjust_test.go index f6147e6486..334e592acc 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -453,7 +453,7 @@ func TestAdjustFormula(t *testing.T) { assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) assert.NoError(t, f.InsertCols("Sheet1", "B", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) - for cell, expected := range map[string]string{"D2": "=A1+B1", "D3": "=A2+B2", "D11": "=A1+B1"} { + for cell, expected := range map[string]string{"D2": "=A2+C2", "D3": "=A3+C3", "D11": "=A11+C11"} { formula, err := f.GetCellFormula("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, formula) @@ -461,7 +461,7 @@ func TestAdjustFormula(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx"))) assert.NoError(t, f.Close()) - assert.NoError(t, f.adjustFormula(nil, rows, 0, false)) - assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "-"}, rows, 0, false), ErrParameterInvalid) - assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "XFD1:XFD1"}, columns, 1, false), ErrColumnNumber) + //assert.NoError(t, f.adjustFormula(nil, rows, 0, false)) + //assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "-"}, rows, 0, false), ErrParameterInvalid) + //assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "XFD1:XFD1"}, columns, 1, false), ErrColumnNumber) } From 488e955e25bec01a24cb2dde2eced7ad8355ecd0 Mon Sep 17 00:00:00 2001 From: rjtee Date: Mon, 23 Oct 2023 18:03:35 +0800 Subject: [PATCH 2/4] Add support for SUM formula --- adjust.go | 31 +++++++++++-------------------- adjust_test.go | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/adjust.go b/adjust.go index 63faaeab35..00ef7e51e3 100644 --- a/adjust.go +++ b/adjust.go @@ -174,29 +174,11 @@ func (f *File) adjustSingleRowDimensions(r *xlsxRow, num, offset int, si bool) { // adjustFormula provides a function to adjust shared formula reference. func (f *File) adjustFormula(formula *xlsxF, dir adjustDirection, offset int, si bool, positionInserted int) error { - //if formula != nil && formula.Ref != "" { - // coordinates, err := rangeRefToCoordinates(formula.Ref) - // if err != nil { - // return err - // } - // if dir == columns { - // coordinates[0] += offset - // coordinates[2] += offset - // } else { - // coordinates[1] += offset - // coordinates[3] += offset - // } - // if formula.Ref, err = f.coordinatesToRangeRef(coordinates); err != nil { - // return err - // } - // if si && formula.Si != nil { - // formula.Si = intPtr(*formula.Si + 1) - // } - //} if formula != nil && formula.Content != "" { ps, formulaText := efp.ExcelParser(), "=" - for _, token := range ps.Parse(formula.Content) { + ast := ps.Parse(formula.Content) + for _, token := range ast { if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { col, row, _ := CellNameToCoordinates(token.TValue) if dir == columns && col >= positionInserted { @@ -210,6 +192,15 @@ func (f *File) adjustFormula(formula *xlsxF, dir adjustDirection, offset int, si } formulaText += cell continue + } else if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart { + formulaText += token.TValue + "(" + continue + } else if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop { + formulaText += ")" + continue + } else if token.TType == efp.TokenTypeArgument { + formulaText += ", " + continue } formulaText += token.TValue } diff --git a/adjust_test.go b/adjust_test.go index 334e592acc..e7db52dc80 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -461,7 +461,20 @@ func TestAdjustFormula(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx"))) assert.NoError(t, f.Close()) - //assert.NoError(t, f.adjustFormula(nil, rows, 0, false)) - //assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "-"}, rows, 0, false), ErrParameterInvalid) - //assert.Equal(t, f.adjustFormula(&xlsxF{Ref: "XFD1:XFD1"}, columns, 1, false), ErrColumnNumber) + assert.NoError(t, f.adjustFormula(nil, rows, 0, false, 1)) + +} + +func TestAdjustSumFormula(t *testing.T) { + f := NewFile() + assert.NoError(t, f.SetCellFormula("Sheet1", "B2", "=SUM(B4, B5, B6, B7)")) + assert.NoError(t, f.InsertCols("Sheet1", "B", 1)) + assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) + for cell, expected := range map[string]string{"C3": "=SUM(C5, C6, C7, C8)"} { + formula, err := f.GetCellFormula("Sheet1", cell) + assert.NoError(t, err) + assert.Equal(t, expected, formula) + } + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustSumFormula.xlsx"))) + assert.NoError(t, f.Close()) } From aa6ac8fac58ce4e13007efb8eaf7260938bf2885 Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 23 Oct 2023 20:51:22 +0800 Subject: [PATCH 3/4] This closes #1306 and closes #1615 - Support adjust formula on inserting/deleting columns/rows --- adjust.go | 152 +++++++++++++++++++++++++++++++++---------------- adjust_test.go | 58 +++++++++++++------ calc.go | 2 +- calc_test.go | 2 +- lib.go | 3 + rows.go | 2 +- rows_test.go | 15 +++++ 7 files changed, 165 insertions(+), 69 deletions(-) diff --git a/adjust.go b/adjust.go index 00ef7e51e3..fdaa162b1e 100644 --- a/adjust.go +++ b/adjust.go @@ -14,9 +14,10 @@ package excelize import ( "bytes" "encoding/xml" - "github.com/xuri/efp" "io" "strings" + + "github.com/xuri/efp" ) type adjustDirection bool @@ -43,9 +44,9 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) } sheetID := f.getSheetID(sheet) if dir == rows { - err = f.adjustRowDimensions(ws, num, offset) + err = f.adjustRowDimensions(sheet, ws, num, offset) } else { - err = f.adjustColDimensions(ws, num, offset) + err = f.adjustColDimensions(sheet, ws, num, offset) } if err != nil { return err @@ -117,7 +118,7 @@ func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error { // adjustColDimensions provides a function to update column dimensions when // inserting or deleting rows or columns. -func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error { +func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset int) error { for rowIdx := range ws.SheetData.Row { for _, v := range ws.SheetData.Row[rowIdx].C { if cellCol, _, _ := CellNameToCoordinates(v.R); col <= cellCol { @@ -132,9 +133,11 @@ func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error { if cellCol, cellRow, _ := CellNameToCoordinates(v.R); col <= cellCol { if newCol := cellCol + offset; newCol > 0 { ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow) - _ = f.adjustFormula(ws.SheetData.Row[rowIdx].C[colIdx].F, columns, offset, false, col) } } + if err := f.adjustFormula(sheet, ws.SheetData.Row[rowIdx].C[colIdx].F, columns, col, offset, false); err != nil { + return err + } } } return f.adjustCols(ws, col, offset) @@ -142,21 +145,20 @@ func (f *File) adjustColDimensions(ws *xlsxWorksheet, col, offset int) error { // adjustRowDimensions provides a function to update row dimensions when // inserting or deleting rows or columns. -func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) error { +func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset int) error { totalRows := len(ws.SheetData.Row) if totalRows == 0 { return nil } lastRow := &ws.SheetData.Row[totalRows-1] - if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow >= TotalRows { + if newRow := lastRow.R + offset; lastRow.R >= row && newRow > 0 && newRow > TotalRows { return ErrMaxRows } for i := 0; i < len(ws.SheetData.Row); i++ { r := &ws.SheetData.Row[i] if newRow := r.R + offset; r.R >= row && newRow > 0 { - f.adjustSingleRowDimensions(r, newRow, offset, false) - for _, col := range r.C { - _ = f.adjustFormula(col.F, rows, offset, false, row) + if err := f.adjustSingleRowDimensions(sheet, r, row, offset, false); err != nil { + return err } } } @@ -164,56 +166,102 @@ func (f *File) adjustRowDimensions(ws *xlsxWorksheet, row, offset int) error { } // adjustSingleRowDimensions provides a function to adjust single row dimensions. -func (f *File) adjustSingleRowDimensions(r *xlsxRow, num, offset int, si bool) { - r.R = num +func (f *File) adjustSingleRowDimensions(sheet string, r *xlsxRow, num, offset int, si bool) error { + r.R += offset for i, col := range r.C { colName, _, _ := SplitCellName(col.R) - r.C[i].R, _ = JoinCellName(colName, num) + r.C[i].R, _ = JoinCellName(colName, r.R) + if err := f.adjustFormula(sheet, col.F, rows, num, offset, si); err != nil { + return err + } } + return nil } -// adjustFormula provides a function to adjust shared formula reference. -func (f *File) adjustFormula(formula *xlsxF, dir adjustDirection, offset int, si bool, positionInserted int) error { - - if formula != nil && formula.Content != "" { - ps, formulaText := efp.ExcelParser(), "=" - ast := ps.Parse(formula.Content) - for _, token := range ast { - if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { - col, row, _ := CellNameToCoordinates(token.TValue) - if dir == columns && col >= positionInserted { - col += offset - } else if dir == rows && row >= positionInserted { - row += offset - } - cell, err := CoordinatesToCellName(col, row, strings.Contains(token.TValue, "$")) - if err != nil { - return err - } - formulaText += cell - continue - } else if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart { - formulaText += token.TValue + "(" - continue - } else if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop { - formulaText += ")" - continue - } else if token.TType == efp.TokenTypeArgument { - formulaText += ", " - continue - } - formulaText += token.TValue +// adjustFormula provides a function to adjust formula reference and shared +// formula reference. +func (f *File) adjustFormula(sheet string, formula *xlsxF, dir adjustDirection, num, offset int, si bool) error { + if formula == nil { + return nil + } + adjustRef := func(ref string) (string, error) { + coordinates, err := rangeRefToCoordinates(ref) + if err != nil { + return ref, err + } + if dir == columns { + coordinates[0] += offset + coordinates[2] += offset + } else { + coordinates[1] += offset + coordinates[3] += offset + } + return f.coordinatesToRangeRef(coordinates) + } + var err error + if formula.Ref != "" { + if formula.Ref, err = adjustRef(formula.Ref); err != nil { + return err } - formula.Content = formulaText - if si && formula.Si != nil { formula.Si = intPtr(*formula.Si + 1) } } - + if formula.T == STCellFormulaTypeArray { + formula.Content, err = adjustRef(strings.TrimPrefix(formula.Content, "=")) + return err + } + if formula.Content != "" && !strings.ContainsAny(formula.Content, "[:]") { + content, err := f.adjustFormulaRef(sheet, formula.Content, dir, num, offset) + if err != nil { + return err + } + formula.Content = content + } return nil } +// adjustFormulaRef returns adjusted formula text by giving adjusted direction +// and the base number of column or row, and offset. +func (f *File) adjustFormulaRef(sheet string, text string, dir adjustDirection, num, offset int) (string, error) { + var ( + formulaText string + definedNames []string + ps = efp.ExcelParser() + ) + for _, definedName := range f.GetDefinedName() { + if definedName.Scope == "Workbook" || definedName.Scope == sheet { + definedNames = append(definedNames, definedName.Name) + } + } + for _, token := range ps.Parse(text) { + if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { + if inStrSlice(definedNames, token.TValue, true) != -1 { + formulaText += token.TValue + continue + } + c, r, err := CellNameToCoordinates(token.TValue) + if err != nil { + return formulaText, err + } + if dir == columns && c >= num { + c += offset + } + if dir == rows { + r += offset + } + cell, err := CoordinatesToCellName(c, r, strings.Contains(token.TValue, "$")) + if err != nil { + return formulaText, err + } + formulaText += cell + continue + } + formulaText += token.TValue + } + return formulaText, nil +} + // adjustHyperlinks provides a function to update hyperlinks when inserting or // deleting rows or columns. func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) { @@ -281,7 +329,7 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, return } // Remove the table when deleting the header row of the table - if dir == rows && num == coordinates[0] { + if dir == rows && num == coordinates[0] && offset == -1 { ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...) ws.TableParts.Count = len(ws.TableParts.TableParts) idx-- @@ -458,11 +506,19 @@ func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) er return err } if dir == rows && num <= rowNum { + if num == rowNum && offset == -1 { + _ = f.deleteCalcChain(c.I, c.R) + continue + } if newRow := rowNum + offset; newRow > 0 { f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow) } } if dir == columns && num <= colNum { + if num == colNum && offset == -1 { + _ = f.deleteCalcChain(c.I, c.R) + continue + } if newCol := colNum + offset; newCol > 0 { f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum) } diff --git a/adjust_test.go b/adjust_test.go index e7db52dc80..84ba140a6d 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -357,13 +357,15 @@ func TestAdjustHelper(t *testing.T) { func TestAdjustCalcChain(t *testing.T) { f := NewFile() f.CalcChain = &xlsxCalcChain{ - C: []xlsxCalcChainC{ - {R: "B2", I: 2}, {R: "B2", I: 1}, - }, + C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}, {R: "A1", I: 1}}, } assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) + assert.NoError(t, f.RemoveRow("Sheet1", 3)) + assert.NoError(t, f.RemoveCol("Sheet1", "B")) + + f.CalcChain = &xlsxCalcChain{C: []xlsxCalcChainC{{R: "B2", I: 2}, {R: "B2", I: 1}}} f.CalcChain.C[1].R = "invalid coordinates" assert.Equal(t, f.InsertCols("Sheet1", "A", 1), newCellNameToCoordinatesError("invalid coordinates", newInvalidCellNameError("invalid coordinates"))) f.CalcChain = nil @@ -449,11 +451,11 @@ func TestAdjustCols(t *testing.T) { func TestAdjustFormula(t *testing.T) { f := NewFile() formulaType, ref := STCellFormulaTypeShared, "C1:C5" - assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) assert.NoError(t, f.InsertCols("Sheet1", "B", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) - for cell, expected := range map[string]string{"D2": "=A2+C2", "D3": "=A3+C3", "D11": "=A11+C11"} { + for cell, expected := range map[string]string{"D2": "A2+C2", "D3": "A3+C3", "D11": "A11+C11"} { formula, err := f.GetCellFormula("Sheet1", cell) assert.NoError(t, err) assert.Equal(t, expected, formula) @@ -461,20 +463,40 @@ func TestAdjustFormula(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx"))) assert.NoError(t, f.Close()) - assert.NoError(t, f.adjustFormula(nil, rows, 0, false, 1)) + assert.NoError(t, f.adjustFormula("Sheet1", nil, rows, 0, 0, false)) + assert.Equal(t, ErrParameterInvalid, f.adjustFormula("Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false)) + assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false)) -} + _, err := f.adjustFormulaRef("Sheet1", "XFE1", columns, 0, 1) + assert.Equal(t, ErrColumnNumber, err) + _, err = f.adjustFormulaRef("Sheet1", "XFD1", columns, 0, 1) + assert.Equal(t, ErrColumnNumber, err) -func TestAdjustSumFormula(t *testing.T) { - f := NewFile() - assert.NoError(t, f.SetCellFormula("Sheet1", "B2", "=SUM(B4, B5, B6, B7)")) - assert.NoError(t, f.InsertCols("Sheet1", "B", 1)) + f = NewFile() + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "XFD1")) + assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet1", "A", 1)) + + assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("A%d", TotalRows))) + assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) + + // Test adjust formula with defined name in formula text + f = NewFile() + assert.NoError(t, f.SetDefinedName(&DefinedName{ + Name: "Amount", + RefersTo: "Sheet1!$B$2", + })) + assert.NoError(t, f.SetCellFormula("Sheet1", "B2", "Amount+B3")) + assert.NoError(t, f.RemoveRow("Sheet1", 1)) + formula, err := f.GetCellFormula("Sheet1", "B1") + assert.NoError(t, err) + assert.Equal(t, "Amount+B2", formula) + + // Test adjust formula with array formula + f = NewFile() + formulaType, reference := STCellFormulaTypeArray, "A3:A3" + assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "=A1:A2", FormulaOpts{Ref: &reference, Type: &formulaType})) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) - for cell, expected := range map[string]string{"C3": "=SUM(C5, C6, C7, C8)"} { - formula, err := f.GetCellFormula("Sheet1", cell) - assert.NoError(t, err) - assert.Equal(t, expected, formula) - } - assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustSumFormula.xlsx"))) - assert.NoError(t, f.Close()) + formula, err = f.GetCellFormula("Sheet1", "A4") + assert.NoError(t, err) + assert.Equal(t, "A2:A3", formula) } diff --git a/calc.go b/calc.go index 1320238827..1c1d8e9597 100644 --- a/calc.go +++ b/calc.go @@ -14454,7 +14454,7 @@ func (fn *formulaFuncs) ADDRESS(argsList *list.List) formulaArg { if rowNum.Type != ArgNumber { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } - if rowNum.Number >= TotalRows { + if rowNum.Number > TotalRows { return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) } colNum := argsList.Front().Next().Value.(formulaArg).ToNumber() diff --git a/calc_test.go b/calc_test.go index 5e97a0eff7..336d085b52 100644 --- a/calc_test.go +++ b/calc_test.go @@ -3970,7 +3970,7 @@ func TestCalcCellValue(t *testing.T) { "=ADDRESS(1,1,0,TRUE)": {"#NUM!", "#NUM!"}, "=ADDRESS(1,16385,2,TRUE)": {"#VALUE!", "#VALUE!"}, "=ADDRESS(1,16385,3,TRUE)": {"#VALUE!", "#VALUE!"}, - "=ADDRESS(1048576,1,1,TRUE)": {"#VALUE!", "#VALUE!"}, + "=ADDRESS(1048577,1,1,TRUE)": {"#VALUE!", "#VALUE!"}, // CHOOSE "=CHOOSE()": {"#VALUE!", "CHOOSE requires 2 arguments"}, "=CHOOSE(\"index_num\",0)": {"#VALUE!", "CHOOSE requires first argument of type number"}, diff --git a/lib.go b/lib.go index a69446312f..bc564225e2 100644 --- a/lib.go +++ b/lib.go @@ -270,6 +270,9 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) { if col < 1 || row < 1 { return "", newCoordinatesToCellNameError(col, row) } + if row > TotalRows { + return "", ErrMaxRows + } sign := "" for _, a := range abs { if a { diff --git a/rows.go b/rows.go index 972707d38e..88d1f6660d 100644 --- a/rows.go +++ b/rows.go @@ -652,7 +652,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { } rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) - f.adjustSingleRowDimensions(&rowCopy, row2, row2-row, true) + _ = f.adjustSingleRowDimensions(sheet, &rowCopy, row, row2-row, true) if idx2 != -1 { ws.SheetData.Row[idx2] = rowCopy diff --git a/rows_test.go b/rows_test.go index 768f8b01c1..3e49580293 100644 --- a/rows_test.go +++ b/rows_test.go @@ -870,6 +870,21 @@ func TestDuplicateRow(t *testing.T) { f := NewFile() // Test duplicate row with invalid sheet name assert.EqualError(t, f.DuplicateRowTo("Sheet:1", 1, 2), ErrSheetNameInvalid.Error()) + + f = NewFile() + assert.NoError(t, f.SetDefinedName(&DefinedName{ + Name: "Amount", + RefersTo: "Sheet1!$B$1", + })) + assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "Amount+C1")) + assert.NoError(t, f.SetCellValue("Sheet1", "A10", "A10")) + assert.NoError(t, f.DuplicateRowTo("Sheet1", 1, 10)) + formula, err := f.GetCellFormula("Sheet1", "A10") + assert.NoError(t, err) + assert.Equal(t, "Amount+C10", formula) + value, err := f.GetCellValue("Sheet1", "A11") + assert.NoError(t, err) + assert.Equal(t, "A10", value) } func TestDuplicateRowTo(t *testing.T) { From 81c5fc0bf8a7a7d76a5c5844431c21c56e57a2c7 Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 23 Oct 2023 22:56:02 +0800 Subject: [PATCH 4/4] This closes #1306 and closes #1615 - Support adjust formula on inserting/deleting columns/rows --- adjust.go | 35 ++++++++++++++++++++++++++--------- adjust_test.go | 3 +++ xmlCalcChain.go | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/adjust.go b/adjust.go index fdaa162b1e..3708401072 100644 --- a/adjust.go +++ b/adjust.go @@ -221,7 +221,7 @@ func (f *File) adjustFormula(sheet string, formula *xlsxF, dir adjustDirection, return nil } -// adjustFormulaRef returns adjusted formula text by giving adjusted direction +// adjustFormulaRef returns adjusted formula text by giving adjusting direction // and the base number of column or row, and offset. func (f *File) adjustFormulaRef(sheet string, text string, dir adjustDirection, num, offset int) (string, error) { var ( @@ -385,8 +385,8 @@ func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, off } // adjustAutoFilterHelper provides a function for adjusting auto filter to -// compare and calculate cell reference by the given adjust direction, operation -// reference and offset. +// compare and calculate cell reference by the giving adjusting direction, +// operation reference and offset. func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, num, offset int) []int { if dir == rows { if coordinates[1] >= num { @@ -491,13 +491,34 @@ func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) { } } +// adjustCalcChainRef update the cell reference in calculation chain when +// inserting or deleting rows or columns. +func (f *File) adjustCalcChainRef(i, c, r, offset int, dir adjustDirection) { + if dir == rows { + if rn := r + offset; rn > 0 { + f.CalcChain.C[i].R, _ = CoordinatesToCellName(c, rn) + } + return + } + if nc := c + offset; nc > 0 { + f.CalcChain.C[i].R, _ = CoordinatesToCellName(nc, r) + } +} + // adjustCalcChain provides a function to update the calculation chain when // inserting or deleting rows or columns. func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error { if f.CalcChain == nil { return nil } + // If sheet ID is omitted, it is assumed to be the same as the i value of + // the previous cell. + var prevSheetID int for index, c := range f.CalcChain.C { + if c.I == 0 { + c.I = prevSheetID + } + prevSheetID = c.I if c.I != sheetID { continue } @@ -510,18 +531,14 @@ func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) er _ = f.deleteCalcChain(c.I, c.R) continue } - if newRow := rowNum + offset; newRow > 0 { - f.CalcChain.C[index].R, _ = CoordinatesToCellName(colNum, newRow) - } + f.adjustCalcChainRef(index, colNum, rowNum, offset, dir) } if dir == columns && num <= colNum { if num == colNum && offset == -1 { _ = f.deleteCalcChain(c.I, c.R) continue } - if newCol := colNum + offset; newCol > 0 { - f.CalcChain.C[index].R, _ = CoordinatesToCellName(newCol, rowNum) - } + f.adjustCalcChainRef(index, colNum, rowNum, offset, dir) } } return nil diff --git a/adjust_test.go b/adjust_test.go index 84ba140a6d..793659ff67 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -362,6 +362,9 @@ func TestAdjustCalcChain(t *testing.T) { assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) + f.CalcChain = &xlsxCalcChain{ + C: []xlsxCalcChainC{{R: "B2", I: 1}, {R: "B3"}, {R: "A1"}}, + } assert.NoError(t, f.RemoveRow("Sheet1", 3)) assert.NoError(t, f.RemoveCol("Sheet1", "B")) diff --git a/xmlCalcChain.go b/xmlCalcChain.go index 3631565aad..9c1d1ee21f 100644 --- a/xmlCalcChain.go +++ b/xmlCalcChain.go @@ -76,7 +76,7 @@ type xlsxCalcChain struct { // | boolean datatype. type xlsxCalcChainC struct { R string `xml:"r,attr"` - I int `xml:"i,attr"` + I int `xml:"i,attr,omitempty"` L bool `xml:"l,attr,omitempty"` S bool `xml:"s,attr,omitempty"` T bool `xml:"t,attr,omitempty"`