diff --git a/calc.go b/calc.go index 96abd64c..402c7dee 100644 --- a/calc.go +++ b/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 } diff --git a/calc_test.go b/calc_test.go index 1cb1580d..b9c9a8d8 100644 --- a/calc_test.go +++ b/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()) +}