This fixed across worksheet reference issue for the formula calculation engine

This commit is contained in:
xuri 2023-05-17 00:05:27 +08:00
parent 1088302331
commit ef3e81de8e
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
2 changed files with 118 additions and 108 deletions

178
calc.go
View File

@ -951,9 +951,6 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
if err != nil {
return result, err
}
if result.Type == ArgError {
return result, errors.New(result.Error)
}
opfdStack.Push(result)
continue
}
@ -965,10 +962,7 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
}
result, err := f.parseReference(ctx, sheet, token.TValue)
if err != nil {
return newEmptyFormulaArg(), err
}
if result.Type == ArgUnknown {
return newEmptyFormulaArg(), errors.New(formulaErrorVALUE)
return result, err
}
// when current token is range, next token is argument and opfdStack not empty,
// should push value to opfdStack and continue
@ -1442,74 +1436,99 @@ func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdSt
return nil
}
// parseRef parse reference for a cell, column name or row number.
func (f *File) parseRef(ref string) (cellRef, bool, bool, error) {
var (
err, colErr, rowErr error
cr cellRef
cell = ref
tokens = strings.Split(ref, "!")
)
if len(tokens) == 2 { // have a worksheet
cr.Sheet, cell = tokens[0], tokens[1]
}
if cr.Col, cr.Row, err = CellNameToCoordinates(cell); err != nil {
if cr.Col, colErr = ColumnNameToNumber(cell); colErr == nil { // cast to column
return cr, true, false, nil
}
if cr.Row, rowErr = strconv.Atoi(cell); rowErr == nil { // cast to row
return cr, false, true, nil
}
return cr, false, false, err
}
return cr, false, false, err
}
// prepareCellRange checking and convert cell reference to a cell range.
func (cr *cellRange) prepareCellRange(col, row bool, cellRef cellRef) error {
if col {
cellRef.Row = TotalRows
}
if row {
cellRef.Col = MaxColumns
}
if cellRef.Sheet == "" {
cellRef.Sheet = cr.From.Sheet
}
if cr.From.Sheet != cellRef.Sheet || cr.To.Sheet != cellRef.Sheet {
return errors.New("invalid reference")
}
if cr.From.Col > cellRef.Col {
cr.From.Col = cellRef.Col
}
if cr.From.Row > cellRef.Row {
cr.From.Row = cellRef.Row
}
if cr.To.Col < cellRef.Col {
cr.To.Col = cellRef.Col
}
if cr.To.Row < cellRef.Row {
cr.To.Row = cellRef.Row
}
return nil
}
// parseReference parse reference and extract values by given reference
// characters and default sheet name.
func (f *File) parseReference(ctx *calcContext, sheet, reference string) (arg formulaArg, err error) {
func (f *File) parseReference(ctx *calcContext, sheet, reference string) (formulaArg, error) {
reference = strings.ReplaceAll(reference, "$", "")
refs, cellRanges, cellRefs := list.New(), list.New(), list.New()
for _, ref := range strings.Split(reference, ":") {
tokens := strings.Split(ref, "!")
cr := cellRef{}
if len(tokens) == 2 { // have a worksheet name
cr.Sheet = tokens[0]
// cast to cell reference
if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[1]); err != nil {
// cast to column
if cr.Col, err = ColumnNameToNumber(tokens[1]); err != nil {
// cast to row
if cr.Row, err = strconv.Atoi(tokens[1]); err != nil {
err = newInvalidColumnNameError(tokens[1])
return
}
cr.Col = MaxColumns
ranges, cellRanges, cellRefs := strings.Split(reference, ":"), list.New(), list.New()
if len(ranges) > 1 {
var cr cellRange
for i, ref := range ranges {
cellRef, col, row, err := f.parseRef(ref)
if err != nil {
return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
}
if i == 0 {
if col {
cellRef.Row = 1
}
}
if refs.Len() > 0 {
e := refs.Back()
cellRefs.PushBack(e.Value.(cellRef))
refs.Remove(e)
}
refs.PushBack(cr)
continue
}
// cast to cell reference
if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[0]); err != nil {
// cast to column
if cr.Col, err = ColumnNameToNumber(tokens[0]); err != nil {
// cast to row
if cr.Row, err = strconv.Atoi(tokens[0]); err != nil {
err = newInvalidColumnNameError(tokens[0])
return
if row {
cellRef.Col = 1
}
cr.Col = MaxColumns
if cellRef.Sheet == "" {
cellRef.Sheet = sheet
}
cr.From, cr.To = cellRef, cellRef
continue
}
if err := cr.prepareCellRange(col, row, cellRef); err != nil {
return newErrorFormulaArg(formulaErrorNAME, err.Error()), err
}
cellRanges.PushBack(cellRange{
From: cellRef{Sheet: sheet, Col: cr.Col, Row: 1},
To: cellRef{Sheet: sheet, Col: cr.Col, Row: TotalRows},
})
cellRefs.Init()
arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
return
}
e := refs.Back()
if e == nil {
cr.Sheet = sheet
refs.PushBack(cr)
continue
}
cellRanges.PushBack(cellRange{
From: e.Value.(cellRef),
To: cr,
})
refs.Remove(e)
cellRanges.PushBack(cr)
return f.rangeResolver(ctx, cellRefs, cellRanges)
}
if refs.Len() > 0 {
e := refs.Back()
cellRefs.PushBack(e.Value.(cellRef))
refs.Remove(e)
cellRef, _, _, err := f.parseRef(reference)
if err != nil {
return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
}
arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
return
if cellRef.Sheet == "" {
cellRef.Sheet = sheet
}
cellRefs.PushBack(cellRef)
return f.rangeResolver(ctx, cellRefs, cellRanges)
}
// prepareValueRange prepare value range.
@ -1598,9 +1617,6 @@ func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List)
// prepare value range
for temp := cellRanges.Front(); temp != nil; temp = temp.Next() {
cr := temp.Value.(cellRange)
if cr.From.Sheet != cr.To.Sheet {
err = errors.New(formulaErrorVALUE)
}
rng := []int{cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row}
_ = sortCoordinates(rng)
cr.From.Col, cr.From.Row, cr.To.Col, cr.To.Row = rng[0], rng[1], rng[2], rng[3]
@ -14155,18 +14171,9 @@ func calcColumnsMinMax(argsList *list.List) (min, max int) {
if min == 0 {
min = cr.Value.(cellRange).From.Col
}
if min > cr.Value.(cellRange).From.Col {
min = cr.Value.(cellRange).From.Col
}
if min > cr.Value.(cellRange).To.Col {
min = cr.Value.(cellRange).To.Col
}
if max < cr.Value.(cellRange).To.Col {
max = cr.Value.(cellRange).To.Col
}
if max < cr.Value.(cellRange).From.Col {
max = cr.Value.(cellRange).From.Col
}
}
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
@ -14175,9 +14182,6 @@ func calcColumnsMinMax(argsList *list.List) (min, max int) {
if min == 0 {
min = refs.Value.(cellRef).Col
}
if min > refs.Value.(cellRef).Col {
min = refs.Value.(cellRef).Col
}
if max < refs.Value.(cellRef).Col {
max = refs.Value.(cellRef).Col
}
@ -14936,18 +14940,9 @@ func calcRowsMinMax(argsList *list.List) (min, max int) {
if min == 0 {
min = cr.Value.(cellRange).From.Row
}
if min > cr.Value.(cellRange).From.Row {
min = cr.Value.(cellRange).From.Row
}
if min > cr.Value.(cellRange).To.Row {
min = cr.Value.(cellRange).To.Row
}
if max < cr.Value.(cellRange).To.Row {
max = cr.Value.(cellRange).To.Row
}
if max < cr.Value.(cellRange).From.Row {
max = cr.Value.(cellRange).From.Row
}
}
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
@ -14956,9 +14951,6 @@ func calcRowsMinMax(argsList *list.List) (min, max int) {
if min == 0 {
min = refs.Value.(cellRef).Row
}
if min > refs.Value.(cellRef).Row {
min = refs.Value.(cellRef).Row
}
if max < refs.Value.(cellRef).Row {
max = refs.Value.(cellRef).Row
}

View File

@ -2409,7 +2409,7 @@ func TestCalcCellValue(t *testing.T) {
// ABS
"=ABS()": {"#VALUE!", "ABS requires 1 numeric argument"},
"=ABS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ABS(~)": {"", newInvalidColumnNameError("~").Error()},
"=ABS(~)": {"#NAME?", "invalid reference"},
// ACOS
"=ACOS()": {"#VALUE!", "ACOS requires 1 numeric argument"},
"=ACOS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
@ -3794,17 +3794,19 @@ func TestCalcCellValue(t *testing.T) {
"=CHOOSE(2,0)": {"#VALUE!", "index_num should be <= to the number of values"},
"=CHOOSE(1,NA())": {"#N/A", "#N/A"},
// COLUMN
"=COLUMN(1,2)": {"#VALUE!", "COLUMN requires at most 1 argument"},
"=COLUMN(\"\")": {"#VALUE!", "invalid reference"},
"=COLUMN(Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=COLUMN(Sheet1!A1!B1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=COLUMN(1,2)": {"#VALUE!", "COLUMN requires at most 1 argument"},
"=COLUMN(\"\")": {"#VALUE!", "invalid reference"},
"=COLUMN(Sheet1)": {"#NAME?", "invalid reference"},
"=COLUMN(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"=COLUMN(Sheet1!A1:Sheet2!A2)": {"#NAME?", "invalid reference"},
"=COLUMN(Sheet1!A1:1A)": {"#NAME?", "invalid reference"},
// COLUMNS
"=COLUMNS()": {"#VALUE!", "COLUMNS requires 1 argument"},
"=COLUMNS(1)": {"#VALUE!", "invalid reference"},
"=COLUMNS(\"\")": {"#VALUE!", "invalid reference"},
"=COLUMNS(Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=COLUMNS(Sheet1!A1!B1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=COLUMNS(Sheet1!Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=COLUMNS(Sheet1)": {"#NAME?", "invalid reference"},
"=COLUMNS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"=COLUMNS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"},
// FORMULATEXT
"=FORMULATEXT()": {"#VALUE!", "FORMULATEXT requires 1 argument"},
"=FORMULATEXT(1)": {"#VALUE!", "#VALUE!"},
@ -3874,15 +3876,15 @@ func TestCalcCellValue(t *testing.T) {
// ROW
"=ROW(1,2)": {"#VALUE!", "ROW requires at most 1 argument"},
"=ROW(\"\")": {"#VALUE!", "invalid reference"},
"=ROW(Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=ROW(Sheet1!A1!B1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=ROW(Sheet1)": {"#NAME?", "invalid reference"},
"=ROW(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
// ROWS
"=ROWS()": {"#VALUE!", "ROWS requires 1 argument"},
"=ROWS(1)": {"#VALUE!", "invalid reference"},
"=ROWS(\"\")": {"#VALUE!", "invalid reference"},
"=ROWS(Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=ROWS(Sheet1!A1!B1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=ROWS(Sheet1!Sheet1)": {"", newInvalidColumnNameError("Sheet1").Error()},
"=ROWS(Sheet1)": {"#NAME?", "invalid reference"},
"=ROWS(Sheet1!A1!B1)": {"#NAME?", "invalid reference"},
"=ROWS(Sheet1!Sheet1)": {"#NAME?", "invalid reference"},
// Web Functions
// ENCODEURL
"=ENCODEURL()": {"#VALUE!", "ENCODEURL requires 1 argument"},
@ -4376,6 +4378,7 @@ func TestCalcCellValue(t *testing.T) {
// SUM
"=A1/A3": "0.333333333333333",
"=SUM(A1:A2)": "3",
"=SUM(Sheet1!A1:Sheet1!A2)": "3",
"=SUM(Sheet1!A1,A2)": "3",
"=(-2-SUM(-4+A2))*5": "0",
"=SUM(Sheet1!A1:Sheet1!A1:A2,A2)": "5",
@ -5549,8 +5552,7 @@ func TestCalcSHEETS(t *testing.T) {
assert.NoError(t, err)
formulaList := map[string]string{
"=SHEETS(Sheet1!A1:B1)": "1",
"=SHEETS(Sheet1!A1:Sheet1!A1)": "1",
"=SHEETS(Sheet1!A1:Sheet2!A1)": "2",
"=SHEETS(Sheet1!A1:Sheet1!B1)": "1",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formula))
@ -5905,3 +5907,19 @@ func TestCalcCellResolver(t *testing.T) {
assert.Equal(t, expected, result, formula)
}
}
func TestEvalInfixExp(t *testing.T) {
f := NewFile()
arg, err := f.evalInfixExp(nil, "Sheet1", "A1", []efp.Token{
{TSubType: efp.TokenSubTypeRange, TValue: "1A"},
})
assert.Equal(t, arg, newEmptyFormulaArg())
assert.Equal(t, formulaErrorNAME, err.Error())
}
func TestParseToken(t *testing.T) {
f := NewFile()
assert.Equal(t, formulaErrorNAME, f.parseToken(nil, "Sheet1",
efp.Token{TSubType: efp.TokenSubTypeRange, TValue: "1A"}, nil, nil,
).Error())
}