• R/O
  • HTTP
  • SSH
  • HTTPS

excelize: 提交

Go language library for reading and writing Microsoft Excel™ (XLAM / XLSM / XLSX / XLTM / XLTX) spreadsheets


Commit MetaInfo

修订版a65c5846e45fece382f72465f9e858c788dfcfef (tree)
时间2022-07-10 19:14:48
作者xuri <xuri.me@gmai...>
Commiterxuri

Log Message

This closes #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

更改概述

差异

--- a/calc.go
+++ b/calc.go
@@ -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
--- a/calc_test.go
+++ b/calc_test.go
@@ -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},
--- a/excelize.go
+++ b/excelize.go
@@ -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
Show on old repository browser