From 1efa2838875efe06a9a4b7b1d582f70205de3aa5 Mon Sep 17 00:00:00 2001 From: xuri Date: Tue, 1 Mar 2022 00:10:59 +0800 Subject: [PATCH] ref #65, new formula functions FORMULATEXT and TYPE --- calc.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++------ calc_test.go | 29 +++++++++++++++++++++-- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/calc.go b/calc.go index 8358d33..0f44912 100644 --- a/calc.go +++ b/calc.go @@ -416,6 +416,7 @@ type formulaFuncs struct { // FLOOR // FLOOR.MATH // FLOOR.PRECISE +// FORMULATEXT // FV // FVSCHEDULE // GAMMA @@ -615,6 +616,7 @@ type formulaFuncs struct { // TRIMMEAN // TRUE // TRUNC +// TYPE // UNICHAR // UNICODE // UPPER @@ -6874,7 +6876,7 @@ func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorVALUE, "ISERR requires 1 argument") } token := argsList.Front().Value.(formulaArg) - result := "FALSE" + result := false if token.Type == ArgError { for _, errType := range []string{ formulaErrorDIV, formulaErrorNAME, formulaErrorNUM, @@ -6882,11 +6884,11 @@ func (fn *formulaFuncs) ISERR(argsList *list.List) formulaArg { formulaErrorSPILL, formulaErrorCALC, formulaErrorGETTINGDATA, } { if errType == token.String { - result = "TRUE" + result = true } } } - return newStringFormulaArg(result) + return newBoolFormulaArg(result) } // ISERROR function tests if an initial supplied expression (or value) returns @@ -6900,7 +6902,7 @@ func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorVALUE, "ISERROR requires 1 argument") } token := argsList.Front().Value.(formulaArg) - result := "FALSE" + result := false if token.Type == ArgError { for _, errType := range []string{ formulaErrorDIV, formulaErrorNAME, formulaErrorNA, formulaErrorNUM, @@ -6908,11 +6910,11 @@ func (fn *formulaFuncs) ISERROR(argsList *list.List) formulaArg { formulaErrorCALC, formulaErrorGETTINGDATA, } { if errType == token.String { - result = "TRUE" + result = true } } } - return newStringFormulaArg(result) + return newBoolFormulaArg(result) } // ISEVEN function tests if a supplied number (or numeric expression) @@ -7190,6 +7192,32 @@ func (fn *formulaFuncs) SHEETS(argsList *list.List) formulaArg { return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } +// TYPE function returns an integer that represents the value's data type. The +// syntax of the function is: +// +// TYPE(value) +// +func (fn *formulaFuncs) TYPE(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "TYPE requires 1 argument") + } + token := argsList.Front().Value.(formulaArg) + switch token.Type { + case ArgError: + return newNumberFormulaArg(16) + case ArgMatrix: + return newNumberFormulaArg(64) + default: + if arg := token.ToNumber(); arg.Type != ArgError || len(token.Value()) == 0 { + return newNumberFormulaArg(1) + } + if arg := token.ToBool(); arg.Type != ArgError { + return newNumberFormulaArg(4) + } + return newNumberFormulaArg(2) + } +} + // T function tests if a supplied value is text and if so, returns the // supplied text; Otherwise, the function returns an empty text string. The // syntax of the function is: @@ -7343,7 +7371,6 @@ func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg { case ArgNumber: return newBoolFormulaArg(!(token.Number != 0)) case ArgError: - return token } return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument") @@ -9415,6 +9442,32 @@ func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg { return newNumberFormulaArg(float64(result)) } +// FORMULATEXT function returns a formula as a text string. The syntax of the +// function is: +// +// FORMULATEXT(reference) +// +func (fn *formulaFuncs) FORMULATEXT(argsList *list.List) formulaArg { + if argsList.Len() != 1 { + return newErrorFormulaArg(formulaErrorVALUE, "FORMULATEXT requires 1 argument") + } + refs := argsList.Front().Value.(formulaArg).cellRefs + col, row := 0, 0 + if refs != nil && refs.Len() > 0 { + col, row = refs.Front().Value.(cellRef).Col, refs.Front().Value.(cellRef).Row + } + ranges := argsList.Front().Value.(formulaArg).cellRanges + if ranges != nil && ranges.Len() > 0 { + col, row = ranges.Front().Value.(cellRange).From.Col, ranges.Front().Value.(cellRange).From.Row + } + cell, err := CoordinatesToCellName(col, row) + if err != nil { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + formula, _ := fn.f.GetCellFormula(fn.sheet, cell) + return newStringFormulaArg(formula) +} + // checkHVLookupArgs checking arguments, prepare extract mode, lookup value, // and data for the formula functions HLOOKUP and VLOOKUP. func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue, tableArray, matchMode, errArg formulaArg) { diff --git a/calc_test.go b/calc_test.go index e620eb3..d01f31b 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1005,8 +1005,9 @@ func TestCalcCellValue(t *testing.T) { "=ISERR(NA())": "FALSE", "=ISERR(POWER(0,-1)))": "TRUE", // ISERROR - "=ISERROR(A1)": "FALSE", - "=ISERROR(NA())": "TRUE", + "=ISERROR(A1)": "FALSE", + "=ISERROR(NA())": "TRUE", + "=ISERROR(\"#VALUE!\")": "FALSE", // ISEVEN "=ISEVEN(A1)": "FALSE", "=ISEVEN(A2)": "TRUE", @@ -1055,6 +1056,14 @@ func TestCalcCellValue(t *testing.T) { // SHEETS "=SHEETS()": "1", "=SHEETS(A1)": "1", + // TYPE + "=TYPE(2)": "1", + "=TYPE(10/2)": "1", + "=TYPE(C1)": "1", + "=TYPE(\"text\")": "2", + "=TYPE(TRUE)": "4", + "=TYPE(NA())": "16", + "=TYPE(MUNIT(2))": "64", // T "=T(\"text\")": "text", "=T(N(10))": "", @@ -2536,6 +2545,8 @@ func TestCalcCellValue(t *testing.T) { // SHEETS "=SHEETS(\"\",\"\")": "SHEETS accepts at most 1 argument", "=SHEETS(\"Sheet1\")": "#N/A", + // TYPE + "=TYPE()": "TYPE requires 1 argument", // T "=T()": "T requires 1 argument", "=T(NA())": "#N/A", @@ -2839,6 +2850,9 @@ func TestCalcCellValue(t *testing.T) { "=COLUMNS(Sheet1)": newInvalidColumnNameError("Sheet1").Error(), "=COLUMNS(Sheet1!A1!B1)": newInvalidColumnNameError("Sheet1").Error(), "=COLUMNS(Sheet1!Sheet1)": newInvalidColumnNameError("Sheet1").Error(), + // FORMULATEXT + "=FORMULATEXT()": "FORMULATEXT requires 1 argument", + "=FORMULATEXT(1)": "#VALUE!", // HLOOKUP "=HLOOKUP()": "HLOOKUP requires at least 3 arguments", "=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array", @@ -3693,6 +3707,17 @@ func TestCalcAVERAGEIF(t *testing.T) { } } +func TestCalcFORMULATEXT(t *testing.T) { + f, formulaText := NewFile(), "=SUM(B1:C1)" + assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formulaText)) + for _, formula := range []string{"=FORMULATEXT(A1)", "=FORMULATEXT(A:A)", "=FORMULATEXT(A1:B1)"} { + assert.NoError(t, f.SetCellFormula("Sheet1", "D1", formula), formula) + result, err := f.CalcCellValue("Sheet1", "D1") + assert.NoError(t, err, formula) + assert.Equal(t, formulaText, result, formula) + } +} + func TestCalcHLOOKUP(t *testing.T) { cellData := [][]interface{}{ {"Example Result Table"}, -- GitLab