mirror of
https://github.com/unidoc/unioffice.git
synced 2025-05-08 19:29:25 +08:00
formula: add support for referencing other sheets
This adds support references like 'Sheet 1'! so that formulas can pull values from other sheets.
This commit is contained in:
parent
e0786dae4a
commit
fb43078e87
@ -7,7 +7,9 @@
|
||||
|
||||
package spreadsheet
|
||||
|
||||
import "baliance.com/gooxml/spreadsheet/formula"
|
||||
import (
|
||||
"baliance.com/gooxml/spreadsheet/formula"
|
||||
)
|
||||
|
||||
func newEvalContext(s *Sheet) *evalContext {
|
||||
return &evalContext{s, make(map[string]struct{})}
|
||||
@ -46,3 +48,12 @@ func (e *evalContext) Cell(ref string, ev formula.Evaluator) formula.Result {
|
||||
// TODO: handle this properly
|
||||
// return formula.MakeErrorResult()
|
||||
}
|
||||
|
||||
func (e *evalContext) Sheet(name string) formula.Context {
|
||||
for _, sheet := range e.s.w.Sheets() {
|
||||
if sheet.Name() == name {
|
||||
return sheet.FormulaContext()
|
||||
}
|
||||
}
|
||||
return formula.InvalidReferenceContext
|
||||
}
|
||||
|
@ -12,4 +12,8 @@ package formula
|
||||
type Context interface {
|
||||
// Cell returns the result of evaluating a cell.
|
||||
Cell(ref string, ev Evaluator) Result
|
||||
|
||||
// Sheet returns an evaluation context for a given sheet name. This is used
|
||||
// when evaluating cells that pull data from other sheets (e.g. ='Sheet 2'!A1)
|
||||
Sheet(name string) Context
|
||||
}
|
||||
|
@ -94,74 +94,76 @@ var yyExca = [...]int{
|
||||
|
||||
const yyPrivate = 57344
|
||||
|
||||
const yyLast = 145
|
||||
const yyLast = 149
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
3, 24, 25, 26, 59, 34, 26, 36, 37, 17,
|
||||
26, 39, 33, 38, 35, 33, 21, 60, 41, 19,
|
||||
42, 43, 62, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, 54, 55, 61, 56, 11, 22, 23,
|
||||
24, 25, 26, 31, 27, 28, 29, 30, 32, 58,
|
||||
9, 33, 1, 18, 10, 2, 8, 0, 0, 0,
|
||||
0, 63, 57, 22, 23, 24, 25, 26, 31, 27,
|
||||
28, 29, 30, 32, 0, 0, 33, 22, 23, 24,
|
||||
25, 26, 31, 27, 28, 29, 30, 32, 0, 0,
|
||||
33, 22, 23, 24, 25, 26, 0, 0, 0, 13,
|
||||
14, 15, 16, 0, 33, 21, 20, 5, 0, 12,
|
||||
0, 6, 7, 0, 0, 0, 4, 13, 14, 15,
|
||||
16, 0, 0, 21, 20, 0, 0, 12, 40, 6,
|
||||
7, 13, 14, 15, 16, 0, 0, 21, 20, 0,
|
||||
0, 12, 0, 6, 7,
|
||||
3, 26, 27, 28, 62, 36, 28, 38, 39, 41,
|
||||
22, 37, 35, 40, 28, 35, 44, 63, 18, 20,
|
||||
17, 45, 46, 65, 11, 47, 48, 49, 50, 51,
|
||||
52, 53, 54, 55, 56, 57, 58, 64, 59, 42,
|
||||
24, 25, 26, 27, 28, 33, 29, 30, 31, 32,
|
||||
34, 9, 1, 35, 19, 10, 2, 8, 0, 0,
|
||||
0, 0, 61, 0, 66, 60, 24, 25, 26, 27,
|
||||
28, 33, 29, 30, 31, 32, 34, 0, 0, 35,
|
||||
24, 25, 26, 27, 28, 33, 29, 30, 31, 32,
|
||||
34, 0, 0, 35, 24, 25, 26, 27, 28, 0,
|
||||
0, 0, 0, 13, 14, 15, 16, 35, 23, 22,
|
||||
21, 5, 0, 12, 0, 6, 7, 0, 0, 0,
|
||||
4, 13, 14, 15, 16, 0, 23, 22, 21, 0,
|
||||
0, 12, 43, 6, 7, 13, 14, 15, 16, 0,
|
||||
23, 22, 21, 0, 0, 12, 0, 6, 7,
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
92, -1000, -1000, 58, 124, -10, 124, 124, -1000, -1000,
|
||||
-1000, -1000, 124, -1000, -1000, -1000, -1000, -19, -1000, -1000,
|
||||
110, -1000, 124, 124, 124, 124, 124, 124, 124, 124,
|
||||
124, 124, 124, 124, 58, 124, -20, -20, 44, 3,
|
||||
-1000, -14, -1000, 58, -20, -20, -17, -17, -1000, 72,
|
||||
72, 72, 72, 72, 72, -13, 19, -1000, -1000, -1000,
|
||||
124, -1000, -1000, 58,
|
||||
96, -1000, -1000, 61, 128, -13, 128, 128, -1000, -1000,
|
||||
-1000, -1000, 128, -1000, -1000, -1000, -1000, -21, -3, -1000,
|
||||
-1000, 114, -1000, -1000, 128, 128, 128, 128, 128, 128,
|
||||
128, 128, 128, 128, 128, 128, 61, 128, -20, -20,
|
||||
47, -3, -1000, -1000, -14, -1000, 61, -20, -20, -17,
|
||||
-17, -1000, 75, 75, 75, 75, 75, 75, -9, 21,
|
||||
-1000, -1000, -1000, 128, -1000, -1000, 61,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 0, 56, 55, 54, 9, 53, 52, 50, 37,
|
||||
22, 20, 19, 18,
|
||||
0, 0, 57, 56, 55, 20, 54, 52, 51, 24,
|
||||
23, 21, 19, 18, 16,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 7, 3, 3, 3, 8, 8, 8, 8, 1,
|
||||
1, 1, 2, 2, 2, 2, 4, 4, 5, 6,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 9, 9, 9, 13, 13, 11, 10, 10,
|
||||
1, 1, 2, 2, 2, 2, 4, 4, 4, 13,
|
||||
5, 6, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 9, 9, 9, 14, 14, 11,
|
||||
10, 10,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 1, 2, 4, 1, 1, 1, 1, 2,
|
||||
2, 1, 1, 1, 1, 3, 1, 1, 1, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 1, 2, 3, 1, 3, 1, 1, 0,
|
||||
2, 1, 1, 1, 1, 3, 1, 2, 1, 1,
|
||||
1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 1, 2, 3, 1, 3, 1,
|
||||
1, 0,
|
||||
}
|
||||
var yyChk = [...]int{
|
||||
|
||||
-1000, -7, -3, -1, 24, 15, 19, 20, -2, -8,
|
||||
-4, -9, 17, 7, 8, 9, 10, -5, -6, -12,
|
||||
14, 13, 19, 20, 21, 22, 23, 25, 26, 27,
|
||||
28, 24, 29, 32, -1, 24, -1, -1, -1, 30,
|
||||
18, -13, -11, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, 18, -5, 18,
|
||||
31, 16, -10, -1,
|
||||
-4, -9, 17, 7, 8, 9, 10, -5, -13, -6,
|
||||
-12, 14, 13, 12, 19, 20, 21, 22, 23, 25,
|
||||
26, 27, 28, 24, 29, 32, -1, 24, -1, -1,
|
||||
-1, 30, -5, 18, -14, -11, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
18, -5, 18, 31, 16, -10, -1,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
0, -2, 1, 2, 0, 0, 0, 0, 11, 12,
|
||||
13, 14, 0, 5, 6, 7, 8, 16, 17, 32,
|
||||
0, 18, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 3, 0, 9, 10, 0, 0,
|
||||
33, 0, 35, 37, 20, 21, 22, 23, 24, 25,
|
||||
26, 27, 28, 29, 30, 31, 0, 15, 19, 34,
|
||||
39, 4, 36, 38,
|
||||
13, 14, 0, 5, 6, 7, 8, 16, 0, 18,
|
||||
34, 0, 20, 19, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 3, 0, 9, 10,
|
||||
0, 0, 17, 35, 0, 37, 39, 22, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32, 33, 0,
|
||||
15, 21, 36, 41, 4, 38, 40,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
@ -562,97 +564,107 @@ yydefault:
|
||||
{
|
||||
yyVAL.expr = yyDollar[2].expr
|
||||
}
|
||||
case 18:
|
||||
case 17:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewPrefixExpr(yyDollar[1].expr, yyDollar[2].expr)
|
||||
}
|
||||
case 19:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewSheetPrefixExpr(yyDollar[1].node.val)
|
||||
}
|
||||
case 20:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewCellRef(yyDollar[1].node.val)
|
||||
}
|
||||
case 19:
|
||||
case 21:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewRange(yyDollar[1].expr, yyDollar[3].expr)
|
||||
}
|
||||
case 20:
|
||||
case 22:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypePlus, yyDollar[3].expr)
|
||||
}
|
||||
case 21:
|
||||
case 23:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMinus, yyDollar[3].expr)
|
||||
}
|
||||
case 22:
|
||||
case 24:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMult, yyDollar[3].expr)
|
||||
}
|
||||
case 23:
|
||||
case 25:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeDiv, yyDollar[3].expr)
|
||||
}
|
||||
case 24:
|
||||
case 26:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeExp, yyDollar[3].expr)
|
||||
}
|
||||
case 25:
|
||||
case 27:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLT, yyDollar[3].expr)
|
||||
}
|
||||
case 26:
|
||||
case 28:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGT, yyDollar[3].expr)
|
||||
}
|
||||
case 27:
|
||||
case 29:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 28:
|
||||
case 30:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 29:
|
||||
case 31:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 30:
|
||||
case 32:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeNE, yyDollar[3].expr)
|
||||
}
|
||||
case 31:
|
||||
case 33:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeConcat, yyDollar[3].expr)
|
||||
}
|
||||
case 33:
|
||||
case 35:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, nil)
|
||||
}
|
||||
case 34:
|
||||
case 36:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, yyDollar[2].args)
|
||||
}
|
||||
case 35:
|
||||
case 37:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyVAL.args, yyDollar[1].expr)
|
||||
}
|
||||
case 36:
|
||||
case 38:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyDollar[1].args, yyDollar[3].expr)
|
||||
}
|
||||
case 39:
|
||||
case 41:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewEmptyExpr()
|
||||
|
@ -18,12 +18,12 @@ package formula
|
||||
|
||||
%type <expr> formula formula1 initial reference referenceItem refFunctionCall
|
||||
%type <expr> start constant functionCall argument argument1
|
||||
%type <expr> binOp
|
||||
%type <args> arguments
|
||||
%type <expr> binOp prefix
|
||||
%type <args> arguments
|
||||
|
||||
%token <expr> tokenHorizontalRange tokenReservedName tokenDDECall
|
||||
%token <expr> tokenHorizontalRange tokenReservedName tokenDDECall
|
||||
%token <node> tokenBool tokenNumber tokenString tokenError tokenErrorRef tokenSheet tokenCell
|
||||
%token <node> tokenFunctionBultin
|
||||
%token <node> tokenFunctionBultin
|
||||
|
||||
%token tokenLBrace tokenRBrace tokenLParen tokenRParen
|
||||
%token tokenPlus tokenMinus tokenMult tokenDiv tokenExp tokenEQ tokenLT tokenGT tokenLEQ tokenGEQ tokenNE
|
||||
@ -65,11 +65,12 @@ formula1:
|
||||
|
||||
reference:
|
||||
referenceItem
|
||||
| prefix referenceItem { $$ = NewPrefixExpr($1,$2)}
|
||||
| refFunctionCall;
|
||||
|
||||
referenceItem:
|
||||
tokenCell { $$ = NewCellRef($1.val)}
|
||||
;
|
||||
prefix: tokenSheet { $$ = NewSheetPrefixExpr($1.val) };
|
||||
|
||||
referenceItem: tokenCell { $$ = NewCellRef($1.val)} ;
|
||||
|
||||
refFunctionCall:
|
||||
referenceItem tokenColon referenceItem { $$ = NewRange($1,$3) };
|
||||
|
28
spreadsheet/formula/invalidreferencecontext.go
Normal file
28
spreadsheet/formula/invalidreferencecontext.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017 Baliance. 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 by contacting sales@baliance.com.
|
||||
|
||||
package formula
|
||||
|
||||
// InvalidReferenceContext is a Context that can be used when evaluating an
|
||||
// invalid reference (e.g. referencing a non-existent sheet). It implements
|
||||
// Context safely, but returns error results.
|
||||
var InvalidReferenceContext = &ivr{}
|
||||
|
||||
type ivr struct {
|
||||
}
|
||||
|
||||
func (i *ivr) Cell(ref string, ev Evaluator) Result {
|
||||
return MakeErrorResult("invalid reference")
|
||||
}
|
||||
|
||||
func (i *ivr) Sheet(name string) Context {
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *ivr) Reference(ref string, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -37,11 +37,12 @@ import (
|
||||
builtinFunction = [A-Z] [A-Z0-9.]+ '(';
|
||||
excelFn = '_xlfn.' [A-Z_] [A-Z0-9.]+ '(';
|
||||
|
||||
sheetChar = ^['%\[\]\\:/?();{}#"=<>&+\-*/^%,_];
|
||||
sheetChar = ^['%\[\]\\:/?();{}#"=<>&+\-*/^%,_!];
|
||||
enclosedSheetChar = ^['*\[\]\\:\/?];
|
||||
|
||||
number = [0-9]+ '.'? [0-9]* ('e' [0-9]+)?;
|
||||
sheet = (sheetChar+ | squote (enclosedSheetChar | dquote)+ squote '!');
|
||||
sheet = sheetChar+ '!';
|
||||
quotedSheet = (sheetChar+ | squote (enclosedSheetChar | dquote)+ squote) '!';
|
||||
|
||||
namedRange = [A-Z_\\][A-Z0-9\\_.];
|
||||
|
||||
@ -58,7 +59,8 @@ import (
|
||||
errorLiteral => {l.emit(tokenError, data[ts:te])};
|
||||
errorRef => {l.emit(tokenErrorRef, data[ts:te])};
|
||||
horizontalRange => {l.emit(tokenHorizontalRange, data[ts:te])};
|
||||
sheet => {l.emit(tokenSheet, data[ts:te])};
|
||||
sheet => {l.emit(tokenSheet, data[ts:te-1])}; # chop '!'
|
||||
quotedSheet => {l.emit(tokenSheet, data[ts+1:te-2])}; # chop leading quote and trailing quote & !
|
||||
reservedName => {l.emit(tokenReservedName, data[ts:te])};
|
||||
|
||||
|
||||
|
34
spreadsheet/formula/prefixexpr.go
Normal file
34
spreadsheet/formula/prefixexpr.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2017 Baliance. 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 by contacting sales@baliance.com.
|
||||
|
||||
package formula
|
||||
|
||||
import "fmt"
|
||||
|
||||
type PrefixExpr struct {
|
||||
pfx Expression
|
||||
exp Expression
|
||||
}
|
||||
|
||||
func NewPrefixExpr(pfx, exp Expression) Expression {
|
||||
return &PrefixExpr{pfx, exp}
|
||||
}
|
||||
|
||||
func (p PrefixExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
ref := p.pfx.Reference(ctx, ev)
|
||||
switch ref.Type {
|
||||
case ReferenceTypeSheet:
|
||||
sheetCtx := ctx.Sheet(ref.Value)
|
||||
return p.exp.Eval(sheetCtx, ev)
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("no support for reference type %s", ref.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (p PrefixExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
@ -15,6 +15,7 @@ const (
|
||||
ReferenceTypeUnknown ReferenceType = iota
|
||||
ReferenceTypeInvalid
|
||||
ReferenceTypeCell
|
||||
ReferenceTypeSheet
|
||||
)
|
||||
|
||||
type Reference struct {
|
||||
|
@ -4,9 +4,9 @@ package formula
|
||||
|
||||
import "fmt"
|
||||
|
||||
const _ReferenceType_name = "ReferenceTypeUnknownReferenceTypeInvalidReferenceTypeCell"
|
||||
const _ReferenceType_name = "ReferenceTypeUnknownReferenceTypeInvalidReferenceTypeCellReferenceTypeSheet"
|
||||
|
||||
var _ReferenceType_index = [...]uint8{0, 20, 40, 57}
|
||||
var _ReferenceType_index = [...]uint8{0, 20, 40, 57, 75}
|
||||
|
||||
func (i ReferenceType) String() string {
|
||||
if i >= ReferenceType(len(_ReferenceType_index)-1) {
|
||||
|
24
spreadsheet/formula/sheetprefixexpr.go
Normal file
24
spreadsheet/formula/sheetprefixexpr.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2017 Baliance. 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 by contacting sales@baliance.com.
|
||||
|
||||
package formula
|
||||
|
||||
type SheetPrefixExpr struct {
|
||||
sheet string
|
||||
}
|
||||
|
||||
func NewSheetPrefixExpr(s string) Expression {
|
||||
return &SheetPrefixExpr{s}
|
||||
}
|
||||
|
||||
func (s SheetPrefixExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult("sheet prefix should never be evaluated")
|
||||
}
|
||||
|
||||
func (s SheetPrefixExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeSheet, Value: s.sheet}
|
||||
}
|
BIN
spreadsheet/formula/testdata/formulareference.xlsx
vendored
BIN
spreadsheet/formula/testdata/formulareference.xlsx
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user