forked from p30928647/excelize
This fixed across worksheet reference issue for the formula calculation engine
This commit is contained in:
parent
1088302331
commit
ef3e81de8e
178
calc.go
178
calc.go
|
@ -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
|
||||
}
|
||||
|
|
48
calc_test.go
48
calc_test.go
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue