@@ -26,6 +26,7 @@ import (
26
26
"sort"
27
27
"strconv"
28
28
"strings"
29
+ "sync"
29
30
"time"
30
31
"unicode"
31
32
"unsafe"
@@ -193,6 +194,13 @@ var (
193
194
}
194
195
)
195
196
197
+ // calcContext defines the formula execution context.
198
+ type calcContext struct {
199
+ sync.Mutex
200
+ entry string
201
+ iterations map[string]uint
202
+ }
203
+
196
204
// cellRef defines the structure of a cell reference.
197
205
type cellRef struct {
198
206
Col int
@@ -312,6 +320,7 @@ func (fa formulaArg) ToList() []formulaArg {
312
320
// formulaFuncs is the type of the formula functions.
313
321
type formulaFuncs struct {
314
322
f *File
323
+ ctx *calcContext
315
324
sheet, cell string
316
325
}
317
326
@@ -758,6 +767,13 @@ type formulaFuncs struct {
758
767
// ZTEST
759
768
//
760
769
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) {
761
777
var (
762
778
formula string
763
779
token formulaArg
@@ -770,7 +786,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
770
786
if tokens == nil {
771
787
return
772
788
}
773
- if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
789
+ if token, err = f.evalInfixExp(ctx, sheet, cell, tokens); err != nil {
774
790
return
775
791
}
776
792
result = token.Value()
@@ -850,7 +866,7 @@ func newEmptyFormulaArg() formulaArg {
850
866
//
851
867
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
852
868
//
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) {
854
870
var err error
855
871
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
856
872
var inArray, inArrayRow bool
@@ -860,7 +876,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
860
876
861
877
// out of function stack
862
878
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 {
864
880
return newEmptyFormulaArg(), err
865
881
}
866
882
}
@@ -896,7 +912,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
896
912
token.TValue = refTo
897
913
}
898
914
// parse reference: must reference at here
899
- result, err := f.parseReference(sheet, token.TValue)
915
+ result, err := f.parseReference(ctx, sheet, token.TValue)
900
916
if err != nil {
901
917
return result, err
902
918
}
@@ -912,7 +928,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
912
928
if refTo != "" {
913
929
token.TValue = refTo
914
930
}
915
- result, err := f.parseReference(sheet, token.TValue)
931
+ result, err := f.parseReference(ctx, sheet, token.TValue)
916
932
if err != nil {
917
933
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), err
918
934
}
@@ -938,7 +954,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
938
954
}
939
955
940
956
// 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 {
942
958
return newEmptyFormulaArg(), err
943
959
}
944
960
@@ -975,7 +991,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
975
991
arrayRow, inArray = []formulaArg{}, false
976
992
continue
977
993
}
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 {
979
995
return newEmptyFormulaArg(), err
980
996
}
981
997
}
@@ -994,13 +1010,13 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
994
1010
}
995
1011
996
1012
// 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 {
998
1014
if !isFunctionStopToken(token) {
999
1015
return nil
1000
1016
}
1001
1017
prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack)
1002
1018
// 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(
1004
1020
"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
1005
1021
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
1006
1022
if arg.Type == ArgError && opfStack.Len() == 1 {
@@ -1337,14 +1353,14 @@ func tokenToFormulaArg(token efp.Token) formulaArg {
1337
1353
1338
1354
// parseToken parse basic arithmetic operator priority and evaluate based on
1339
1355
// 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 {
1341
1357
// parse reference: must reference at here
1342
1358
if token.TSubType == efp.TokenSubTypeRange {
1343
1359
refTo := f.getDefinedNameRefTo(token.TValue, sheet)
1344
1360
if refTo != "" {
1345
1361
token.TValue = refTo
1346
1362
}
1347
- result, err := f.parseReference(sheet, token.TValue)
1363
+ result, err := f.parseReference(ctx, sheet, token.TValue)
1348
1364
if err != nil {
1349
1365
return errors.New(formulaErrorNAME)
1350
1366
}
@@ -1386,7 +1402,7 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
1386
1402
1387
1403
// parseReference parse reference and extract values by given reference
1388
1404
// 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) {
1390
1406
reference = strings.ReplaceAll(reference, "$", "")
1391
1407
refs, cellRanges, cellRefs := list.New(), list.New(), list.New()
1392
1408
for _, ref := range strings.Split(reference, ":") {
@@ -1430,7 +1446,7 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
1430
1446
To: cellRef{Sheet: sheet, Col: cr.Col, Row: TotalRows},
1431
1447
})
1432
1448
cellRefs.Init()
1433
- arg, err = f.rangeResolver(cellRefs, cellRanges)
1449
+ arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
1434
1450
return
1435
1451
}
1436
1452
e := refs.Back()
@@ -1450,7 +1466,7 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
1450
1466
cellRefs.PushBack(e.Value.(cellRef))
1451
1467
refs.Remove(e)
1452
1468
}
1453
- arg, err = f.rangeResolver(cellRefs, cellRanges)
1469
+ arg, err = f.rangeResolver(ctx, cellRefs, cellRanges)
1454
1470
return
1455
1471
}
1456
1472
@@ -1486,10 +1502,27 @@ func prepareValueRef(cr cellRef, valueRange []int) {
1486
1502
}
1487
1503
}
1488
1504
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
+
1489
1522
// rangeResolver extract value as string from given reference and range list.
1490
1523
// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
1491
1524
// 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) {
1493
1526
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
1494
1527
// value range order: from row, to row, from column, to column
1495
1528
valueRange := []int{0, 0, 0, 0}
@@ -1525,7 +1558,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
1525
1558
if cell, err = CoordinatesToCellName(col, row); err != nil {
1526
1559
return
1527
1560
}
1528
- if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true} ); err != nil {
1561
+ if value, err = f.cellResolver(ctx, sheet, cell ); err != nil {
1529
1562
return
1530
1563
}
1531
1564
matrixRow = append(matrixRow, formulaArg{
@@ -1544,7 +1577,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
1544
1577
if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
1545
1578
return
1546
1579
}
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 {
1548
1581
return
1549
1582
}
1550
1583
arg.Type = ArgString
@@ -15092,7 +15125,7 @@ func (fn *formulaFuncs) INDIRECT(argsList *list.List) formulaArg {
15092
15125
}
15093
15126
return newStringFormulaArg(value)
15094
15127
}
15095
- arg, _ := fn.f.parseReference(fn.sheet, fromRef+":"+toRef)
15128
+ arg, _ := fn.f.parseReference(fn.ctx, fn. sheet, fromRef+":"+toRef)
15096
15129
return arg
15097
15130
}
15098
15131
0 commit comments