Skip to content

Commit

Permalink
Add more complex tests and fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
TeeRenJing committed Oct 30, 2023
1 parent a3d31a1 commit af3a801
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 38 deletions.
93 changes: 63 additions & 30 deletions adjust.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
140 changes: 135 additions & 5 deletions adjust_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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))
Expand Down Expand Up @@ -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)

})
}
5 changes: 4 additions & 1 deletion col.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit af3a801

Please sign in to comment.