Skip to content

Commit a65c584

Browse files
committed
This closes qax-os#1262, support for dependence formulas calculation
- Add export option `MaxCalcIterations` for specifies the maximum iterations for iterative calculation - Update unit test for the database formula functions
1 parent 1dbed64 commit a65c584

File tree

3 files changed

+57
-20
lines changed

3 files changed

+57
-20
lines changed

calc.go

+51-18
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"sort"
2727
"strconv"
2828
"strings"
29+
"sync"
2930
"time"
3031
"unicode"
3132
"unsafe"
@@ -193,6 +194,13 @@ var (
193194
}
194195
)
195196

197+
// calcContext defines the formula execution context.
198+
type calcContext struct {
199+
sync.Mutex
200+
entry string
201+
iterations map[string]uint
202+
}
203+
196204
// cellRef defines the structure of a cell reference.
197205
type cellRef struct {
198206
Col int
@@ -312,6 +320,7 @@ func (fa formulaArg) ToList() []formulaArg {
312320
// formulaFuncs is the type of the formula functions.
313321
type formulaFuncs struct {
314322
f *File
323+
ctx *calcContext
315324
sheet, cell string
316325
}
317326

@@ -758,6 +767,13 @@ type formulaFuncs struct {
758767
// ZTEST
759768
//
760769
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
770+
return f.calcCellValue(&calcContext{
771+
entry: fmt.Sprintf("%s!%s", sheet, cell),
772+
iterations: make(map[string]uint),
773+
}, sheet, cell)
774+
}
775+
776+
func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result string, err error) {
761777
var (
762778
formula string
763779
token formulaArg
@@ -770,7 +786,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
770786
if tokens == nil {
771787
return
772788
}
773-
if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
789+
if token, err = f.evalInfixExp(ctx, sheet, cell, tokens); err != nil {
774790
return
775791
}
776792
result = token.Value()
@@ -850,7 +866,7 @@ func newEmptyFormulaArg() formulaArg {
850866
//
851867
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
852868
//
853-
func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) {
869+
func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.Token) (formulaArg, error) {
854870
var err error
855871
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
856872
var inArray, inArrayRow bool
@@ -860,7 +876,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
860876

861877
// out of function stack
862878
if opfStack.Len() == 0 {
863-
if err = f.parseToken(sheet, token, opdStack, optStack); err != nil {
879+
if err = f.parseToken(ctx, sheet, token, opdStack, optStack); err != nil {
864880
return newEmptyFormulaArg(), err
865881
}
866882
}
@@ -896,7 +912,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
896912
token.TValue = refTo
897913
}
898914
// parse reference: must reference at here
899-
result, err := f.parseReference(sheet, token.TValue)
915+
result, err := f.parseReference(ctx, sheet, token.TValue)
900916
if err != nil {
901917
return result, err
902918
}
@@ -912,7 +928,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
912928
if refTo != "" {
913929
token.TValue = refTo
914930
}
915-
result, err := f.parseReference(sheet, token.TValue)
931+
result, err := f.parseReference(ctx, sheet, token.TValue)
916932
if err != nil {
917933
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), err
918934
}
@@ -938,7 +954,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
938954
}
939955

940956
// check current token is opft
941-
if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil {
957+
if err = f.parseToken(ctx, sheet, token, opfdStack, opftStack); err != nil {
942958
return newEmptyFormulaArg(), err
943959
}
944960

@@ -975,7 +991,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
975991
arrayRow, inArray = []formulaArg{}, false
976992
continue
977993
}
978-
if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
994+
if err = f.evalInfixExpFunc(ctx, sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
979995
return newEmptyFormulaArg(), err
980996
}
981997
}
@@ -994,13 +1010,13 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
9941010
}
9951011

9961012
// evalInfixExpFunc evaluate formula function in the infix expression.
997-
func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
1013+
func (f *File) evalInfixExpFunc(ctx *calcContext, sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
9981014
if !isFunctionStopToken(token) {
9991015
return nil
10001016
}
10011017
prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack)
10021018
// call formula function to evaluate
1003-
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
1019+
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell, ctx: ctx}, strings.NewReplacer(
10041020
"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
10051021
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
10061022
if arg.Type == ArgError && opfStack.Len() == 1 {
@@ -1337,14 +1353,14 @@ func tokenToFormulaArg(token efp.Token) formulaArg {
13371353

13381354
// parseToken parse basic arithmetic operator priority and evaluate based on
13391355
// operators and operands.
1340-
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error {
1356+
func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdStack, optStack *Stack) error {
13411357
// parse reference: must reference at here
13421358
if token.TSubType == efp.TokenSubTypeRange {
13431359
refTo := f.getDefinedNameRefTo(token.TValue, sheet)
13441360
if refTo != "" {
13451361
token.TValue = refTo
13461362
}
1347-
result, err := f.parseReference(sheet, token.TValue)
1363+
result, err := f.parseReference(ctx, sheet, token.TValue)
13481364
if err != nil {
13491365
return errors.New(formulaErrorNAME)
13501366
}
@@ -1386,7 +1402,7 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
13861402

13871403
// parseReference parse reference and extract values by given reference
13881404
// characters and default sheet name.
1389-
func (f *File) parseReference(sheet, reference string) (arg formulaArg, err error) {
1405+
func (f *File) parseReference(ctx *calcContext, sheet, reference string) (arg formulaArg, err error) {
13901406
reference = strings.ReplaceAll(reference, "$", "")
13911407
refs, cellRanges, cellRefs := list.New(), list.New(), list.New()
13921408
for _, ref := range strings.Split(reference, ":") {
@@ -1430,7 +1446,7 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
14301446
To: cellRef{Sheet: sheet, Col: cr.Col, Row: TotalRows},
14311447
})
14321448
cellRefs.Init()
1433-
arg, err = f.rangeResolver(cellRefs, cellRanges)
1449+
arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
14341450
return
14351451
}
14361452
e := refs.Back()
@@ -1450,7 +1466,7 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
14501466
cellRefs.PushBack(e.Value.(cellRef))
14511467
refs.Remove(e)
14521468
}
1453-
arg, err = f.rangeResolver(cellRefs, cellRanges)
1469+
arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
14541470
return
14551471
}
14561472

@@ -1486,10 +1502,27 @@ func prepareValueRef(cr cellRef, valueRange []int) {
14861502
}
14871503
}
14881504

1505+
// cellResolver calc cell value by given worksheet name, cell reference and context.
1506+
func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (string, error) {
1507+
var value string
1508+
ref := fmt.Sprintf("%s!%s", sheet, cell)
1509+
if formula, _ := f.GetCellFormula(sheet, cell); len(formula) != 0 {
1510+
ctx.Lock()
1511+
if ctx.entry != ref && ctx.iterations[ref] <= f.options.MaxCalcIterations {
1512+
ctx.iterations[ref]++
1513+
ctx.Unlock()
1514+
value, _ = f.calcCellValue(ctx, sheet, cell)
1515+
return value, nil
1516+
}
1517+
ctx.Unlock()
1518+
}
1519+
return f.GetCellValue(sheet, cell, Options{RawCellValue: true})
1520+
}
1521+
14891522
// rangeResolver extract value as string from given reference and range list.
14901523
// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
14911524
// be reference A1:B3.
1492-
func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, err error) {
1525+
func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List) (arg formulaArg, err error) {
14931526
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
14941527
// value range order: from row, to row, from column, to column
14951528
valueRange := []int{0, 0, 0, 0}
@@ -1525,7 +1558,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
15251558
if cell, err = CoordinatesToCellName(col, row); err != nil {
15261559
return
15271560
}
1528-
if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil {
1561+
if value, err = f.cellResolver(ctx, sheet, cell); err != nil {
15291562
return
15301563
}
15311564
matrixRow = append(matrixRow, formulaArg{
@@ -1544,7 +1577,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
15441577
if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
15451578
return
15461579
}
1547-
if arg.String, err = f.GetCellValue(cr.Sheet, cell, Options{RawCellValue: true}); err != nil {
1580+
if arg.String, err = f.cellResolver(ctx, cr.Sheet, cell); err != nil {
15481581
return
15491582
}
15501583
arg.Type = ArgString
@@ -15092,7 +15125,7 @@ func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg {
1509215125
}
1509315126
return newStringFormulaArg(value)
1509415127
}
15095-
arg, _ := fn.f.parseReference(fn.sheet, fromRef+":"+toRef)
15128+
arg, _ := fn.f.parseReference(fn.ctx, fn.sheet, fromRef+":"+toRef)
1509615129
return arg
1509715130
}
1509815131

calc_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -4606,8 +4606,8 @@ func TestCalcCOVAR(t *testing.T) {
46064606
func TestCalcDatabase(t *testing.T) {
46074607
cellData := [][]interface{}{
46084608
{"Tree", "Height", "Age", "Yield", "Profit", "Height"},
4609-
{"=Apple", ">1000%", nil, nil, nil, "<16"},
4610-
{"=Pear"},
4609+
{nil, ">1000%", nil, nil, nil, "<16"},
4610+
{},
46114611
{"Tree", "Height", "Age", "Yield", "Profit"},
46124612
{"Apple", 18, 20, 14, 105},
46134613
{"Pear", 12, 12, 10, 96},

excelize.go

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
6363

6464
// Options define the options for open and reading spreadsheet.
6565
//
66+
// MaxCalcIterations specifies the maximum iterations for iterative
67+
// calculation, the default value is 0.
68+
//
6669
// Password specifies the password of the spreadsheet in plain text.
6770
//
6871
// RawCellValue specifies if apply the number format for the cell value or get
@@ -78,6 +81,7 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
7881
// should be less than or equal to UnzipSizeLimit, the default value is
7982
// 16MB.
8083
type Options struct {
84+
MaxCalcIterations uint
8185
Password string
8286
RawCellValue bool
8387
UnzipSizeLimit int64

0 commit comments

Comments
 (0)