mirror of
https://github.com/unidoc/unioffice.git
synced 2025-05-02 22:17:07 +08:00

* MATCH, IFS, MAXA, MINA * OFFSET fixed * ISBLANK, ISERR, ISERROR, ISEVEN ,ISFORMULA, ISNONTEXT, ISNUMBER, ISODD, ISTEXT * ISLEAPYEAR, ISLOGICAL, ISNA, ISREF * FIND, FINDB * SEARCH, SEARCHB * CONCAT, CONCATENATE * YEAR, YEARFRAC * CONCAT is fixed, now TRUE and FALSE are concatenated instead of 1 and 0 in case of boolean results * NOW, TODAY, TIME, TIMEVALUE * DATE * DATEDIF
89 lines
2.3 KiB
Go
89 lines
2.3 KiB
Go
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by the terms of the Affero GNU General
|
|
// Public License version 3.0 as published by the Free Software Foundation and
|
|
// appearing in the file LICENSE included in the packaging of this file. A
|
|
// commercial license can be purchased on https://unidoc.io.
|
|
|
|
package formula
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Evaluator is the interface for a formula evaluator. This is needed so we can
|
|
// pass it to the spreadsheet to let it evaluate formula cells before returning
|
|
// the results.
|
|
type Evaluator interface {
|
|
Eval(ctx Context, formula string) Result
|
|
}
|
|
|
|
func NewEvaluator() Evaluator {
|
|
return &defEval{}
|
|
}
|
|
|
|
type defEval struct {
|
|
isRef bool
|
|
booleans []bool
|
|
}
|
|
|
|
func (d *defEval) Eval(ctx Context, formula string) Result {
|
|
expr := ParseString(formula)
|
|
if expr != nil {
|
|
d.addInfo(ctx, expr)
|
|
return expr.Eval(ctx, d)
|
|
}
|
|
return MakeErrorResult(fmt.Sprintf("unable to parse formula %s", formula))
|
|
}
|
|
|
|
// addInfo adds information which is needed for some functions but is lost after evaluation. E.g. which zeroes and ones were actually booleans before evaluation and which arguments are actually references.
|
|
func (d *defEval) addInfo(ctx Context, expr Expression) {
|
|
switch expr.(type) {
|
|
case FunctionCall:
|
|
switch expr.(FunctionCall).name {
|
|
case "ISREF":
|
|
for _, arg := range expr.(FunctionCall).args {
|
|
switch arg.(type) {
|
|
case CellRef:
|
|
d.isRef = validateRef(arg.(CellRef))
|
|
return
|
|
case Range:
|
|
switch arg.(Range).from.(type) {
|
|
case CellRef:
|
|
d.isRef = validateRef(arg.(Range).from.(CellRef))
|
|
return
|
|
}
|
|
switch arg.(Range).to.(type) {
|
|
case CellRef:
|
|
d.isRef = validateRef(arg.(Range).to.(CellRef))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
case "CONCAT", "_xlfn.CONCAT", "CONCATENATE":
|
|
d.booleans = []bool{}
|
|
for _, arg := range expr.(FunctionCall).args {
|
|
switch arg.(type) {
|
|
case CellRef:
|
|
cr := arg.(CellRef).s
|
|
d.booleans = append(d.booleans, ctx.IsBool(cr))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var refRegexp *regexp.Regexp = regexp.MustCompile(`^([a-z]+)([0-9]+)$`)
|
|
|
|
func validateRef(cr CellRef) bool {
|
|
if submatch := refRegexp.FindStringSubmatch(strings.ToLower(cr.s)); len(submatch) > 2 {
|
|
col := submatch[1]
|
|
row, _ := strconv.Atoi(submatch[2])
|
|
return row <= 1048576 && col <= "zz"
|
|
}
|
|
return false
|
|
}
|