mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
Spreadsheet formulas: Flattening files (#366)
* evaluation errors fixed * allow underscores in sheet names * allow wildcard in *LOOKUP functions * horizontal and vertical ranges * horizontal and vertical ranges for sheet references * remove redundant recalculate * caching * fixed named ranges, made Evaluator more generic * Memory usage shown in flatten example * temporary file deleted * ParseCellReference duplicate removed * ISREF is fixed
This commit is contained in:
parent
2cfb3539c7
commit
a5ef37c0bc
BIN
_examples/spreadsheet/flatten/formulas.xlsx
Normal file
BIN
_examples/spreadsheet/flatten/formulas.xlsx
Normal file
Binary file not shown.
79
_examples/spreadsheet/flatten/main.go
Normal file
79
_examples/spreadsheet/flatten/main.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
||||
package main
|
||||
// This example demonstrates flattening all formulas from an input Excel file and outputs the flattened values to a new xlsx.
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unioffice/spreadsheet"
|
||||
"github.com/unidoc/unioffice/spreadsheet/formula"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ss, err := spreadsheet.Open("formulas.xlsx")
|
||||
if err != nil {
|
||||
log.Fatalf("error opening document: %s", err)
|
||||
}
|
||||
|
||||
sheets := ss.Sheets()
|
||||
|
||||
start := time.Now().UnixNano()
|
||||
formEv := formula.NewEvaluator()
|
||||
for _, sheet := range sheets {
|
||||
fmt.Println("Sheet name:", sheet.Name())
|
||||
ctx := sheet.FormulaContext()
|
||||
for _, row := range sheet.Rows() {
|
||||
for _, cell := range row.Cells() {
|
||||
c := ctx.Cell(cell.Reference(), formEv)
|
||||
value := ""
|
||||
if cell.X().V != nil {
|
||||
value = *cell.X().V
|
||||
}
|
||||
cell.Clear()
|
||||
setValue(cell, c, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
finish := time.Now().UnixNano()
|
||||
fmt.Printf("total time: %d ns\n", finish - start)
|
||||
PrintMemUsage()
|
||||
|
||||
ss.SaveToFile("values.xlsx")
|
||||
}
|
||||
|
||||
func setValue(cell spreadsheet.Cell, c formula.Result, value string) {
|
||||
switch c.Type {
|
||||
case formula.ResultTypeNumber:
|
||||
if c.IsBoolean {
|
||||
cell.SetBool(value != "0")
|
||||
} else {
|
||||
cell.SetNumber(c.ValueNumber)
|
||||
}
|
||||
case formula.ResultTypeString:
|
||||
cell.SetString(c.ValueString)
|
||||
case formula.ResultTypeList:
|
||||
setValue(cell, c.ValueList[0], value)
|
||||
case formula.ResultTypeArray:
|
||||
setValue(cell, c.ValueArray[0][0], value)
|
||||
case formula.ResultTypeError:
|
||||
cell.SetError(c.ValueString)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintMemUsage() {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||
fmt.Println("Memory usage:")
|
||||
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
|
||||
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
|
||||
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
|
||||
fmt.Printf("\tNumGC = %v\n", m.NumGC)
|
||||
}
|
||||
|
||||
func bToMb(b uint64) uint64 {
|
||||
return b / 1024 / 1024
|
||||
}
|
@ -161,7 +161,7 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
|
||||
// SML
|
||||
case WorksheetType, WorksheetTypeStrict, WorksheetContentType:
|
||||
return fmt.Sprintf("xl/worksheets/sheet%d.xml", index)
|
||||
case SharedStingsType, SharedStingsTypeStrict, SharedStringsContentType:
|
||||
case SharedStringsType, SharedStringsTypeStrict, SharedStringsContentType:
|
||||
return "xl/sharedStrings.xml"
|
||||
|
||||
// WML
|
||||
|
@ -58,7 +58,7 @@ func TestSMLFilenames(t *testing.T) {
|
||||
{2, unioffice.CommentsType, "xl/comments2.xml"},
|
||||
{15, unioffice.WorksheetType, "xl/worksheets/sheet15.xml"},
|
||||
{2, unioffice.VMLDrawingType, "xl/drawings/vmlDrawing2.vml"},
|
||||
{0, unioffice.SharedStingsType, "xl/sharedStrings.xml"},
|
||||
{0, unioffice.SharedStringsType, "xl/sharedStrings.xml"},
|
||||
{1, unioffice.ThemeType, "xl/theme/theme1.xml"},
|
||||
{2, unioffice.ImageType, "xl/media/image2.png"},
|
||||
}
|
||||
|
12
schemas.go
12
schemas.go
@ -23,9 +23,11 @@ const (
|
||||
CustomXMLTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/customXml"
|
||||
|
||||
// SML strict
|
||||
WorksheetTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
|
||||
SharedStingsTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings"
|
||||
TableTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/table"
|
||||
WorksheetTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"
|
||||
SharedStringsTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings"
|
||||
// Deprecated: Renamed to SharedStringsTypeStrict, will be removed in next major version.
|
||||
SharedStingsTypeStrict = SharedStringsTypeStrict
|
||||
TableTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/table"
|
||||
|
||||
// WML strict
|
||||
HeaderTypeStrict = "http://purl.oclc.org/ooxml/officeDocument/relationships/header"
|
||||
@ -65,7 +67,9 @@ const (
|
||||
// SML
|
||||
WorksheetType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
|
||||
WorksheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
|
||||
SharedStingsType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
||||
SharedStringsType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
||||
// Deprecated: Renamed to SharedStringsType, will be removed in next major version.
|
||||
SharedStingsType = SharedStringsType
|
||||
SharedStringsContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
|
||||
SMLStyleSheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
|
||||
TableType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
|
||||
|
@ -142,6 +142,7 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error {
|
||||
// returning an ID from the shared strings table. To reuse a string, call
|
||||
// SetStringByID with the ID returned.
|
||||
func (c Cell) SetString(s string) int {
|
||||
c.w.ensureSharedStringsRelationships()
|
||||
c.clearValue()
|
||||
id := c.w.SharedStrings.AddString(s)
|
||||
c.x.V = unioffice.String(strconv.Itoa(id))
|
||||
@ -152,6 +153,7 @@ func (c Cell) SetString(s string) int {
|
||||
// SetStringByID sets the cell type to string, and the value a string in the
|
||||
// shared strings table.
|
||||
func (c Cell) SetStringByID(id int) {
|
||||
c.w.ensureSharedStringsRelationships()
|
||||
c.clearValue()
|
||||
c.x.V = unioffice.String(strconv.Itoa(id))
|
||||
c.x.TAttr = sml.ST_CellTypeS
|
||||
@ -293,6 +295,13 @@ func (c Cell) SetBool(v bool) {
|
||||
c.x.TAttr = sml.ST_CellTypeB
|
||||
}
|
||||
|
||||
// SetError sets the cell type to error and the value to the given error message.
|
||||
func (c Cell) SetError(msg string) {
|
||||
c.clearValue()
|
||||
c.x.V = unioffice.String(msg)
|
||||
c.x.TAttr = sml.ST_CellTypeE
|
||||
}
|
||||
|
||||
// GetValueAsBool retrieves the cell's value as a boolean
|
||||
func (c Cell) GetValueAsBool() (bool, error) {
|
||||
if c.x.TAttr != sml.ST_CellTypeB {
|
||||
@ -539,6 +548,11 @@ func (c Cell) IsBool() bool {
|
||||
return c.x.TAttr == sml.ST_CellTypeB
|
||||
}
|
||||
|
||||
// IsError returns true if the cell is an error type cell.
|
||||
func (c Cell) IsError() bool {
|
||||
return c.x.TAttr == sml.ST_CellTypeE
|
||||
}
|
||||
|
||||
// HasFormula returns true if the cell has an asoociated formula.
|
||||
func (c Cell) HasFormula() bool {
|
||||
return c.x.F != nil
|
||||
|
@ -10,6 +10,9 @@ package spreadsheet
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/unidoc/unioffice/spreadsheet/formula"
|
||||
"github.com/unidoc/unioffice/spreadsheet/reference"
|
||||
@ -26,6 +29,13 @@ type evalContext struct {
|
||||
}
|
||||
|
||||
func (e *evalContext) Cell(ref string, ev formula.Evaluator) formula.Result {
|
||||
if !validateRef(ref) {
|
||||
return formula.MakeErrorResultType(formula.ErrorTypeName, "")
|
||||
}
|
||||
fullRef := e.s.Name() + "!" + ref
|
||||
if cached, found := ev.GetFromCache(fullRef); found {
|
||||
return cached
|
||||
}
|
||||
cr, err := reference.ParseCellReference(ref)
|
||||
if err != nil {
|
||||
return formula.MakeErrorResult(fmt.Sprintf("error parsing %s: %s", ref, err))
|
||||
@ -52,21 +62,37 @@ func (e *evalContext) Cell(ref string, ev formula.Evaluator) formula.Result {
|
||||
e.evaluating[ref] = struct{}{}
|
||||
res := ev.Eval(e, c.GetFormula())
|
||||
delete(e.evaluating, ref)
|
||||
ev.SetCache(fullRef, res)
|
||||
return res
|
||||
}
|
||||
|
||||
if c.IsEmpty() {
|
||||
return formula.MakeEmptyResult()
|
||||
res := formula.MakeEmptyResult()
|
||||
ev.SetCache(fullRef, res)
|
||||
return res
|
||||
} else if c.IsNumber() {
|
||||
v, _ := c.GetValueAsNumber()
|
||||
return formula.MakeNumberResult(v)
|
||||
res := formula.MakeNumberResult(v)
|
||||
ev.SetCache(fullRef, res)
|
||||
return res
|
||||
} else if c.IsBool() {
|
||||
v, _ := c.GetValueAsBool()
|
||||
return formula.MakeBoolResult(v)
|
||||
res := formula.MakeBoolResult(v)
|
||||
ev.SetCache(fullRef, res)
|
||||
return res
|
||||
}
|
||||
|
||||
v, _ := c.GetRawValue()
|
||||
return formula.MakeStringResult(v)
|
||||
|
||||
if c.IsError() {
|
||||
errRes := formula.MakeErrorResult("")
|
||||
errRes.ValueString = v
|
||||
ev.SetCache(fullRef, errRes)
|
||||
return errRes
|
||||
}
|
||||
res := formula.MakeStringResult(v)
|
||||
ev.SetCache(fullRef, res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (e *evalContext) Sheet(name string) formula.Context {
|
||||
@ -86,7 +112,7 @@ func (e *evalContext) NamedRange(ref string) formula.Reference {
|
||||
}
|
||||
for _, tbl := range e.s.w.Tables() {
|
||||
if tbl.Name() == ref {
|
||||
return formula.MakeRangeReference(tbl.Reference())
|
||||
return formula.MakeRangeReference(fmt.Sprintf("%s!%s", e.s.Name(), tbl.Reference()))
|
||||
}
|
||||
}
|
||||
return formula.ReferenceInvalid
|
||||
@ -173,3 +199,47 @@ func (e *evalContext) IsDBCS() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LastColumn returns the name of last column which contains data in range of context sheet's given rows.
|
||||
func (e *evalContext) LastColumn(rowFrom, rowTo int) string {
|
||||
sheet := e.s
|
||||
max := 1
|
||||
for row := rowFrom; row <= rowTo; row++ {
|
||||
l := len(sheet.Row(uint32(row)).Cells())
|
||||
if l > max {
|
||||
max = l
|
||||
}
|
||||
}
|
||||
return reference.IndexToColumn(uint32(max-1))
|
||||
}
|
||||
|
||||
// LastRow returns the name of last row which contains data in range of context sheet's given columns.
|
||||
func (e *evalContext) LastRow(col string) int {
|
||||
sheet := e.s
|
||||
colIdx := int(reference.ColumnToIndex(col))
|
||||
max := 1
|
||||
for _, r := range sheet.x.SheetData.Row {
|
||||
if r.RAttr != nil {
|
||||
row := Row{sheet.w, sheet.x, r}
|
||||
l := len(row.Cells())
|
||||
if l > colIdx {
|
||||
max = int(row.RowNumber())
|
||||
}
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
var refRegexp *regexp.Regexp = regexp.MustCompile(`^([a-z]+)([0-9]+)$`)
|
||||
|
||||
func validateRef(cr string) bool {
|
||||
if submatch := refRegexp.FindStringSubmatch(strings.ToLower(cr)); len(submatch) > 2 {
|
||||
col := submatch[1]
|
||||
row, err := strconv.Atoi(submatch[2])
|
||||
if err != nil { // for the case if the row number is bigger then int capacity
|
||||
return false
|
||||
}
|
||||
return row <= 1048576 && col <= "zz"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -44,10 +44,23 @@ func NewBinaryExpr(lhs Expression, op BinOpType, rhs Expression) Expression {
|
||||
return BinaryExpr{lhs, rhs, op}
|
||||
}
|
||||
|
||||
func equalsToEmpty(res Result) bool {
|
||||
if res.Type == ResultTypeString {
|
||||
return res.ValueString == ""
|
||||
}
|
||||
return res.ValueNumber == 0
|
||||
}
|
||||
|
||||
// Eval evaluates the binary expression using the context given.
|
||||
func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
lhs := b.lhs.Eval(ctx, ev)
|
||||
if lhs.Type == ResultTypeError {
|
||||
return lhs
|
||||
}
|
||||
rhs := b.rhs.Eval(ctx, ev)
|
||||
if rhs.Type == ResultTypeError {
|
||||
return rhs
|
||||
}
|
||||
|
||||
// peel off array/list ops first
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -112,6 +125,17 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString < rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty { // empty always equals to empty but always less than string or number
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
} else if lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber { // when comparing string to number, string is always greater than number
|
||||
return MakeBoolResult(false)
|
||||
} else if lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(true)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(true)
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
case BinOpTypeGT:
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -121,6 +145,17 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString > rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
} else if lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber {
|
||||
return MakeBoolResult(true)
|
||||
} else if lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(false)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(false)
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(true)
|
||||
}
|
||||
case BinOpTypeEQ:
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -131,6 +166,15 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString == rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(true)
|
||||
}
|
||||
} else if (lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber) || (lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString) { // string never equals to number
|
||||
return MakeBoolResult(false)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(equalsToEmpty(rhs))
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(equalsToEmpty(lhs))
|
||||
}
|
||||
case BinOpTypeNE:
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -140,6 +184,15 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString != rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
} else if (lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber) || (lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(true)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(!equalsToEmpty(rhs))
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(!equalsToEmpty(lhs))
|
||||
}
|
||||
case BinOpTypeLEQ:
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -149,6 +202,17 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString <= rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(true)
|
||||
}
|
||||
} else if lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber {
|
||||
return MakeBoolResult(false)
|
||||
} else if lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(true)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(equalsToEmpty(rhs))
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(equalsToEmpty(lhs))
|
||||
}
|
||||
case BinOpTypeGEQ:
|
||||
if lhs.Type == rhs.Type {
|
||||
@ -158,6 +222,17 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
if lhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(lhs.ValueString >= rhs.ValueString)
|
||||
}
|
||||
if lhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(true)
|
||||
}
|
||||
} else if lhs.Type == ResultTypeString && rhs.Type == ResultTypeNumber {
|
||||
return MakeBoolResult(true)
|
||||
} else if lhs.Type == ResultTypeNumber && rhs.Type == ResultTypeString {
|
||||
return MakeBoolResult(false)
|
||||
} else if lhs.Type == ResultTypeEmpty && (rhs.Type == ResultTypeNumber || rhs.Type == ResultTypeString) {
|
||||
return MakeBoolResult(equalsToEmpty(rhs))
|
||||
} else if (lhs.Type == ResultTypeNumber || lhs.Type == ResultTypeString) && rhs.Type == ResultTypeEmpty {
|
||||
return MakeBoolResult(equalsToEmpty(lhs))
|
||||
}
|
||||
case BinOpTypeConcat:
|
||||
return MakeStringResult(lhs.Value() + rhs.Value())
|
||||
@ -166,6 +241,7 @@ func (b BinaryExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult("unsupported binary op")
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for BinaryExpr.
|
||||
func (b BinaryExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -13,10 +13,12 @@ import (
|
||||
"github.com/unidoc/unioffice"
|
||||
)
|
||||
|
||||
// Bool is a boolean expression.
|
||||
type Bool struct {
|
||||
b bool
|
||||
}
|
||||
|
||||
// NewBool constructs a new boolean expression.
|
||||
func NewBool(v string) Expression {
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
@ -25,10 +27,12 @@ func NewBool(v string) Expression {
|
||||
return Bool{b}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns a boolean.
|
||||
func (b Bool) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeBoolResult(b.b)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for Bool.
|
||||
func (b Bool) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ func (c CellRef) Eval(ctx Context, ev Evaluator) Result {
|
||||
return ctx.Cell(c.s, ev)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a cell.
|
||||
func (c CellRef) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeCell, Value: c.s}
|
||||
}
|
||||
|
@ -7,14 +7,17 @@
|
||||
|
||||
package formula
|
||||
|
||||
// ConstArrayExpr is a constant array expression.
|
||||
type ConstArrayExpr struct {
|
||||
data [][]Expression
|
||||
}
|
||||
|
||||
// NewConstArrayExpr constructs a new constant array expression with a given data.
|
||||
func NewConstArrayExpr(data [][]Expression) Expression {
|
||||
return &ConstArrayExpr{data}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of a constant array expression.
|
||||
func (c ConstArrayExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
res := [][]Result{}
|
||||
for _, row := range c.data {
|
||||
@ -27,6 +30,7 @@ func (c ConstArrayExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeArrayResult(res)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for ConstArrayExpr.
|
||||
func (c ConstArrayExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ type Context interface {
|
||||
// IsDBCS returns if workbook default language is among DBCS.
|
||||
IsDBCS() bool
|
||||
|
||||
// LastColumn returns the name of last column which contains data in range of context sheet's given rows.
|
||||
LastColumn(rowFrom, rowTo int) string
|
||||
|
||||
// LastRow returns the name of last row which contains data in range of context sheet's given columns.
|
||||
LastRow(colFrom string) int
|
||||
|
||||
// SetLocked returns sets cell's protected attribute.
|
||||
SetLocked(cellRef string, locked bool)
|
||||
|
||||
|
54
spreadsheet/formula/def_eval.go
Normal file
54
spreadsheet/formula/def_eval.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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"
|
||||
|
||||
// defEval is the default formula evaluator which implements the Evaluator interface.
|
||||
type defEval struct {
|
||||
evCache
|
||||
lastEvalIsRef bool
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of a formula.
|
||||
func (d *defEval) Eval(ctx Context, formula string) Result {
|
||||
expr := ParseString(formula)
|
||||
if expr != nil {
|
||||
d.checkLastEvalIsRef(ctx, expr)
|
||||
result := expr.Eval(ctx, d)
|
||||
if result.Type == ResultTypeError {
|
||||
d.lastEvalIsRef = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
return MakeErrorResult(fmt.Sprintf("unable to parse formula %s", formula))
|
||||
}
|
||||
|
||||
// LastEvalIsRef returns if last evaluation with the evaluator was a reference.
|
||||
func (d *defEval) LastEvalIsRef() bool {
|
||||
return d.lastEvalIsRef
|
||||
}
|
||||
|
||||
// checkLastEvalIsRef adds information which is needed for some functions but is lost after evaluation. E.g. which arguments are actually references.
|
||||
func (d *defEval) checkLastEvalIsRef(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, Range, HorizontalRange, VerticalRange, NamedRangeRef, PrefixExpr, PrefixRangeExpr, PrefixHorizontalRange, PrefixVerticalRange:
|
||||
evResult := arg.Eval(ctx, d)
|
||||
d.lastEvalIsRef = !(evResult.Type == ResultTypeError && evResult.ValueString == "#NAME?")
|
||||
default:
|
||||
d.lastEvalIsRef = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,17 +7,21 @@
|
||||
|
||||
package formula
|
||||
|
||||
// EmptyExpr is an empty expression.
|
||||
type EmptyExpr struct {
|
||||
}
|
||||
|
||||
// NewEmptyExpr constructs a new empty expression.
|
||||
func NewEmptyExpr() Expression {
|
||||
return EmptyExpr{}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of an empty expression.
|
||||
func (e EmptyExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeEmptyResult()
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for EmptyExpr.
|
||||
func (e EmptyExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -7,18 +7,22 @@
|
||||
|
||||
package formula
|
||||
|
||||
// Error is an error expression.
|
||||
type Error struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// NewError constructs a new error expression from a string.
|
||||
func NewError(v string) Expression {
|
||||
return Error{v}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of an error expression.
|
||||
func (e Error) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult(e.s)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for Error.
|
||||
func (e Error) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -7,75 +7,20 @@
|
||||
|
||||
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.
|
||||
// NOTE: in order to implement Evaluator without cache embed noCache in it.
|
||||
type Evaluator interface {
|
||||
Eval(ctx Context, formula string) Result
|
||||
SetCache(key string, value Result)
|
||||
GetFromCache(key string) (Result, bool)
|
||||
LastEvalIsRef() bool
|
||||
}
|
||||
|
||||
// NewEvaluator constructs a new defEval object which is the default formula evaluator.
|
||||
func NewEvaluator() Evaluator {
|
||||
return &defEval{}
|
||||
}
|
||||
|
||||
type defEval struct {
|
||||
isRef 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, err := strconv.Atoi(submatch[2])
|
||||
if err != nil { // for the case if the row number is bigger then int capacity
|
||||
return false
|
||||
}
|
||||
return row <= 1048576 && col <= "zz"
|
||||
}
|
||||
return false
|
||||
ev := &defEval{}
|
||||
ev.evCache = newEvCache()
|
||||
return ev
|
||||
}
|
||||
|
36
spreadsheet/formula/evcache.go
Normal file
36
spreadsheet/formula/evcache.go
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 "sync"
|
||||
|
||||
// evCache is a struct with collection of caching methods intended for add cache support to evaluators.
|
||||
type evCache struct {
|
||||
cache map[string]Result
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
func newEvCache() evCache {
|
||||
ev := evCache{}
|
||||
ev.cache = make(map[string]Result)
|
||||
ev.lock = &sync.Mutex{}
|
||||
return ev
|
||||
}
|
||||
|
||||
func (ec *evCache) SetCache(key string, value Result) {
|
||||
ec.lock.Lock()
|
||||
ec.cache[key] = value
|
||||
ec.lock.Unlock()
|
||||
}
|
||||
|
||||
func (ec *evCache) GetFromCache(key string) (Result, bool) {
|
||||
ec.lock.Lock()
|
||||
result, found := ec.cache[key]
|
||||
ec.lock.Unlock()
|
||||
return result, found
|
||||
}
|
@ -771,7 +771,7 @@ func YearFrac(args []Result) Result {
|
||||
basis := 0
|
||||
if argsNum == 3 && args[2].Type != ResultTypeEmpty {
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("YEARFRAC requires two or three number arguments")
|
||||
return MakeErrorResult("YEARFRAC requires basis argument to be a number")
|
||||
}
|
||||
basis = int(args[2].ValueNumber)
|
||||
if !checkBasis(basis) {
|
||||
|
@ -293,10 +293,11 @@ func Offset(ctx Context, ev Evaluator, args []Result) Result {
|
||||
return MakeErrorResult(fmt.Sprintf("Invalid range in OFFSET(): %s", ref.Type))
|
||||
}
|
||||
|
||||
col, rowIdx, err := ParseCellReference(origin)
|
||||
if err != nil {
|
||||
return MakeErrorResult(fmt.Sprintf("parse origin error OFFSET(): %s", err))
|
||||
parsedRef, parseErr := reference.ParseCellReference(origin)
|
||||
if parseErr != nil {
|
||||
return MakeErrorResult(fmt.Sprintf("parse origin error OFFSET(): %s", parseErr.Error()))
|
||||
}
|
||||
col, rowIdx, sheetName := parsedRef.Column, parsedRef.RowIdx, parsedRef.SheetName
|
||||
|
||||
rOff := args[1].AsNumber()
|
||||
if rOff.Type != ResultTypeNumber {
|
||||
@ -348,7 +349,11 @@ func Offset(ctx Context, ev Evaluator, args []Result) Result {
|
||||
|
||||
beg := fmt.Sprintf("%s%d", reference.IndexToColumn(origCol), origRow)
|
||||
end := fmt.Sprintf("%s%d", reference.IndexToColumn(endCol), endRow)
|
||||
return resultFromCellRange(ctx, ev, beg, end)
|
||||
if sheetName == "" {
|
||||
return resultFromCellRange(ctx, ev, beg, end)
|
||||
} else {
|
||||
return resultFromCellRange(ctx.Sheet(sheetName), ev, beg, end)
|
||||
}
|
||||
}
|
||||
|
||||
// VLookup implements the VLOOKUP function that returns a matching value from a
|
||||
@ -392,7 +397,7 @@ lfor:
|
||||
continue
|
||||
}
|
||||
rval := row[0]
|
||||
switch compareResults(rval, lookupValue, false) {
|
||||
switch compareResults(rval, lookupValue, false, exactMatch) {
|
||||
case cmpResultLess:
|
||||
// less than
|
||||
matchIdx = i
|
||||
@ -425,7 +430,7 @@ const (
|
||||
cmpResultInvalid cmpResult = 2
|
||||
)
|
||||
|
||||
func compareResults(lhs, rhs Result, caseSensitive bool) cmpResult {
|
||||
func compareResults(lhs, rhs Result, caseSensitive, exactMatch bool) cmpResult {
|
||||
lhs = lhs.AsNumber()
|
||||
rhs = rhs.AsNumber()
|
||||
// differing types
|
||||
@ -446,11 +451,21 @@ func compareResults(lhs, rhs Result, caseSensitive bool) cmpResult {
|
||||
|
||||
// both strings
|
||||
if lhs.Type == ResultTypeString {
|
||||
ls := lhs.ValueString
|
||||
rs := rhs.ValueString
|
||||
if !caseSensitive {
|
||||
return cmpResult(strings.Compare(strings.ToLower(lhs.ValueString),
|
||||
strings.ToLower(rhs.ValueString)))
|
||||
ls = strings.ToLower(ls)
|
||||
rs = strings.ToLower(rs)
|
||||
}
|
||||
return cmpResult(strings.Compare(lhs.ValueString, rhs.ValueString))
|
||||
if exactMatch {
|
||||
match := wildcard.Match(rs, ls)
|
||||
if match {
|
||||
return cmpResultEqual
|
||||
} else {
|
||||
return cmpResultGreater
|
||||
}
|
||||
}
|
||||
return cmpResult(strings.Compare(ls, rs))
|
||||
}
|
||||
|
||||
// empty cells are equal
|
||||
@ -467,7 +482,7 @@ func compareResults(lhs, rhs Result, caseSensitive bool) cmpResult {
|
||||
return cmpResultGreater
|
||||
}
|
||||
for i := range lhs.ValueList {
|
||||
cmp := compareResults(lhs.ValueList[i], rhs.ValueList[i], caseSensitive)
|
||||
cmp := compareResults(lhs.ValueList[i], rhs.ValueList[i], caseSensitive, exactMatch)
|
||||
if cmp != cmpResultEqual {
|
||||
return cmp
|
||||
}
|
||||
@ -493,7 +508,7 @@ func compareResults(lhs, rhs Result, caseSensitive bool) cmpResult {
|
||||
return cmpResultGreater
|
||||
}
|
||||
for c := range lrow {
|
||||
cmp := compareResults(lrow[c], rrow[c], caseSensitive)
|
||||
cmp := compareResults(lrow[c], rrow[c], caseSensitive, exactMatch)
|
||||
if cmp != cmpResultEqual {
|
||||
return cmp
|
||||
}
|
||||
@ -523,7 +538,7 @@ func Lookup(args []Result) Result {
|
||||
|
||||
idx := -1
|
||||
for i, v := range col {
|
||||
if compareResults(lookupValue, v, false) == cmpResultEqual {
|
||||
if compareResults(lookupValue, v, false, false) == cmpResultEqual {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
@ -592,7 +607,7 @@ func HLookup(args []Result) Result {
|
||||
row := arr.ValueArray[0]
|
||||
lfor:
|
||||
for i, val := range row {
|
||||
switch compareResults(val, lookupValue, false) {
|
||||
switch compareResults(val, lookupValue, false, exactMatch) {
|
||||
case cmpResultLess:
|
||||
// less than
|
||||
matchIdx = i
|
||||
|
@ -88,6 +88,9 @@ func Cell(ctx Context, ev Evaluator, args []Result) Result {
|
||||
return MakeErrorResult("Incorrect reference: " + refStr)
|
||||
}
|
||||
address := "$"+cr.Column+"$"+strconv.Itoa(int(cr.RowIdx))
|
||||
if cr.SheetName != "" {
|
||||
address = cr.SheetName + "!" + address
|
||||
}
|
||||
return MakeStringResult(address)
|
||||
case "col":
|
||||
cr, err := reference.ParseCellReference(refStr)
|
||||
@ -189,7 +192,11 @@ func Cell(ctx Context, ev Evaluator, args []Result) Result {
|
||||
if err != nil {
|
||||
return MakeErrorResult("Incorrect reference: " + refStr)
|
||||
}
|
||||
return MakeNumberResult(ctx.GetWidth(int(cr.ColumnIdx)))
|
||||
if cr.SheetName == "" {
|
||||
return MakeNumberResult(ctx.GetWidth(int(cr.ColumnIdx)))
|
||||
} else {
|
||||
return MakeNumberResult(ctx.Sheet(cr.SheetName).GetWidth(int(cr.ColumnIdx)))
|
||||
}
|
||||
}
|
||||
return MakeErrorResult("Incorrect first argument of CELL: "+typ.ValueString)
|
||||
}
|
||||
@ -295,7 +302,7 @@ func IsLogical(ctx Context, ev Evaluator, args []Result) Result {
|
||||
return MakeErrorResult("ISLOGICAL requires the first argument to be of type reference")
|
||||
}
|
||||
|
||||
return MakeBoolResult(ctx.IsBool(ref.Value))
|
||||
return MakeBoolResult(ctx.Cell(ref.Value, ev).IsBoolean)
|
||||
}
|
||||
|
||||
// IsNA is an implementation of the Excel ISNA() function.
|
||||
@ -342,7 +349,7 @@ func IsRef(ctx Context, ev Evaluator, args []Result) Result {
|
||||
if len(args) != 1 {
|
||||
MakeErrorResult("ISREF() accepts a single argument")
|
||||
}
|
||||
return MakeBoolResult(ev.(*defEval).isRef)
|
||||
return MakeBoolResult(ev.LastEvalIsRef())
|
||||
}
|
||||
|
||||
// ISTEXT is an implementation of the Excel ISTEXT() function.
|
||||
|
@ -19,7 +19,6 @@ func init() {
|
||||
RegisterFunction("OR", Or)
|
||||
RegisterFunction("TRUE", True)
|
||||
RegisterFunction("_xlfn.XOR", Xor)
|
||||
|
||||
}
|
||||
|
||||
// And is an implementation of the Excel AND() function.
|
||||
@ -91,36 +90,63 @@ func If(args []Result) Result {
|
||||
// false case
|
||||
if len(args) == 3 {
|
||||
return args[2]
|
||||
} else {
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
case ResultTypeList:
|
||||
return MakeListResult(ifList(args))
|
||||
return ifList(args)
|
||||
case ResultTypeArray:
|
||||
return ifArray(args)
|
||||
|
||||
default:
|
||||
return MakeErrorResult("IF initial argument must be numeric or array")
|
||||
}
|
||||
return MakeBoolResult(false)
|
||||
}
|
||||
|
||||
func fillArray(arg Result, rows, cols int) [][]Result {
|
||||
array := [][]Result{}
|
||||
result := [][]Result{}
|
||||
switch arg.Type {
|
||||
case ResultTypeArray:
|
||||
return arg.ValueArray
|
||||
case ResultTypeNumber, ResultTypeString:
|
||||
for r := 0; r < rows; r++ {
|
||||
list := []Result{}
|
||||
for c := 0; c < cols; c++ {
|
||||
list = append(list, arg)
|
||||
for ir, row := range arg.ValueArray {
|
||||
if ir < rows {
|
||||
result = append(result, fillList(MakeListResult(row), cols))
|
||||
} else {
|
||||
result = append(result, fillList(MakeErrorResultType(ErrorTypeNA, ""), cols))
|
||||
}
|
||||
array = append(array, list)
|
||||
}
|
||||
return array
|
||||
case ResultTypeList:
|
||||
return [][]Result{arg.ValueList}
|
||||
list := fillList(arg, cols)
|
||||
for r := 0; r < rows; r++ {
|
||||
result = append(result, list)
|
||||
}
|
||||
case ResultTypeNumber, ResultTypeString, ResultTypeError, ResultTypeEmpty:
|
||||
for r := 0; r < rows; r++ {
|
||||
list := fillList(arg, cols)
|
||||
result = append(result, list)
|
||||
}
|
||||
}
|
||||
return [][]Result{}
|
||||
return result
|
||||
}
|
||||
|
||||
func fillList(arg Result, cols int) []Result {
|
||||
list := []Result{}
|
||||
switch arg.Type {
|
||||
case ResultTypeList:
|
||||
valueList := arg.ValueList
|
||||
maxCols := len(valueList)
|
||||
for ic := 0; ic < cols; ic++ {
|
||||
if ic < maxCols {
|
||||
list = append(list, valueList[ic])
|
||||
} else {
|
||||
list = append(list, MakeErrorResultType(ErrorTypeNA, ""))
|
||||
}
|
||||
}
|
||||
case ResultTypeNumber, ResultTypeString, ResultTypeError, ResultTypeEmpty:
|
||||
for ic := 0; ic < cols; ic++ {
|
||||
list = append(list, arg)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func ifArray(args []Result) Result {
|
||||
@ -129,7 +155,7 @@ func ifArray(args []Result) Result {
|
||||
if len(args) == 1 {
|
||||
result := [][]Result{}
|
||||
for _, v := range condArray {
|
||||
result = append(result, ifList([]Result{MakeListResult(v)}))
|
||||
result = append(result, ifList([]Result{MakeListResult(v)}).ValueList)
|
||||
}
|
||||
return MakeArrayResult(result)
|
||||
} else if len(args) == 2 {
|
||||
@ -143,12 +169,9 @@ func ifArray(args []Result) Result {
|
||||
if i < tl {
|
||||
truesList = truesArray[i]
|
||||
} else {
|
||||
truesList = []Result{}
|
||||
for j := 0; j < cols; j++ {
|
||||
truesList = append(truesList, MakeErrorResultType(ErrorTypeValue, ""))
|
||||
}
|
||||
truesList = fillList(MakeErrorResultType(ErrorTypeNA, ""), cols)
|
||||
}
|
||||
result = append(result, ifList([]Result{MakeListResult(v), MakeListResult(truesList)}))
|
||||
result = append(result, ifList([]Result{MakeListResult(v), MakeListResult(truesList)}).ValueList)
|
||||
}
|
||||
return MakeArrayResult(result)
|
||||
} else if len(args) == 3 {
|
||||
@ -164,85 +187,141 @@ func ifArray(args []Result) Result {
|
||||
if i < tl {
|
||||
truesList = truesArray[i]
|
||||
} else {
|
||||
truesList = []Result{}
|
||||
for j := 0; j < cols; j++ {
|
||||
truesList = append(truesList, MakeErrorResultType(ErrorTypeValue, ""))
|
||||
}
|
||||
truesList = fillList(MakeErrorResultType(ErrorTypeNA, ""), cols)
|
||||
}
|
||||
if i < fl {
|
||||
falsesList = falsesArray[i]
|
||||
} else {
|
||||
falsesList = []Result{}
|
||||
for j := 0; j < cols; j++ {
|
||||
falsesList = append(falsesList, MakeErrorResultType(ErrorTypeValue, ""))
|
||||
}
|
||||
falsesList = fillList(MakeErrorResultType(ErrorTypeNA, ""), cols)
|
||||
}
|
||||
result = append(result, ifList([]Result{MakeListResult(v), MakeListResult(truesList), MakeListResult(falsesList)}))
|
||||
result = append(result, ifList([]Result{MakeListResult(v), MakeListResult(truesList), MakeListResult(falsesList)}).ValueList)
|
||||
}
|
||||
return MakeArrayResult(result)
|
||||
}
|
||||
return MakeErrorResultType(ErrorTypeValue, "")
|
||||
}
|
||||
|
||||
func ifList(args []Result) []Result {
|
||||
func ifList(args []Result) Result {
|
||||
condList := args[0].ValueList
|
||||
cols := len(condList)
|
||||
switch len(args) {
|
||||
// single argument returns list of contitions
|
||||
if len(args) == 1 {
|
||||
case 1:
|
||||
result := []Result{}
|
||||
for _, v := range condList {
|
||||
result = append(result, MakeBoolResult(v.ValueNumber != 0))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return MakeListResult(result)
|
||||
// two arguments case
|
||||
if len(args) == 2 {
|
||||
trues := args[1].ValueList
|
||||
tl := len(trues)
|
||||
result := []Result{}
|
||||
for i, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = MakeBoolResult(false)
|
||||
} else {
|
||||
if i < tl {
|
||||
newValue = trues[i]
|
||||
case 2:
|
||||
trues := args[1]
|
||||
switch trues.Type {
|
||||
case ResultTypeNumber, ResultTypeString, ResultTypeEmpty:
|
||||
result := []Result{}
|
||||
for _, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = MakeBoolResult(false)
|
||||
} else {
|
||||
newValue = MakeErrorResultType(ErrorTypeValue, "")
|
||||
newValue = trues
|
||||
}
|
||||
result = append(result, newValue)
|
||||
}
|
||||
result = append(result, newValue)
|
||||
return MakeListResult(result)
|
||||
case ResultTypeList:
|
||||
truesList := fillList(trues, cols)
|
||||
result := []Result{}
|
||||
for i, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = MakeBoolResult(false)
|
||||
} else {
|
||||
newValue = truesList[i]
|
||||
}
|
||||
result = append(result, newValue)
|
||||
}
|
||||
return MakeListResult(result)
|
||||
case ResultTypeArray:
|
||||
truesArray := fillArray(trues, len(trues.ValueArray), cols)
|
||||
result := [][]Result{}
|
||||
for _, row := range truesArray {
|
||||
rowResult := []Result{}
|
||||
for ic, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = MakeBoolResult(false)
|
||||
} else {
|
||||
newValue = row[ic]
|
||||
}
|
||||
rowResult = append(rowResult, newValue)
|
||||
}
|
||||
result = append(result, rowResult)
|
||||
}
|
||||
return MakeArrayResult(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// false case
|
||||
if len(args) == 3 {
|
||||
trues := args[1].ValueList
|
||||
falses := args[2].ValueList
|
||||
tl := len(trues)
|
||||
tf := len(falses)
|
||||
result := []Result{}
|
||||
for i, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber != 0 {
|
||||
if i < tl {
|
||||
newValue = trues[i]
|
||||
case 3:
|
||||
trues := args[1]
|
||||
falses := args[2]
|
||||
truesSingleValue := checkSingleValue(trues)
|
||||
falsesSingleValue := checkSingleValue(falses)
|
||||
if truesSingleValue && falsesSingleValue {
|
||||
result := []Result{}
|
||||
for _, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = falses
|
||||
} else {
|
||||
newValue = MakeErrorResultType(ErrorTypeValue, "")
|
||||
}
|
||||
} else {
|
||||
if i < tf {
|
||||
newValue = falses[i]
|
||||
} else {
|
||||
newValue = MakeErrorResultType(ErrorTypeValue, "")
|
||||
newValue = trues
|
||||
}
|
||||
result = append(result, newValue)
|
||||
}
|
||||
result = append(result, newValue)
|
||||
return MakeListResult(result)
|
||||
}
|
||||
return result
|
||||
|
||||
if trues.Type != ResultTypeArray && falses.Type != ResultTypeArray {
|
||||
truesList := fillList(trues, cols)
|
||||
falsesList := fillList(falses, cols)
|
||||
result := []Result{}
|
||||
for i, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = falsesList[i]
|
||||
} else {
|
||||
newValue = truesList[i]
|
||||
}
|
||||
result = append(result, newValue)
|
||||
}
|
||||
return MakeListResult(result)
|
||||
}
|
||||
maxRows := len(trues.ValueArray)
|
||||
if len(falses.ValueArray) > maxRows {
|
||||
maxRows = len(falses.ValueArray)
|
||||
}
|
||||
truesArray := fillArray(trues, maxRows, cols)
|
||||
falsesArray := fillArray(falses, maxRows, cols)
|
||||
result := [][]Result{}
|
||||
for ir := 0; ir < maxRows; ir++ {
|
||||
rowResult := []Result{}
|
||||
for ic, v := range condList {
|
||||
var newValue Result
|
||||
if v.ValueNumber == 0 {
|
||||
newValue = falsesArray[ir][ic]
|
||||
} else {
|
||||
newValue = truesArray[ir][ic]
|
||||
}
|
||||
rowResult = append(rowResult, newValue)
|
||||
}
|
||||
result = append(result, rowResult)
|
||||
}
|
||||
return MakeArrayResult(result)
|
||||
}
|
||||
return []Result{}
|
||||
return MakeErrorResult("")
|
||||
}
|
||||
|
||||
func checkSingleValue(result Result) bool {
|
||||
t := result.Type
|
||||
return t != ResultTypeArray && t != ResultTypeList
|
||||
}
|
||||
|
||||
// IfError is an implementation of the Excel IFERROR() function. It takes two arguments.
|
||||
|
@ -174,15 +174,21 @@ func parseSearchResults(fname string, args []Result) (*parsedSearchObject, Resul
|
||||
return nil, MakeErrorResult(fname + " requires two or three arguments")
|
||||
}
|
||||
findTextResult := args[0]
|
||||
if findTextResult.Type != ResultTypeString {
|
||||
if findTextResult.Type == ResultTypeError {
|
||||
return nil, findTextResult
|
||||
}
|
||||
if findTextResult.Type != ResultTypeString && findTextResult.Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult("The first argument should be a string")
|
||||
}
|
||||
textResult := args[1]
|
||||
if textResult.Type != ResultTypeString {
|
||||
if textResult.Type == ResultTypeError {
|
||||
return nil, textResult
|
||||
}
|
||||
if textResult.Type != ResultTypeString && textResult.Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult("The second argument should be a string")
|
||||
}
|
||||
text := textResult.ValueString
|
||||
findText := findTextResult.ValueString
|
||||
text := textResult.Value()
|
||||
findText := findTextResult.Value()
|
||||
position := 1
|
||||
if argsNum == 3 && args[2].Type != ResultTypeEmpty {
|
||||
positionResult := args[2]
|
||||
@ -388,10 +394,14 @@ func Mid(args []Result) Result {
|
||||
if len(args) != 3 {
|
||||
return MakeErrorResult("MID requires three arguments")
|
||||
}
|
||||
if args[0].Type != ResultTypeString {
|
||||
textArg := args[0]
|
||||
if textArg.Type == ResultTypeError {
|
||||
return textArg
|
||||
}
|
||||
if textArg.Type != ResultTypeString && textArg.Type != ResultTypeNumber && textArg.Type != ResultTypeEmpty {
|
||||
return MakeErrorResult("MID requires text to be a string")
|
||||
}
|
||||
text := args[0].ValueString
|
||||
text := args[0].Value()
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("MID requires start_num to be a number")
|
||||
}
|
||||
|
@ -7,15 +7,18 @@
|
||||
|
||||
package formula
|
||||
|
||||
// FunctionCall is a function call expression.
|
||||
type FunctionCall struct {
|
||||
name string
|
||||
args []Expression
|
||||
}
|
||||
|
||||
// NewFunction constructs a new function call expression.
|
||||
func NewFunction(name string, args []Expression) Expression {
|
||||
return FunctionCall{name, args}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of a function call.
|
||||
func (f FunctionCall) Eval(ctx Context, ev Evaluator) Result {
|
||||
fn := LookupFunction(f.name)
|
||||
if fn != nil {
|
||||
@ -39,6 +42,7 @@ func (f FunctionCall) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult("unknown function " + f.name)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for FunctionCall.
|
||||
func (f FunctionCall) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -920,12 +920,14 @@ func TestIsLogical(t *testing.T) {
|
||||
sheet.Cell("A2").SetBool(false)
|
||||
sheet.Cell("A3").SetNumber(0)
|
||||
sheet.Cell("A4").SetString("FALSE")
|
||||
sheet.Cell("A5").SetFormulaRaw("=1=2")
|
||||
|
||||
td := []testStruct{
|
||||
{`=ISLOGICAL(A1)`, `1 ResultTypeNumber`},
|
||||
{`=ISLOGICAL(A2)`, `1 ResultTypeNumber`},
|
||||
{`=ISLOGICAL(A3)`, `0 ResultTypeNumber`},
|
||||
{`=ISLOGICAL(A4)`, `0 ResultTypeNumber`},
|
||||
{`=ISLOGICAL(A5)`, `1 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
@ -960,7 +962,11 @@ func TestIsRef(t *testing.T) {
|
||||
sheet.Cell("A3").SetFormulaRaw("=ISREF(A1048577)")
|
||||
sheet.Cell("A4").SetFormulaRaw("=ISREF(ZZA0)")
|
||||
sheet.Cell("A5").SetFormulaRaw("=ISREF(ZZ0)")
|
||||
sheet.Cell("A6").SetString("A1")
|
||||
sheet.Cell("A6").SetFormulaRaw("=ISREF(AA:ZZ)")
|
||||
sheet.Cell("A7").SetFormulaRaw("=ISREF(1:4)")
|
||||
sheet.Cell("A8").SetFormulaRaw("=4/0")
|
||||
sheet.Cell("A9").SetFormulaRaw("=ISREF(A7)")
|
||||
sheet.Cell("A10").SetString("A1")
|
||||
|
||||
td := []testStruct{
|
||||
{`A1`, `1 ResultTypeNumber`},
|
||||
@ -968,7 +974,11 @@ func TestIsRef(t *testing.T) {
|
||||
{`A3`, `0 ResultTypeNumber`},
|
||||
{`A4`, `0 ResultTypeNumber`},
|
||||
{`A5`, `1 ResultTypeNumber`},
|
||||
{`A6`, `A1 ResultTypeString`},
|
||||
{`A6`, `1 ResultTypeNumber`},
|
||||
{`A7`, `1 ResultTypeNumber`},
|
||||
{`A8`, `#DIV/0! ResultTypeError`},
|
||||
{`A9`, `1 ResultTypeNumber`},
|
||||
{`A10`, `A1 ResultTypeString`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
@ -1,3 +1,4 @@
|
||||
// Code generated by goyacc -l -o grammar.go grammar.y. DO NOT EDIT.
|
||||
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by the terms of the Affero GNU General
|
||||
@ -18,43 +19,45 @@ type yySymType struct {
|
||||
}
|
||||
|
||||
const tokenHorizontalRange = 57346
|
||||
const tokenReservedName = 57347
|
||||
const tokenDDECall = 57348
|
||||
const tokenLexError = 57349
|
||||
const tokenNamedRange = 57350
|
||||
const tokenBool = 57351
|
||||
const tokenNumber = 57352
|
||||
const tokenString = 57353
|
||||
const tokenError = 57354
|
||||
const tokenErrorRef = 57355
|
||||
const tokenSheet = 57356
|
||||
const tokenCell = 57357
|
||||
const tokenFunctionBuiltin = 57358
|
||||
const tokenLBrace = 57359
|
||||
const tokenRBrace = 57360
|
||||
const tokenLParen = 57361
|
||||
const tokenRParen = 57362
|
||||
const tokenPlus = 57363
|
||||
const tokenMinus = 57364
|
||||
const tokenMult = 57365
|
||||
const tokenDiv = 57366
|
||||
const tokenExp = 57367
|
||||
const tokenEQ = 57368
|
||||
const tokenLT = 57369
|
||||
const tokenGT = 57370
|
||||
const tokenLEQ = 57371
|
||||
const tokenGEQ = 57372
|
||||
const tokenNE = 57373
|
||||
const tokenColon = 57374
|
||||
const tokenComma = 57375
|
||||
const tokenAmpersand = 57376
|
||||
const tokenSemi = 57377
|
||||
const tokenVerticalRange = 57347
|
||||
const tokenReservedName = 57348
|
||||
const tokenDDECall = 57349
|
||||
const tokenLexError = 57350
|
||||
const tokenNamedRange = 57351
|
||||
const tokenBool = 57352
|
||||
const tokenNumber = 57353
|
||||
const tokenString = 57354
|
||||
const tokenError = 57355
|
||||
const tokenErrorRef = 57356
|
||||
const tokenSheet = 57357
|
||||
const tokenCell = 57358
|
||||
const tokenFunctionBuiltin = 57359
|
||||
const tokenLBrace = 57360
|
||||
const tokenRBrace = 57361
|
||||
const tokenLParen = 57362
|
||||
const tokenRParen = 57363
|
||||
const tokenPlus = 57364
|
||||
const tokenMinus = 57365
|
||||
const tokenMult = 57366
|
||||
const tokenDiv = 57367
|
||||
const tokenExp = 57368
|
||||
const tokenEQ = 57369
|
||||
const tokenLT = 57370
|
||||
const tokenGT = 57371
|
||||
const tokenLEQ = 57372
|
||||
const tokenGEQ = 57373
|
||||
const tokenNE = 57374
|
||||
const tokenColon = 57375
|
||||
const tokenComma = 57376
|
||||
const tokenAmpersand = 57377
|
||||
const tokenSemi = 57378
|
||||
|
||||
var yyToknames = [...]string{
|
||||
"$end",
|
||||
"error",
|
||||
"$unk",
|
||||
"tokenHorizontalRange",
|
||||
"tokenVerticalRange",
|
||||
"tokenReservedName",
|
||||
"tokenDDECall",
|
||||
"tokenLexError",
|
||||
@ -101,82 +104,88 @@ var yyExca = [...]int{
|
||||
|
||||
const yyPrivate = 57344
|
||||
|
||||
const yyLast = 179
|
||||
const yyLast = 187
|
||||
|
||||
var yyAct = [...]int{
|
||||
|
||||
43, 3, 42, 30, 18, 38, 68, 44, 45, 28,
|
||||
29, 30, 37, 46, 47, 30, 50, 41, 24, 13,
|
||||
37, 19, 66, 52, 48, 23, 21, 53, 54, 55,
|
||||
56, 57, 58, 59, 60, 61, 62, 63, 64, 67,
|
||||
51, 65, 76, 11, 9, 1, 20, 10, 2, 8,
|
||||
73, 71, 70, 26, 27, 28, 29, 30, 35, 31,
|
||||
32, 33, 34, 36, 72, 0, 37, 0, 0, 75,
|
||||
74, 0, 0, 77, 69, 26, 27, 28, 29, 30,
|
||||
35, 31, 32, 33, 34, 36, 0, 0, 37, 26,
|
||||
27, 28, 29, 30, 35, 31, 32, 33, 34, 36,
|
||||
0, 0, 37, 26, 27, 28, 29, 30, 0, 0,
|
||||
0, 24, 14, 15, 16, 17, 37, 25, 23, 22,
|
||||
39, 0, 12, 0, 6, 7, 0, 0, 0, 40,
|
||||
24, 14, 15, 16, 17, 0, 25, 23, 22, 5,
|
||||
0, 12, 0, 6, 7, 0, 0, 0, 4, 24,
|
||||
14, 15, 16, 17, 0, 25, 23, 22, 39, 0,
|
||||
12, 49, 6, 7, 24, 14, 15, 16, 17, 0,
|
||||
25, 23, 22, 39, 0, 12, 0, 6, 7,
|
||||
45, 3, 44, 32, 18, 40, 72, 46, 47, 30,
|
||||
31, 32, 39, 48, 28, 29, 30, 31, 32, 75,
|
||||
39, 49, 32, 56, 50, 70, 23, 39, 76, 57,
|
||||
58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
|
||||
68, 77, 71, 69, 54, 43, 13, 19, 21, 55,
|
||||
82, 11, 78, 9, 74, 28, 29, 30, 31, 32,
|
||||
37, 33, 34, 35, 36, 38, 1, 20, 39, 10,
|
||||
2, 8, 0, 80, 79, 0, 0, 0, 83, 0,
|
||||
81, 73, 28, 29, 30, 31, 32, 37, 33, 34,
|
||||
35, 36, 38, 0, 0, 39, 28, 29, 30, 31,
|
||||
32, 37, 33, 34, 35, 36, 38, 26, 27, 39,
|
||||
51, 52, 25, 14, 15, 16, 17, 0, 24, 23,
|
||||
22, 41, 23, 12, 0, 6, 7, 26, 27, 0,
|
||||
42, 0, 25, 14, 15, 16, 17, 0, 24, 23,
|
||||
22, 5, 0, 12, 0, 6, 7, 26, 27, 0,
|
||||
4, 0, 25, 14, 15, 16, 17, 0, 24, 23,
|
||||
22, 41, 0, 12, 53, 6, 7, 26, 27, 0,
|
||||
0, 0, 25, 14, 15, 16, 17, 0, 24, 23,
|
||||
22, 41, 0, 12, 0, 6, 7,
|
||||
}
|
||||
var yyPact = [...]int{
|
||||
|
||||
122, -1000, -1000, 68, 156, 103, 156, 156, -1000, -1000,
|
||||
-1000, -1000, 156, -1000, -1000, -1000, -1000, -1000, -18, 10,
|
||||
-1000, -1000, 141, -1000, -1000, -1000, 156, 156, 156, 156,
|
||||
156, 156, 156, 156, 156, 156, 156, 156, 68, 156,
|
||||
156, 4, -27, 68, -14, -14, 54, 10, -1000, -1000,
|
||||
31, -1000, 68, -14, -14, -22, -22, -1000, 82, 82,
|
||||
82, 82, 82, 82, -10, 32, -1000, 156, 156, -1000,
|
||||
-1000, -1000, 156, -1000, -27, 68, -1000, 68,
|
||||
123, -1000, -1000, 74, 163, 103, 163, 163, -1000, -1000,
|
||||
-1000, -1000, 163, -1000, -1000, -1000, -1000, -1000, -12, 106,
|
||||
-1000, -1000, 143, -1000, -1000, -1000, -1000, -1000, 163, 163,
|
||||
163, 163, 163, 163, 163, 163, 163, 163, 163, 163,
|
||||
74, 163, 163, 6, -28, 74, -15, -15, 60, 10,
|
||||
-14, -1000, -1000, -1000, 7, -1000, 74, -15, -15, -23,
|
||||
-23, -1000, -8, -8, -8, -8, -8, -8, -4, 33,
|
||||
-1000, 163, 163, -1000, -1000, 10, -1000, 163, -1000, -28,
|
||||
74, -1000, -1000, 74,
|
||||
}
|
||||
var yyPgo = [...]int{
|
||||
|
||||
0, 0, 49, 48, 47, 4, 46, 45, 44, 43,
|
||||
42, 40, 26, 21, 19, 17, 16, 2,
|
||||
0, 0, 71, 70, 69, 4, 67, 66, 53, 51,
|
||||
50, 49, 48, 47, 46, 45, 44, 2,
|
||||
}
|
||||
var yyR1 = [...]int{
|
||||
|
||||
0, 7, 3, 3, 3, 8, 8, 8, 8, 1,
|
||||
1, 1, 2, 2, 2, 2, 2, 14, 15, 15,
|
||||
17, 17, 4, 4, 4, 13, 5, 5, 6, 12,
|
||||
12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
|
||||
12, 9, 9, 9, 16, 16, 11, 10, 10,
|
||||
17, 17, 4, 4, 4, 13, 5, 6, 6, 6,
|
||||
6, 6, 6, 6, 12, 12, 12, 12, 12, 12,
|
||||
12, 12, 12, 12, 12, 12, 9, 9, 9, 16,
|
||||
16, 11, 10, 10,
|
||||
}
|
||||
var yyR2 = [...]int{
|
||||
|
||||
0, 1, 1, 2, 4, 1, 1, 1, 1, 2,
|
||||
2, 1, 1, 1, 1, 3, 1, 3, 1, 3,
|
||||
1, 3, 1, 2, 1, 1, 1, 1, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 1, 2, 3, 1, 3, 1, 1, 0,
|
||||
1, 3, 1, 2, 1, 1, 1, 3, 4, 1,
|
||||
1, 1, 2, 2, 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, 26, 17, 21, 22, -2, -8,
|
||||
-4, -9, 19, -14, 9, 10, 11, 12, -5, -13,
|
||||
-6, -12, 16, 15, 8, 14, 21, 22, 23, 24,
|
||||
25, 27, 28, 29, 30, 26, 31, 34, -1, 17,
|
||||
26, -15, -17, -1, -1, -1, -1, 32, -5, 20,
|
||||
-16, -11, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, 18, 35, 33, 20,
|
||||
-5, 20, 33, 18, -17, -1, -10, -1,
|
||||
-1000, -7, -3, -1, 27, 18, 22, 23, -2, -8,
|
||||
-4, -9, 20, -14, 10, 11, 12, 13, -5, -13,
|
||||
-6, -12, 17, 16, 15, 9, 4, 5, 22, 23,
|
||||
24, 25, 26, 28, 29, 30, 31, 27, 32, 35,
|
||||
-1, 18, 27, -15, -17, -1, -1, -1, -1, 33,
|
||||
-5, 4, 5, 21, -16, -11, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
19, 36, 34, 21, -5, 33, 21, 34, 19, -17,
|
||||
-1, -5, -10, -1,
|
||||
}
|
||||
var yyDef = [...]int{
|
||||
|
||||
0, -2, 1, 2, 0, 0, 0, 0, 11, 12,
|
||||
13, 14, 0, 16, 5, 6, 7, 8, 22, 0,
|
||||
24, 41, 0, 26, 27, 25, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
|
||||
0, 0, 18, 20, 9, 10, 0, 0, 23, 42,
|
||||
0, 44, 46, 29, 30, 31, 32, 33, 34, 35,
|
||||
36, 37, 38, 39, 40, 0, 17, 0, 0, 15,
|
||||
28, 43, 48, 4, 19, 21, 45, 47,
|
||||
24, 46, 0, 26, 25, 29, 30, 31, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
3, 0, 0, 0, 18, 20, 9, 10, 0, 0,
|
||||
23, 32, 33, 47, 0, 49, 51, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 44, 45, 0,
|
||||
17, 0, 0, 15, 27, 0, 48, 53, 4, 19,
|
||||
21, 28, 50, 52,
|
||||
}
|
||||
var yyTok1 = [...]int{
|
||||
|
||||
@ -187,7 +196,7 @@ var yyTok2 = [...]int{
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
||||
22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35,
|
||||
32, 33, 34, 35, 36,
|
||||
}
|
||||
var yyTok3 = [...]int{
|
||||
0,
|
||||
@ -618,96 +627,121 @@ yydefault:
|
||||
yyVAL.expr = NewCellRef(yyDollar[1].node.val)
|
||||
}
|
||||
case 27:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewNamedRangeRef(yyDollar[1].node.val)
|
||||
}
|
||||
case 28:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewRange(yyDollar[1].expr, yyDollar[3].expr)
|
||||
}
|
||||
case 29:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
case 28:
|
||||
yyDollar = yyS[yypt-4 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypePlus, yyDollar[3].expr)
|
||||
yyVAL.expr = NewPrefixRangeExpr(yyDollar[1].expr, yyDollar[2].expr, yyDollar[4].expr)
|
||||
}
|
||||
case 29:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewNamedRangeRef(yyDollar[1].node.val)
|
||||
}
|
||||
case 30:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMinus, yyDollar[3].expr)
|
||||
yyVAL.expr = NewHorizontalRange(yyDollar[1].node.val)
|
||||
}
|
||||
case 31:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMult, yyDollar[3].expr)
|
||||
yyVAL.expr = NewVerticalRange(yyDollar[1].node.val)
|
||||
}
|
||||
case 32:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeDiv, yyDollar[3].expr)
|
||||
yyVAL.expr = NewPrefixHorizontalRange(yyDollar[1].expr, yyDollar[2].node.val)
|
||||
}
|
||||
case 33:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeExp, yyDollar[3].expr)
|
||||
yyVAL.expr = NewPrefixVerticalRange(yyDollar[1].expr, yyDollar[2].node.val)
|
||||
}
|
||||
case 34:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLT, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypePlus, yyDollar[3].expr)
|
||||
}
|
||||
case 35:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGT, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMinus, yyDollar[3].expr)
|
||||
}
|
||||
case 36:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLEQ, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeMult, yyDollar[3].expr)
|
||||
}
|
||||
case 37:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGEQ, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeDiv, yyDollar[3].expr)
|
||||
}
|
||||
case 38:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeEQ, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeExp, yyDollar[3].expr)
|
||||
}
|
||||
case 39:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeNE, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLT, yyDollar[3].expr)
|
||||
}
|
||||
case 40:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeConcat, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGT, yyDollar[3].expr)
|
||||
}
|
||||
case 41:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeLEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 42:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, nil)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeGEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 43:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, yyDollar[2].args)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeEQ, yyDollar[3].expr)
|
||||
}
|
||||
case 44:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyVAL.args, yyDollar[1].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeNE, yyDollar[3].expr)
|
||||
}
|
||||
case 45:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyDollar[1].args, yyDollar[3].expr)
|
||||
yyVAL.expr = NewBinaryExpr(yyDollar[1].expr, BinOpTypeConcat, yyDollar[3].expr)
|
||||
}
|
||||
case 47:
|
||||
yyDollar = yyS[yypt-2 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, nil)
|
||||
}
|
||||
case 48:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewFunction(yyDollar[1].node.val, yyDollar[2].args)
|
||||
}
|
||||
case 49:
|
||||
yyDollar = yyS[yypt-1 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyVAL.args, yyDollar[1].expr)
|
||||
}
|
||||
case 50:
|
||||
yyDollar = yyS[yypt-3 : yypt+1]
|
||||
{
|
||||
yyVAL.args = append(yyDollar[1].args, yyDollar[3].expr)
|
||||
}
|
||||
case 53:
|
||||
yyDollar = yyS[yypt-0 : yypt+1]
|
||||
{
|
||||
yyVAL.expr = NewEmptyExpr()
|
||||
|
@ -23,7 +23,7 @@ package formula
|
||||
%type <rows> constArrayRows
|
||||
%type <args> arguments constArrayCols
|
||||
|
||||
%token <node> tokenHorizontalRange tokenReservedName tokenDDECall tokenLexError tokenNamedRange
|
||||
%token <node> tokenHorizontalRange tokenVerticalRange tokenReservedName tokenDDECall tokenLexError tokenNamedRange
|
||||
%token <node> tokenBool tokenNumber tokenString tokenError tokenErrorRef tokenSheet tokenCell
|
||||
%token <node> tokenFunctionBuiltin
|
||||
|
||||
@ -78,18 +78,23 @@ constArrayCols:
|
||||
|
||||
reference:
|
||||
referenceItem
|
||||
| prefix referenceItem { $$ = NewPrefixExpr($1,$2)}
|
||||
| prefix referenceItem { $$ = NewPrefixExpr($1,$2)}
|
||||
| refFunctionCall;
|
||||
|
||||
prefix: tokenSheet { $$ = NewSheetPrefixExpr($1.val) };
|
||||
|
||||
referenceItem:
|
||||
tokenCell { $$ = NewCellRef($1.val)}
|
||||
| tokenNamedRange { $$ = NewNamedRangeRef($1.val)}
|
||||
;
|
||||
|
||||
refFunctionCall:
|
||||
referenceItem tokenColon referenceItem { $$ = NewRange($1,$3) };
|
||||
referenceItem tokenColon referenceItem { $$ = NewRange($1,$3) }
|
||||
| prefix referenceItem tokenColon referenceItem { $$ = NewPrefixRangeExpr($1,$2,$4)}
|
||||
| tokenNamedRange { $$ = NewNamedRangeRef($1.val)}
|
||||
| tokenHorizontalRange { $$ = NewHorizontalRange($1.val) }
|
||||
| tokenVerticalRange { $$ = NewVerticalRange($1.val) }
|
||||
| prefix tokenHorizontalRange { $$ = NewPrefixHorizontalRange($1,$2.val) }
|
||||
| prefix tokenVerticalRange { $$ = NewPrefixVerticalRange($1,$2.val) };
|
||||
|
||||
|
||||
binOp:
|
||||
|
58
spreadsheet/formula/horizontalrange.go
Normal file
58
spreadsheet/formula/horizontalrange.go
Normal file
@ -0,0 +1,58 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HorizontalRange is a range expression that when evaluated returns a list of Results from references like 1:4 (all cells from rows 1 to 4).
|
||||
type HorizontalRange struct {
|
||||
rowFrom, rowTo int
|
||||
}
|
||||
|
||||
// NewHorizontalRange constructs a new full rows range.
|
||||
func NewHorizontalRange(v string) Expression {
|
||||
sl := strings.Split(v, ":")
|
||||
if len(sl) != 2 {
|
||||
return nil
|
||||
}
|
||||
from, _ := strconv.Atoi(sl[0])
|
||||
to, _ := strconv.Atoi(sl[1])
|
||||
return HorizontalRange{from, to}
|
||||
}
|
||||
|
||||
// Eval evaluates a horizontal range returning a list of results or an error.
|
||||
func (r HorizontalRange) Eval(ctx Context, ev Evaluator) Result {
|
||||
key := r.horizontalRangeReference()
|
||||
if cached, found := ev.GetFromCache(key); found {
|
||||
return cached
|
||||
}
|
||||
from, to := cellRefsFromHorizontalRange(ctx, r.rowFrom, r.rowTo)
|
||||
res := resultFromCellRange(ctx, ev, from, to)
|
||||
ev.SetCache(key, res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r HorizontalRange) horizontalRangeReference() string {
|
||||
return fmt.Sprintf("%d:%d", r.rowFrom, r.rowTo)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a horizontal range.
|
||||
func (r HorizontalRange) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeHorizontalRange, Value: r.horizontalRangeReference()}
|
||||
}
|
||||
|
||||
func cellRefsFromHorizontalRange(ctx Context, rowFrom, rowTo int) (string, string) {
|
||||
from := "A" + strconv.Itoa(rowFrom)
|
||||
lastColumn := ctx.LastColumn(rowFrom, rowTo)
|
||||
to := lastColumn + strconv.Itoa(rowTo)
|
||||
return from, to
|
||||
}
|
@ -82,3 +82,13 @@ func (i *ivr) IsBool(cellRef string) bool {
|
||||
func (i *ivr) IsDBCS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// LastColumn returns empty string for the invalid reference context.
|
||||
func (i *ivr) LastColumn(rowFrom, rowTo int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// LastRow returns 0 for the invalid reference context.
|
||||
func (i *ivr) LastRow(colFrom string) int {
|
||||
return 0
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -32,19 +32,20 @@ import (
|
||||
fnName = 'TODO';
|
||||
file = 'TODO';
|
||||
horizontalRange = '$'? [0-9]+ ':' '$'? [0-9]+;
|
||||
verticalRange = '$'? [A-Z]+ ':' '$'? [A-Z]+;
|
||||
|
||||
# there is a function list at https://msdn.microsoft.com/en-us/library/dd906358(v=office.12).aspx
|
||||
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+ '!';
|
||||
quotedSheet = (sheetChar+ | squote (enclosedSheetChar | dquote)+ squote) '!';
|
||||
|
||||
namedRange = [A-Z_\\][A-Za-z0-9\\_.]+;
|
||||
namedRange = [A-Za-z_\\][A-Za-z0-9\\_.]+;
|
||||
|
||||
reservedName = '_xlnm.' [A-Z_]+;
|
||||
|
||||
@ -59,6 +60,7 @@ import (
|
||||
errorLiteral => {l.emit(tokenError, data[ts:te])};
|
||||
errorRef => {l.emit(tokenErrorRef, data[ts:te])};
|
||||
horizontalRange => {l.emit(tokenHorizontalRange, data[ts:te])};
|
||||
verticalRange => {l.emit(tokenVerticalRange, 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])};
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NamedRangeRef is a reference to a named range
|
||||
// NamedRangeRef is a reference to a named range.
|
||||
type NamedRangeRef struct {
|
||||
s string
|
||||
}
|
||||
@ -25,20 +25,31 @@ func NewNamedRangeRef(v string) Expression {
|
||||
// Eval evaluates and returns the result of the NamedRangeRef reference.
|
||||
func (n NamedRangeRef) Eval(ctx Context, ev Evaluator) Result {
|
||||
ref := ctx.NamedRange(n.s)
|
||||
switch ref.Type {
|
||||
case ReferenceTypeCell:
|
||||
return ev.Eval(ctx, ref.Value)
|
||||
case ReferenceTypeRange:
|
||||
// should look like "A2:C5"
|
||||
sp := strings.Split(ref.Value, ":")
|
||||
if len(sp) == 2 {
|
||||
return resultFromCellRange(ctx, ev, sp[0], sp[1])
|
||||
}
|
||||
return MakeErrorResult(fmt.Sprintf("unsuppported named range value %s", ref.Value))
|
||||
refValue := ref.Value
|
||||
if cached, found := ev.GetFromCache(refValue); found {
|
||||
return cached
|
||||
}
|
||||
return MakeErrorResult(fmt.Sprintf("unsuppported reference type %s", ref.Type))
|
||||
sl := strings.Split(refValue, "!")
|
||||
if len(sl) != 2 {
|
||||
return MakeErrorResult(fmt.Sprintf("unsupported named range value %s", refValue))
|
||||
}
|
||||
sheetCtx := ctx.Sheet(sl[0])
|
||||
sp := strings.Split(sl[1], ":")
|
||||
switch len(sp) {
|
||||
case 1:
|
||||
result := ev.Eval(sheetCtx, sp[0])
|
||||
ev.SetCache(refValue, result)
|
||||
return result
|
||||
case 2:
|
||||
// should look like "A2:C5"
|
||||
result := resultFromCellRange(sheetCtx, ev, sp[0], sp[1])
|
||||
ev.SetCache(refValue, result)
|
||||
return result
|
||||
}
|
||||
return MakeErrorResult(fmt.Sprintf("unsupported reference type %s", ref.Type))
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a named range.
|
||||
func (n NamedRangeRef) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeNamedRange, Value: n.s}
|
||||
}
|
||||
|
@ -7,14 +7,17 @@
|
||||
|
||||
package formula
|
||||
|
||||
// Negate is a negate expression like -A1.
|
||||
type Negate struct {
|
||||
e Expression
|
||||
}
|
||||
|
||||
// NewNegate constructs a new negate expression.
|
||||
func NewNegate(e Expression) Expression {
|
||||
return Negate{e}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of a Negate expression.
|
||||
func (n Negate) Eval(ctx Context, ev Evaluator) Result {
|
||||
r := n.e.Eval(ctx, ev)
|
||||
if r.Type == ResultTypeNumber {
|
||||
@ -23,6 +26,7 @@ func (n Negate) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult("NEGATE expected number argument")
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for Negate.
|
||||
func (n Negate) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
17
spreadsheet/formula/nocache.go
Normal file
17
spreadsheet/formula/nocache.go
Normal file
@ -0,0 +1,17 @@
|
||||
// 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
|
||||
|
||||
// noCache is a struct with collection of caching methods stubs intended for evaluators without cache.
|
||||
type noCache struct {}
|
||||
|
||||
func (nc *noCache) SetCache(key string, value Result) {}
|
||||
|
||||
func (nc *noCache) GetFromCache(key string) (Result, bool) {
|
||||
return empty, false
|
||||
}
|
@ -13,10 +13,12 @@ import (
|
||||
"github.com/unidoc/unioffice"
|
||||
)
|
||||
|
||||
// Number is a nubmer expression.
|
||||
type Number struct {
|
||||
v float64
|
||||
}
|
||||
|
||||
// NewNumber constructs a new number expression.
|
||||
func NewNumber(v string) Expression {
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
@ -25,10 +27,12 @@ func NewNumber(v string) Expression {
|
||||
return Number{f}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns a number.
|
||||
func (n Number) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeNumberResult(n.v)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for Number.
|
||||
func (n Number) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
@ -9,15 +9,18 @@ package formula
|
||||
|
||||
import "fmt"
|
||||
|
||||
// PrefixExpr is an expression containing reference to another sheet like Sheet1!A1 (the value of the cell A1 from sheet 'Sheet1').
|
||||
type PrefixExpr struct {
|
||||
pfx Expression
|
||||
exp Expression
|
||||
}
|
||||
|
||||
// NewPrefixExpr constructs an expression with prefix.
|
||||
func NewPrefixExpr(pfx, exp Expression) Expression {
|
||||
return &PrefixExpr{pfx, exp}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns an expression with prefix.
|
||||
func (p PrefixExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
ref := p.pfx.Reference(ctx, ev)
|
||||
switch ref.Type {
|
||||
@ -29,6 +32,12 @@ func (p PrefixExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
}
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to an expression with prefix.
|
||||
func (p PrefixExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
pfx := p.pfx.Reference(ctx, ev)
|
||||
exp := p.exp.Reference(ctx, ev)
|
||||
if pfx.Type == ReferenceTypeSheet && exp.Type == ReferenceTypeCell {
|
||||
return Reference{Type: ReferenceTypeCell, Value: pfx.Value + "!" + exp.Value}
|
||||
}
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
60
spreadsheet/formula/prefixhorizontalrange.go
Normal file
60
spreadsheet/formula/prefixhorizontalrange.go
Normal file
@ -0,0 +1,60 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PrefixHorizontalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!1:4 (all cells from rows 1 to 4 of sheet 'Sheet1').
|
||||
type PrefixHorizontalRange struct {
|
||||
pfx Expression
|
||||
rowFrom, rowTo int
|
||||
}
|
||||
|
||||
// NewPrefixHorizontalRange constructs a new full rows range with prefix.
|
||||
func NewPrefixHorizontalRange(pfx Expression, v string) Expression {
|
||||
sl := strings.Split(v, ":")
|
||||
if len(sl) != 2 {
|
||||
return nil
|
||||
}
|
||||
from, _ := strconv.Atoi(sl[0])
|
||||
to, _ := strconv.Atoi(sl[1])
|
||||
return PrefixHorizontalRange{pfx, from, to}
|
||||
}
|
||||
|
||||
// Eval evaluates a horizontal range with prefix returning a list of results or an error.
|
||||
func (r PrefixHorizontalRange) Eval(ctx Context, ev Evaluator) Result {
|
||||
pfx := r.pfx.Reference(ctx, ev)
|
||||
switch pfx.Type {
|
||||
case ReferenceTypeSheet:
|
||||
ref := r.horizontalRangeReference(pfx.Value)
|
||||
if cached, found := ev.GetFromCache(ref); found {
|
||||
return cached
|
||||
}
|
||||
c := ctx.Sheet(pfx.Value)
|
||||
from, to := cellRefsFromHorizontalRange(c, r.rowFrom, r.rowTo)
|
||||
result := resultFromCellRange(c, ev, from, to)
|
||||
ev.SetCache(ref, result)
|
||||
return result
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("no support for reference type %s", pfx.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (r PrefixHorizontalRange) horizontalRangeReference(sheetName string) string {
|
||||
return fmt.Sprintf("%s!%d:%d", sheetName, r.rowFrom, r.rowTo)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a horizontal range with prefix.
|
||||
func (r PrefixHorizontalRange) Reference(ctx Context, ev Evaluator) Reference {
|
||||
pfx := r.pfx.Reference(ctx, ev)
|
||||
return Reference{Type: ReferenceTypeHorizontalRange, Value: r.horizontalRangeReference(pfx.Value)}
|
||||
}
|
58
spreadsheet/formula/prefixrangeexpr.go
Normal file
58
spreadsheet/formula/prefixrangeexpr.go
Normal file
@ -0,0 +1,58 @@
|
||||
// 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"
|
||||
|
||||
// PrefixRangeExpr is a range expression that when evaluated returns a list of Results from a given sheet like Sheet1!A1:B4 (all cells from A1 to B4 from a sheet 'Sheet1').
|
||||
type PrefixRangeExpr struct {
|
||||
pfx, from, to Expression
|
||||
}
|
||||
|
||||
// NewPrefixRangeExpr constructs a new range with prefix.
|
||||
func NewPrefixRangeExpr(pfx, from, to Expression) Expression {
|
||||
return PrefixRangeExpr{pfx, from, to}
|
||||
}
|
||||
|
||||
// Eval evaluates a range with prefix returning a list of results or an error.
|
||||
func (p PrefixRangeExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
pfx := p.pfx.Reference(ctx, ev)
|
||||
from := p.from.Reference(ctx, ev)
|
||||
to := p.to.Reference(ctx, ev)
|
||||
switch pfx.Type {
|
||||
case ReferenceTypeSheet:
|
||||
ref := prefixRangeReference(pfx, from, to)
|
||||
if from.Type == ReferenceTypeCell && to.Type == ReferenceTypeCell {
|
||||
if cached, found := ev.GetFromCache(ref); found {
|
||||
return cached
|
||||
} else {
|
||||
result := resultFromCellRange(ctx.Sheet(pfx.Value), ev, from.Value, to.Value)
|
||||
ev.SetCache(ref, result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
return MakeErrorResult("invalid range " + ref)
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("no support for reference type %s", pfx.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func prefixRangeReference(pfx, from, to Reference) string {
|
||||
return fmt.Sprintf("%s!%s:%s", pfx.Value, from.Value, to.Value)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a range with prefix.
|
||||
func (p PrefixRangeExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
pfx := p.pfx.Reference(ctx, ev)
|
||||
from := p.from.Reference(ctx, ev)
|
||||
to := p.to.Reference(ctx, ev)
|
||||
if pfx.Type == ReferenceTypeSheet && from.Type == ReferenceTypeCell && to.Type == ReferenceTypeCell {
|
||||
return MakeRangeReference(prefixRangeReference(pfx, from, to))
|
||||
}
|
||||
return ReferenceInvalid
|
||||
}
|
57
spreadsheet/formula/prefixverticalrange.go
Normal file
57
spreadsheet/formula/prefixverticalrange.go
Normal file
@ -0,0 +1,57 @@
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PrefixVerticalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!AA:IJ (all cells from columns AA to IJ of sheet 'Sheet1').
|
||||
type PrefixVerticalRange struct {
|
||||
pfx Expression
|
||||
colFrom, colTo string
|
||||
}
|
||||
|
||||
// NewPrefixVerticalRange constructs a new full columns range with prefix.
|
||||
func NewPrefixVerticalRange(pfx Expression, v string) Expression {
|
||||
sl := strings.Split(v, ":")
|
||||
if len(sl) != 2 {
|
||||
return nil
|
||||
}
|
||||
return PrefixVerticalRange{pfx, sl[0], sl[1]}
|
||||
}
|
||||
|
||||
// Eval evaluates a vertical range with prefix returning a list of results or an error.
|
||||
func (r PrefixVerticalRange) Eval(ctx Context, ev Evaluator) Result {
|
||||
pfx := r.pfx.Reference(ctx, ev)
|
||||
switch pfx.Type {
|
||||
case ReferenceTypeSheet:
|
||||
ref := r.verticalRangeReference(pfx.Value)
|
||||
if cached, found := ev.GetFromCache(ref); found {
|
||||
return cached
|
||||
}
|
||||
c := ctx.Sheet(pfx.Value)
|
||||
from, to := cellRefsFromVerticalRange(c, r.colFrom, r.colTo)
|
||||
result := resultFromCellRange(c, ev, from, to)
|
||||
ev.SetCache(ref, result)
|
||||
return result
|
||||
default:
|
||||
return MakeErrorResult(fmt.Sprintf("no support for reference type %s", pfx.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (r PrefixVerticalRange) verticalRangeReference(sheetName string) string {
|
||||
return fmt.Sprintf("%s!%s:%s", sheetName, r.colFrom, r.colTo)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a vertical range with prefix.
|
||||
func (r PrefixVerticalRange) Reference(ctx Context, ev Evaluator) Reference {
|
||||
pfx := r.pfx.Reference(ctx, ev)
|
||||
return Reference{Type: ReferenceTypeVerticalRange, Value: r.verticalRangeReference(pfx.Value)}
|
||||
}
|
@ -9,8 +9,6 @@ package formula
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/unidoc/unioffice/spreadsheet/reference"
|
||||
)
|
||||
@ -25,65 +23,54 @@ func NewRange(from, to Expression) Expression {
|
||||
return Range{from, to}
|
||||
}
|
||||
|
||||
// Eval evaluates the range returning a list of results or an error.
|
||||
// Eval evaluates a range returning a list of results or an error.
|
||||
func (r Range) Eval(ctx Context, ev Evaluator) Result {
|
||||
from := r.from.Reference(ctx, ev)
|
||||
to := r.to.Reference(ctx, ev)
|
||||
ref := rangeReference(from, to)
|
||||
if from.Type == ReferenceTypeCell && to.Type == ReferenceTypeCell {
|
||||
return resultFromCellRange(ctx, ev, from.Value, to.Value)
|
||||
if cached, found := ev.GetFromCache(ref); found {
|
||||
return cached
|
||||
} else {
|
||||
result := resultFromCellRange(ctx, ev, from.Value, to.Value)
|
||||
ev.SetCache(ref, result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
return MakeErrorResult("invalid range " + from.Value + " to " + to.Value)
|
||||
return MakeErrorResult("invalid range " + ref)
|
||||
}
|
||||
|
||||
func rangeReference(from, to Reference) string {
|
||||
return fmt.Sprintf("%s:%s", from.Value, to.Value)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a range.
|
||||
func (r Range) Reference(ctx Context, ev Evaluator) Reference {
|
||||
from := r.from.Reference(ctx, ev)
|
||||
to := r.to.Reference(ctx, ev)
|
||||
if from.Type == ReferenceTypeCell && to.Type == ReferenceTypeCell {
|
||||
return MakeRangeReference(from.Value + ":" + to.Value)
|
||||
return MakeRangeReference(rangeReference(from, to))
|
||||
}
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
||||
// TODO: move these somewhere to remove duplication
|
||||
func ParseCellReference(s string) (col string, row uint32, err error) {
|
||||
s = strings.Replace(s, "$", "", -1)
|
||||
split := -1
|
||||
lfor:
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch {
|
||||
case s[i] >= '0' && s[i] <= '9':
|
||||
split = i
|
||||
break lfor
|
||||
}
|
||||
}
|
||||
switch split {
|
||||
case 0:
|
||||
return col, row, fmt.Errorf("no letter prefix in %s", s)
|
||||
case -1:
|
||||
return col, row, fmt.Errorf("no digits in %s", s)
|
||||
}
|
||||
|
||||
col = s[0:split]
|
||||
r64, err := strconv.ParseUint(s[split:], 10, 32)
|
||||
row = uint32(r64)
|
||||
return col, row, err
|
||||
}
|
||||
|
||||
func resultFromCellRange(ctx Context, ev Evaluator, from, to string) Result {
|
||||
fc, fr, fe := ParseCellReference(from)
|
||||
tc, tr, te := ParseCellReference(to)
|
||||
fromRef, fe := reference.ParseCellReference(from)
|
||||
if fe != nil {
|
||||
return MakeErrorResult("unable to parse range " + from)
|
||||
return MakeErrorResult(fmt.Sprintf("unable to parse range %s: error %s", from, fe.Error()))
|
||||
}
|
||||
fc, fr := fromRef.ColumnIdx, fromRef.RowIdx
|
||||
|
||||
toRef, te := reference.ParseCellReference(to)
|
||||
if te != nil {
|
||||
return MakeErrorResult("unable to parse range " + to)
|
||||
return MakeErrorResult(fmt.Sprintf("unable to parse range %s: error %s", to, te.Error()))
|
||||
}
|
||||
bc := reference.ColumnToIndex(fc)
|
||||
ec := reference.ColumnToIndex(tc)
|
||||
tc, tr := toRef.ColumnIdx, toRef.RowIdx
|
||||
|
||||
arr := [][]Result{}
|
||||
for r := fr; r <= tr; r++ {
|
||||
args := []Result{}
|
||||
for c := bc; c <= ec; c++ {
|
||||
for c := fc; c <= tc; c++ {
|
||||
res := ctx.Cell(fmt.Sprintf("%s%d", reference.IndexToColumn(c), r), ev)
|
||||
args = append(args, res)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ type ReferenceType byte
|
||||
const (
|
||||
ReferenceTypeInvalid ReferenceType = iota
|
||||
ReferenceTypeCell
|
||||
ReferenceTypeHorizontalRange
|
||||
ReferenceTypeVerticalRange
|
||||
ReferenceTypeNamedRange
|
||||
ReferenceTypeRange
|
||||
ReferenceTypeSheet
|
||||
|
@ -7,18 +7,22 @@
|
||||
|
||||
package formula
|
||||
|
||||
// SheetPrefixExpr is a reference to a sheet like Sheet1! (reference to sheet 'Sheet1').
|
||||
type SheetPrefixExpr struct {
|
||||
sheet string
|
||||
}
|
||||
|
||||
// NewSheetPrefixExpr constructs a new prefix expression.
|
||||
func NewSheetPrefixExpr(s string) Expression {
|
||||
return &SheetPrefixExpr{s}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns the result of a sheet expression.
|
||||
func (s SheetPrefixExpr) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeErrorResult("sheet prefix should never be evaluated")
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a sheet.
|
||||
func (s SheetPrefixExpr) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeSheet, Value: s.sheet}
|
||||
}
|
||||
|
@ -9,20 +9,24 @@ package formula
|
||||
|
||||
import "strings"
|
||||
|
||||
// String is a string expression.
|
||||
type String struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// NewString constructs a new string expression.
|
||||
func NewString(v string) Expression {
|
||||
// Excel escapes quotes within a string by repeating them
|
||||
v = strings.Replace(v, `""`, `"`, -1)
|
||||
return String{v}
|
||||
}
|
||||
|
||||
// Eval evaluates and returns a string.
|
||||
func (s String) Eval(ctx Context, ev Evaluator) Result {
|
||||
return MakeStringResult(s.s)
|
||||
}
|
||||
|
||||
// Reference returns an invalid reference for String.
|
||||
func (s String) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return ReferenceInvalid
|
||||
}
|
||||
|
56
spreadsheet/formula/verticalrange.go
Normal file
56
spreadsheet/formula/verticalrange.go
Normal file
@ -0,0 +1,56 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VerticalRange is a range expression that when evaluated returns a list of Results from references like AA:IJ (all cells from columns AA to IJ).
|
||||
type VerticalRange struct {
|
||||
colFrom, colTo string
|
||||
}
|
||||
|
||||
// NewVerticalRange constructs a new full columns range.
|
||||
func NewVerticalRange(v string) Expression {
|
||||
sl := strings.Split(v, ":")
|
||||
if len(sl) != 2 {
|
||||
return nil
|
||||
}
|
||||
return VerticalRange{sl[0], sl[1]}
|
||||
}
|
||||
|
||||
// Eval evaluates a vertical range returning a list of results or an error.
|
||||
func (r VerticalRange) Eval(ctx Context, ev Evaluator) Result {
|
||||
key := r.verticalRangeReference()
|
||||
if cached, found := ev.GetFromCache(key); found {
|
||||
return cached
|
||||
}
|
||||
from, to := cellRefsFromVerticalRange(ctx, r.colFrom, r.colTo)
|
||||
res := resultFromCellRange(ctx, ev, from, to)
|
||||
ev.SetCache(key, res)
|
||||
return res
|
||||
}
|
||||
|
||||
func (r VerticalRange) verticalRangeReference() string {
|
||||
return fmt.Sprintf("%s:%s", r.colFrom, r.colTo)
|
||||
}
|
||||
|
||||
// Reference returns a string reference value to a vertical range.
|
||||
func (r VerticalRange) Reference(ctx Context, ev Evaluator) Reference {
|
||||
return Reference{Type: ReferenceTypeVerticalRange, Value: r.verticalRangeReference()}
|
||||
}
|
||||
|
||||
func cellRefsFromVerticalRange(ctx Context, colFrom, colTo string) (string, string) {
|
||||
from := colFrom + "1"
|
||||
lastRow := ctx.LastRow(colFrom)
|
||||
to := colTo + strconv.Itoa(lastRow)
|
||||
return from, to
|
||||
}
|
@ -40,8 +40,8 @@ func New() *Workbook {
|
||||
wb.ContentTypes.AddOverride(unioffice.AbsoluteFilename(unioffice.DocTypeSpreadsheet, unioffice.StylesType, 0), unioffice.SMLStyleSheetContentType)
|
||||
|
||||
wb.SharedStrings = NewSharedStrings()
|
||||
wb.ContentTypes.AddOverride(unioffice.AbsoluteFilename(unioffice.DocTypeSpreadsheet, unioffice.SharedStingsType, 0), unioffice.SharedStringsContentType)
|
||||
wb.wbRels.AddRelationship(unioffice.RelativeFilename(unioffice.DocTypeSpreadsheet, unioffice.OfficeDocumentType, unioffice.SharedStingsType, 0), unioffice.SharedStingsType)
|
||||
wb.ContentTypes.AddOverride(unioffice.AbsoluteFilename(unioffice.DocTypeSpreadsheet, unioffice.SharedStringsType, 0), unioffice.SharedStringsContentType)
|
||||
wb.wbRels.AddRelationship(unioffice.RelativeFilename(unioffice.DocTypeSpreadsheet, unioffice.OfficeDocumentType, unioffice.SharedStringsType, 0), unioffice.SharedStringsType)
|
||||
|
||||
return wb
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ type CellReference struct {
|
||||
Column string
|
||||
AbsoluteColumn bool
|
||||
AbsoluteRow bool
|
||||
SheetName string
|
||||
}
|
||||
|
||||
func (c CellReference) String() string {
|
||||
@ -44,8 +45,13 @@ func ParseCellReference(s string) (CellReference, error) {
|
||||
if len(s) < 2 {
|
||||
return CellReference{}, errors.New("cell reference must have at least two characters")
|
||||
}
|
||||
r := CellReference{}
|
||||
|
||||
r := CellReference{}
|
||||
sl := strings.Split(s, "!")
|
||||
if len(sl) == 2 {
|
||||
r.SheetName = sl[0]
|
||||
s = sl[1]
|
||||
}
|
||||
// check for absolute column
|
||||
if s[0] == '$' {
|
||||
r.AbsoluteColumn = true
|
||||
|
@ -22,7 +22,7 @@ func ColumnToIndex(col string) uint32 {
|
||||
return res - 1
|
||||
}
|
||||
|
||||
// IndexToColumn maps a column number to a coumn name (e.g. 0 = A, 1 = B, 26 = AA)
|
||||
// IndexToColumn maps a column number to a column name (e.g. 0 = A, 1 = B, 26 = AA)
|
||||
func IndexToColumn(col uint32) string {
|
||||
var a [64 + 1]byte
|
||||
i := len(a)
|
||||
|
@ -15,11 +15,21 @@ import (
|
||||
// ParseRangeReference splits a range reference of the form "A1:B5" into its
|
||||
// components.
|
||||
func ParseRangeReference(s string) (from, to CellReference, err error) {
|
||||
sheetName := ""
|
||||
sp0 := strings.Split(s, "!")
|
||||
if len(sp0) == 2 {
|
||||
sheetName = sp0[0]
|
||||
s = sp0[1]
|
||||
}
|
||||
sp := strings.Split(s, ":")
|
||||
if len(sp) != 2 {
|
||||
return CellReference{}, CellReference{}, errors.New("invalid range format")
|
||||
}
|
||||
|
||||
if sheetName != "" {
|
||||
sp[0] = sheetName + "!" + sp[0]
|
||||
sp[1] = sheetName + "!" + sp[1]
|
||||
}
|
||||
fromRef, err := ParseCellReference(sp[0])
|
||||
if err != nil {
|
||||
return CellReference{}, CellReference{}, err
|
||||
|
@ -648,7 +648,6 @@ func (s *Sheet) RecalculateFormulas() {
|
||||
if c.X().F.TAttr == sml.ST_CellFormulaTypeShared && len(formStr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
res := ev.Eval(ctx, formStr).AsString()
|
||||
if res.Type == formula.ResultTypeError {
|
||||
unioffice.Log("error evaulating formula %s: %s", formStr, res.ErrorMessage)
|
||||
@ -663,8 +662,12 @@ func (s *Sheet) RecalculateFormulas() {
|
||||
|
||||
// the formula is of type array, so if the result is also an
|
||||
// array we need to expand the array out into cells
|
||||
if c.X().F.TAttr == sml.ST_CellFormulaTypeArray && res.Type == formula.ResultTypeArray {
|
||||
s.setArray(c.Reference(), res)
|
||||
if c.X().F.TAttr == sml.ST_CellFormulaTypeArray {
|
||||
if res.Type == formula.ResultTypeArray {
|
||||
s.setArray(c.Reference(), res)
|
||||
} else if res.Type == formula.ResultTypeList {
|
||||
s.setList(c.Reference(), res)
|
||||
}
|
||||
} else if c.X().F.TAttr == sml.ST_CellFormulaTypeShared && c.X().F.RefAttr != nil {
|
||||
from, to, err := reference.ParseRangeReference(*c.X().F.RefAttr)
|
||||
if err != nil {
|
||||
@ -720,6 +723,32 @@ func (s *Sheet) setArray(origin string, arr formula.Result) error {
|
||||
for ic, val := range row {
|
||||
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
||||
if val.Type != formula.ResultTypeEmpty {
|
||||
if val.IsBoolean {
|
||||
cell.SetBool(val.ValueNumber != 0)
|
||||
} else {
|
||||
cell.SetCachedFormulaResult(val.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setList expands an array into cached values starting at the origin which
|
||||
// should be a cell reference of the type "A1". This is used when evaluating
|
||||
// list type formulas.
|
||||
func (s *Sheet) setList(origin string, list formula.Result) error {
|
||||
cref, err := reference.ParseCellReference(origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sr := s.Row(cref.RowIdx)
|
||||
for ic, val := range list.ValueList {
|
||||
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
||||
if val.Type != formula.ResultTypeEmpty {
|
||||
if val.IsBoolean {
|
||||
cell.SetBool(val.ValueNumber != 0)
|
||||
} else {
|
||||
cell.SetCachedFormulaResult(val.String())
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,33 @@ func (wb *Workbook) AddSheet() Sheet {
|
||||
return Sheet{wb, rs, ws}
|
||||
}
|
||||
|
||||
var sstAbsTargetAttr = unioffice.AbsoluteFilename(unioffice.DocTypeSpreadsheet, unioffice.SharedStringsType, 0)
|
||||
var sstRelTargetAttr = unioffice.RelativeFilename(unioffice.DocTypeSpreadsheet, unioffice.OfficeDocumentType, unioffice.SharedStringsType, 0)
|
||||
|
||||
// ensureSharedStringsRelationships checks if relationships and content types related to shared strings are already exist and add them in the case of absence.
|
||||
func (wb *Workbook) ensureSharedStringsRelationships() {
|
||||
foundSharedStringContentType := false
|
||||
for _, o := range wb.ContentTypes.X().Override {
|
||||
if o.ContentTypeAttr == unioffice.SharedStringsContentType {
|
||||
foundSharedStringContentType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundSharedStringContentType {
|
||||
wb.ContentTypes.AddOverride(sstAbsTargetAttr, unioffice.SharedStringsContentType)
|
||||
}
|
||||
foundSharedStringRel := false
|
||||
for _, rel := range wb.wbRels.Relationships() {
|
||||
if rel.X().TargetAttr == sstRelTargetAttr {
|
||||
foundSharedStringRel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundSharedStringRel {
|
||||
wb.wbRels.AddRelationship(sstRelTargetAttr, unioffice.SharedStringsType)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSheet removes the sheet with the given index from the workbook.
|
||||
func (wb *Workbook) RemoveSheet(ind int) error {
|
||||
if wb.SheetCount() <= ind {
|
||||
@ -314,7 +341,7 @@ func (wb *Workbook) Save(w io.Writer) error {
|
||||
zippkg.MarshalXML(z, fn, sheet)
|
||||
zippkg.MarshalXML(z, zippkg.RelationsPathFor(fn), wb.xwsRels[i].X())
|
||||
}
|
||||
if err := zippkg.MarshalXMLByType(z, dt, unioffice.SharedStingsType, wb.SharedStrings.X()); err != nil {
|
||||
if err := zippkg.MarshalXMLByType(z, dt, unioffice.SharedStringsType, wb.SharedStrings.X()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -470,7 +497,7 @@ func (wb *Workbook) onNewRelationship(decMap *zippkg.DecodeMap, target, typ stri
|
||||
decMap.AddTarget(target, thm, typ, 0)
|
||||
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, len(wb.themes))
|
||||
|
||||
case unioffice.SharedStingsType:
|
||||
case unioffice.SharedStringsType:
|
||||
wb.SharedStrings = NewSharedStrings()
|
||||
decMap.AddTarget(target, wb.SharedStrings.X(), typ, 0)
|
||||
rel.TargetAttr = unioffice.RelativeFilename(dt, src.Typ, typ, 0)
|
||||
|
@ -78,8 +78,8 @@ func Decode(f *zip.File, dest interface{}) error {
|
||||
// SML
|
||||
case unioffice.WorksheetTypeStrict:
|
||||
ds.Relationship[i].TypeAttr = unioffice.WorksheetType
|
||||
case unioffice.SharedStingsTypeStrict:
|
||||
ds.Relationship[i].TypeAttr = unioffice.SharedStingsType
|
||||
case unioffice.SharedStringsTypeStrict:
|
||||
ds.Relationship[i].TypeAttr = unioffice.SharedStringsType
|
||||
case unioffice.TableTypeStrict:
|
||||
ds.Relationship[i].TypeAttr = unioffice.TableType
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user