From af3a801987e146e5ae1f28437b7df20f8d0fd133 Mon Sep 17 00:00:00 2001 From: rjtee Date: Mon, 30 Oct 2023 18:48:29 +0800 Subject: [PATCH] Add more complex tests and fix bugs --- adjust.go | 93 +++++++++++++++++++++----------- adjust_test.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++-- col.go | 5 +- rows.go | 7 ++- 4 files changed, 207 insertions(+), 38 deletions(-) diff --git a/adjust.go b/adjust.go index c7657301c5..7546f9dace 100644 --- a/adjust.go +++ b/adjust.go @@ -74,22 +74,30 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) return nil } -func (f *File) adjustOtherSheetHelper(sheet string, dir adjustDirection, num, offset int) { +func (f *File) adjustOtherSheetHelper(sheet, changedSheet string, dir adjustDirection, num, offset int) error { ws, _ := f.workSheetReader(sheet) if dir == rows { numOfRows := len(ws.SheetData.Row) for i := 0; i < numOfRows; i++ { r := &ws.SheetData.Row[i] - f.adjustSingleRowFormulas(sheet, r, num, offset, false, true) + err := f.adjustSingleRowFormulas(sheet, changedSheet, r, num, offset, false, true) + if err != nil { + return err + } } } else { for rowIdx := range ws.SheetData.Row { for colIdx, _ := range ws.SheetData.Row[rowIdx].C { - f.adjustFormula(sheet, ws.SheetData.Row[rowIdx].C[colIdx].F, columns, num, offset, false, true) + err := f.adjustFormula(sheet, changedSheet, ws.SheetData.Row[rowIdx].C[colIdx].F, columns, num, offset, false, true) + if err != nil { + return err + } } } } + + return nil } // adjustCols provides a function to update column style when inserting or @@ -155,7 +163,7 @@ func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset ws.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow) } } - if err := f.adjustFormula(sheet, ws.SheetData.Row[rowIdx].C[colIdx].F, columns, col, offset, false, false); err != nil { + if err := f.adjustFormula(sheet, sheet, ws.SheetData.Row[rowIdx].C[colIdx].F, columns, col, offset, false, false); err != nil { return err } } @@ -180,7 +188,7 @@ func (f *File) adjustRowDimensions(sheet string, ws *xlsxWorksheet, row, offset if newRow := r.R + offset; r.R >= row && newRow > 0 { r.adjustSingleRowDimensions(offset) } - if err := f.adjustSingleRowFormulas(sheet, r, row, offset, false, false); err != nil { + if err := f.adjustSingleRowFormulas(sheet, sheet, r, row, offset, false, false); err != nil { return err } } @@ -197,9 +205,9 @@ func (r *xlsxRow) adjustSingleRowDimensions(offset int) { } // adjustSingleRowFormulas provides a function to adjust single row formulas. -func (f *File) adjustSingleRowFormulas(sheet string, r *xlsxRow, num, offset int, si, onlyOtherSheetRefs bool) error { +func (f *File) adjustSingleRowFormulas(sheet, changedSheet string, r *xlsxRow, num, offset int, si, onlyOtherSheetRefs bool) error { for _, col := range r.C { - if err := f.adjustFormula(sheet, col.F, rows, num, offset, si, onlyOtherSheetRefs); err != nil { + if err := f.adjustFormula(sheet, changedSheet, col.F, rows, num, offset, si, onlyOtherSheetRefs); err != nil { return err } } @@ -235,7 +243,9 @@ func (f *File) adjustCellRef(ref string, dir adjustDirection, num, offset int) ( // 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, onlyOtherSheetRefs bool) error { +func (f *File) adjustFormula(sheet, changedSheet string, formula *xlsxF, dir adjustDirection, num, + offset int, si, + onlyOtherSheetRefs bool) error { if formula == nil { return nil } @@ -249,7 +259,7 @@ func (f *File) adjustFormula(sheet string, formula *xlsxF, dir adjustDirection, } } if formula.Content != "" { - if formula.Content, err = f.adjustFormulaRef(sheet, formula.Content, dir, num, offset, onlyOtherSheetRefs); err != nil { + if formula.Content, err = f.adjustFormulaRef(sheet, changedSheet, formula.Content, dir, num, offset, onlyOtherSheetRefs); err != nil { return err } } @@ -351,14 +361,54 @@ func (f *File) adjustFormulaOperand(token efp.Token, dir adjustDirection, num in return operand, err } +func isOtherSheetRef(formula, changedSheet string) bool { + return strings.Contains(formula, "!") && strings.Contains(formula, changedSheet) +} + +func (f *File) handleExternalSheetRefToken(val *string, token efp.Token, changedSheet string, dir adjustDirection, num, offset int) error { + if !strings.Contains(token.TValue, changedSheet) { + *val += token.TValue + } else { + // extract string after the first '!' + *val += strings.Split(token.TValue, "!")[0] + "!" + token.TValue = strings.Split(token.TValue, "!")[1] + operand, err := f.adjustFormulaOperand(token, dir, num, offset) + if err != nil { + return err + } + *val += operand + } + return nil +} + +func (f *File) handleRangeOperand(val *string, token efp.Token, sheet, changedSheet string, dir adjustDirection, num, offset int, definedNames []string) error { + if inStrSlice(definedNames, token.TValue, true) != -1 { + *val += token.TValue + } else if strings.ContainsAny(token.TValue, "[]") { + *val += token.TValue + } else if strings.Contains(token.TValue, "!") { + if err := f.handleExternalSheetRefToken(val, token, changedSheet, dir, num, offset); err != nil { + return err + } + } else if sheet == changedSheet { + operand, err := f.adjustFormulaOperand(token, dir, num, offset) + if err != nil { + return err + } + *val += operand + } else { + *val += token.TValue + } + return nil +} + // adjustFormulaRef returns adjusted formula by giving adjusting direction and // the base number of column or row, and offset. -func (f *File) adjustFormulaRef(sheet, formula string, dir adjustDirection, num, offset int, onlyOtherSheetRefs bool) (string, error) { +func (f *File) adjustFormulaRef(sheet, changedSheet, formula string, dir adjustDirection, num, offset int, onlyOtherSheetRefs bool) (string, error) { // check if formula is referencing other sheet - if !strings.Contains(formula, "!") && onlyOtherSheetRefs { + if onlyOtherSheetRefs && !isOtherSheetRef(formula, changedSheet) { return formula, nil } - var ( val string definedNames []string @@ -371,26 +421,9 @@ func (f *File) adjustFormulaRef(sheet, formula string, dir adjustDirection, num, } for _, token := range ps.Parse(formula) { if isRangeOperand(token) { - if inStrSlice(definedNames, token.TValue, true) != -1 { - val += token.TValue - continue - } - if strings.ContainsAny(token.TValue, "[]") { - val += token.TValue - continue - } - if strings.Contains(token.TValue, "!") { - // extract string before the first '!' - refSheetName := strings.Split(token.TValue, "!")[0] + "!" - // extract string after the first '!' - token.TValue = strings.Split(token.TValue, "!")[1] - val += refSheetName - } - operand, err := f.adjustFormulaOperand(token, dir, num, offset) - if err != nil { + if err := f.handleRangeOperand(&val, token, sheet, changedSheet, dir, num, offset, definedNames); err != nil { return val, err } - val += operand continue } if isFunctionStart(token) { diff --git a/adjust_test.go b/adjust_test.go index 2d48da509e..7c542920d4 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -523,13 +523,13 @@ 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("Sheet1", nil, rows, 0, 0, false, false)) - assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false, false)) - assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false, false)) + assert.NoError(t, f.adjustFormula("Sheet1", "Sheet1", nil, rows, 0, 0, false, false)) + assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false, false)) + assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false, false)) - _, err := f.adjustFormulaRef("Sheet1", "XFE1", columns, 0, 1, false) + _, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", columns, 0, 1, false) assert.Equal(t, ErrColumnNumber, err) - _, err = f.adjustFormulaRef("Sheet1", "XFD1", columns, 0, 1, false) + _, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", columns, 0, 1, false) assert.Equal(t, ErrColumnNumber, err) f = NewFile() @@ -539,6 +539,15 @@ func TestAdjustFormula(t *testing.T) { assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("A%d", TotalRows))) assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) + f = NewFile() + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "Sheet2!XFD1")) + assert.Equal(t, ErrColumnNumber, f.InsertCols("Sheet2", "A", 1)) + + assert.NoError(t, f.SetCellFormula("Sheet1", "B2", fmt.Sprintf("Sheet2!A%d", TotalRows))) + assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet2", 1, 1)) + f = NewFile() assert.NoError(t, f.SetCellFormula("Sheet1", "B3", "SUM(1048576:1:2)")) assert.Equal(t, ErrMaxRows, f.InsertRows("Sheet1", 1, 1)) @@ -908,3 +917,124 @@ func TestSheet1RefSheet2(t *testing.T) { assert.Equal(t, "SUM(A4,B4)", formula) }) } + +func TestMixedCrossSheetReferences(t *testing.T) { + t.Run("mix_different_kind_of_references_test_rows)", func(t *testing.T) { + f := NewFile() + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) + + // Tests formulas referencing Sheet2 should update + // but those referencing the original sheet or Sheet 3 should not update + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "Sheet2!A1+Sheet2!A2+Sheet1!A3+Sheet1!A4")) + assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "Sheet2!B1+Sheet2!B2+B3+B4")) + assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "Sheet2!C1+Sheet2!C2+Sheet3!A3+Sheet3!A4")) + assert.NoError(t, f.SetCellFormula("Sheet1", "E1", "SUM(Sheet2!D1:D2,Sheet1!A3:A4)")) + assert.NoError(t, f.SetCellFormula("Sheet1", "F1", "SUM(Sheet2!E1:E2,A3:A4)")) + assert.NoError(t, f.SetCellFormula("Sheet1", "G1", "SUM(Sheet2!F1:F2,Sheet3!A3:A4)")) + + // insert row in the middle of the range + assert.NoError(t, f.InsertRows("Sheet2", 2, 1)) + formula, err := f.GetCellFormula("Sheet1", "B1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!A1+Sheet2!A3+Sheet1!A3+Sheet1!A4", formula) + formula, err = f.GetCellFormula("Sheet1", "C1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!B1+Sheet2!B3+B3+B4", formula) + formula, err = f.GetCellFormula("Sheet1", "D1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!C1+Sheet2!C3+Sheet3!A3+Sheet3!A4", formula) + formula, err = f.GetCellFormula("Sheet1", "E1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!D1:D3,Sheet1!A3:A4)", formula) + formula, err = f.GetCellFormula("Sheet1", "F1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!E1:E3,A3:A4)", formula) + formula, err = f.GetCellFormula("Sheet1", "G1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!F1:F3,Sheet3!A3:A4)", formula) + + // insert row in the top of the range + assert.NoError(t, f.InsertRows("Sheet2", 1, 1)) + formula, err = f.GetCellFormula("Sheet1", "B1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!A2+Sheet2!A4+Sheet1!A3+Sheet1!A4", formula) + formula, err = f.GetCellFormula("Sheet1", "C1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!B2+Sheet2!B4+B3+B4", formula) + formula, err = f.GetCellFormula("Sheet1", "D1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!C2+Sheet2!C4+Sheet3!A3+Sheet3!A4", formula) + formula, err = f.GetCellFormula("Sheet1", "E1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!D2:D4,Sheet1!A3:A4)", formula) + formula, err = f.GetCellFormula("Sheet1", "F1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!E2:E4,A3:A4)", formula) + formula, err = f.GetCellFormula("Sheet1", "G1") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!F2:F4,Sheet3!A3:A4)", formula) + }) + + t.Run("mix_different_kind_of_references_test_cols)", func(t *testing.T) { + f := NewFile() + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) + + // Tests formulas referencing Sheet2 should update + // but those referencing the original sheet or Sheet 3 should not update + assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "Sheet2!A1+Sheet2!B1+Sheet1!C1+Sheet1!D1")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "Sheet2!A2+Sheet2!B2+C2+D2")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "Sheet2!A3+Sheet2!B3+Sheet3!C3+Sheet3!D3")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A4", "SUM(Sheet2!A4:B4,Sheet1!C4:D4)")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A5", "SUM(Sheet2!A5:B5,C5:D5)")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A6", "SUM(Sheet2!A6:B6,Sheet3!C6:D6)")) + + // insert row in the middle of the range + assert.NoError(t, f.InsertCols("Sheet2", "B", 1)) + formula, err := f.GetCellFormula("Sheet1", "A1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!A1+Sheet2!C1+Sheet1!C1+Sheet1!D1", formula) + formula, err = f.GetCellFormula("Sheet1", "A2") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!A2+Sheet2!C2+C2+D2", formula) + formula, err = f.GetCellFormula("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!A3+Sheet2!C3+Sheet3!C3+Sheet3!D3", formula) + formula, err = f.GetCellFormula("Sheet1", "A4") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!A4:C4,Sheet1!C4:D4)", formula) + formula, err = f.GetCellFormula("Sheet1", "A5") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!A5:C5,C5:D5)", formula) + formula, err = f.GetCellFormula("Sheet1", "A6") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!A6:C6,Sheet3!C6:D6)", formula) + + // insert row in the top of the range + assert.NoError(t, f.InsertCols("Sheet2", "A", 1)) + formula, err = f.GetCellFormula("Sheet1", "A1") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!B1+Sheet2!D1+Sheet1!C1+Sheet1!D1", formula) + formula, err = f.GetCellFormula("Sheet1", "A2") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!B2+Sheet2!D2+C2+D2", formula) + formula, err = f.GetCellFormula("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "Sheet2!B3+Sheet2!D3+Sheet3!C3+Sheet3!D3", formula) + formula, err = f.GetCellFormula("Sheet1", "A4") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!B4:D4,Sheet1!C4:D4)", formula) + formula, err = f.GetCellFormula("Sheet1", "A5") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!B5:D5,C5:D5)", formula) + formula, err = f.GetCellFormula("Sheet1", "A6") + assert.NoError(t, err) + assert.Equal(t, "SUM(Sheet2!B6:D6,Sheet3!C6:D6)", formula) + + }) +} diff --git a/col.go b/col.go index 04bc98d12f..9934f0fd3a 100644 --- a/col.go +++ b/col.go @@ -752,7 +752,10 @@ func (f *File) InsertCols(sheet, col string, n int) error { if s == sheet { continue } - f.adjustOtherSheetHelper(s, columns, num, n) + err = f.adjustOtherSheetHelper(s, sheet, columns, num, n) + if err != nil { + return err + } } return nil diff --git a/rows.go b/rows.go index 83e37dccf2..b857a8f2d8 100644 --- a/rows.go +++ b/rows.go @@ -594,7 +594,10 @@ func (f *File) InsertRows(sheet string, row, n int) error { if s == sheet { continue } - f.adjustOtherSheetHelper(s, rows, row, n) + err = f.adjustOtherSheetHelper(s, sheet, rows, row, n) + if err != nil { + return err + } } return nil @@ -667,7 +670,7 @@ func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { rowCopy.C = append(make([]xlsxC, 0, len(rowCopy.C)), rowCopy.C...) rowCopy.adjustSingleRowDimensions(row2 - row) - _ = f.adjustSingleRowFormulas(sheet, &rowCopy, row, row2-row, true, false) + _ = f.adjustSingleRowFormulas(sheet, sheet, &rowCopy, row, row2-row, true, false) if idx2 != -1 { ws.SheetData.Row[idx2] = rowCopy