forked from p30928647/excelize
Improves the calculation engine, docs update, and adds the dependabot
- Initialize array formula support for the formula calculation engine - Update example and unit test of `AddPivotTable` - Update the supported hash algorithm of ProtectSheet
This commit is contained in:
parent
afb2d27c90
commit
1c167b96a3
|
@ -0,0 +1,10 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
40
calc.go
40
calc.go
|
@ -829,6 +829,8 @@ func newEmptyFormulaArg() formulaArg {
|
|||
func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) {
|
||||
var err error
|
||||
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
|
||||
var inArray, inArrayRow bool
|
||||
var arrayRow []formulaArg
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
token := tokens[i]
|
||||
|
||||
|
@ -841,6 +843,14 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
|
|||
|
||||
// function start
|
||||
if isFunctionStartToken(token) {
|
||||
if token.TValue == "ARRAY" {
|
||||
inArray = true
|
||||
continue
|
||||
}
|
||||
if token.TValue == "ARRAYROW" {
|
||||
inArrayRow = true
|
||||
continue
|
||||
}
|
||||
opfStack.Push(token)
|
||||
argsStack.Push(list.New().Init())
|
||||
opftStack.Push(token) // to know which operators belong to a function use the function as a separator
|
||||
|
@ -922,7 +932,19 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
|
|||
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical {
|
||||
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
|
||||
}
|
||||
|
||||
if inArrayRow && isOperand(token) {
|
||||
arrayRow = append(arrayRow, tokenToFormulaArg(token))
|
||||
continue
|
||||
}
|
||||
if inArrayRow && isFunctionStopToken(token) {
|
||||
inArrayRow = false
|
||||
continue
|
||||
}
|
||||
if inArray && isFunctionStopToken(token) {
|
||||
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop())
|
||||
arrayRow, inArray = []formulaArg{}, false
|
||||
continue
|
||||
}
|
||||
if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
||||
return newEmptyFormulaArg(), err
|
||||
}
|
||||
|
@ -1274,6 +1296,15 @@ func isOperand(token efp.Token) bool {
|
|||
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText)
|
||||
}
|
||||
|
||||
// tokenToFormulaArg create a formula argument by given token.
|
||||
func tokenToFormulaArg(token efp.Token) formulaArg {
|
||||
if token.TSubType == efp.TokenSubTypeNumber {
|
||||
num, _ := strconv.ParseFloat(token.TValue, 64)
|
||||
return newNumberFormulaArg(num)
|
||||
}
|
||||
return newStringFormulaArg(token.TValue)
|
||||
}
|
||||
|
||||
// parseToken parse basic arithmetic operator priority and evaluate based on
|
||||
// operators and operands.
|
||||
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error {
|
||||
|
@ -1318,12 +1349,7 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
|
|||
}
|
||||
// opd
|
||||
if isOperand(token) {
|
||||
if token.TSubType == efp.TokenSubTypeNumber {
|
||||
num, _ := strconv.ParseFloat(token.TValue, 64)
|
||||
opdStack.Push(newNumberFormulaArg(num))
|
||||
} else {
|
||||
opdStack.Push(newStringFormulaArg(token.TValue))
|
||||
}
|
||||
opdStack.Push(tokenToFormulaArg(token))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
73
calc_test.go
73
calc_test.go
|
@ -60,55 +60,88 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=1&2": "12",
|
||||
"=15%": "0.15",
|
||||
"=1+20%": "1.2",
|
||||
"={1}+2": "3",
|
||||
"=1+{2}": "3",
|
||||
"={1}+{2}": "3",
|
||||
`="A"="A"`: "TRUE",
|
||||
`="A"<>"A"`: "FALSE",
|
||||
// Engineering Functions
|
||||
// BESSELI
|
||||
"=BESSELI(4.5,1)": "15.3892227537359",
|
||||
"=BESSELI(32,1)": "5502845511211.25",
|
||||
"=BESSELI(4.5,1)": "15.3892227537359",
|
||||
"=BESSELI(32,1)": "5502845511211.25",
|
||||
"=BESSELI({32},1)": "5502845511211.25",
|
||||
"=BESSELI(32,{1})": "5502845511211.25",
|
||||
"=BESSELI({32},{1})": "5502845511211.25",
|
||||
// BESSELJ
|
||||
"=BESSELJ(1.9,2)": "0.329925727692387",
|
||||
"=BESSELJ(1.9,2)": "0.329925727692387",
|
||||
"=BESSELJ({1.9},2)": "0.329925727692387",
|
||||
"=BESSELJ(1.9,{2})": "0.329925727692387",
|
||||
"=BESSELJ({1.9},{2})": "0.329925727692387",
|
||||
// BESSELK
|
||||
"=BESSELK(0.05,0)": "3.11423403428966",
|
||||
"=BESSELK(0.05,1)": "19.9096743272486",
|
||||
"=BESSELK(0.05,2)": "799.501207124235",
|
||||
"=BESSELK(3,2)": "0.0615104585619118",
|
||||
"=BESSELK(0.05,0)": "3.11423403428966",
|
||||
"=BESSELK(0.05,1)": "19.9096743272486",
|
||||
"=BESSELK(0.05,2)": "799.501207124235",
|
||||
"=BESSELK(3,2)": "0.0615104585619118",
|
||||
"=BESSELK({3},2)": "0.0615104585619118",
|
||||
"=BESSELK(3,{2})": "0.0615104585619118",
|
||||
"=BESSELK({3},{2})": "0.0615104585619118",
|
||||
// BESSELY
|
||||
"=BESSELY(0.05,0)": "-1.97931100684153",
|
||||
"=BESSELY(0.05,1)": "-12.789855163794",
|
||||
"=BESSELY(0.05,2)": "-509.61489554492",
|
||||
"=BESSELY(9,2)": "-0.229082087487741",
|
||||
"=BESSELY(0.05,0)": "-1.97931100684153",
|
||||
"=BESSELY(0.05,1)": "-12.789855163794",
|
||||
"=BESSELY(0.05,2)": "-509.61489554492",
|
||||
"=BESSELY(9,2)": "-0.229082087487741",
|
||||
"=BESSELY({9},2)": "-0.229082087487741",
|
||||
"=BESSELY(9,{2})": "-0.229082087487741",
|
||||
"=BESSELY({9},{2})": "-0.229082087487741",
|
||||
// BIN2DEC
|
||||
"=BIN2DEC(\"10\")": "2",
|
||||
"=BIN2DEC(\"11\")": "3",
|
||||
"=BIN2DEC(\"0000000010\")": "2",
|
||||
"=BIN2DEC(\"1111111110\")": "-2",
|
||||
"=BIN2DEC(\"110\")": "6",
|
||||
"=BIN2DEC({\"110\"})": "6",
|
||||
// BIN2HEX
|
||||
"=BIN2HEX(\"10\")": "2",
|
||||
"=BIN2HEX(\"0000000001\")": "1",
|
||||
"=BIN2HEX(\"10\",10)": "0000000002",
|
||||
"=BIN2HEX(\"1111111110\")": "FFFFFFFFFE",
|
||||
"=BIN2HEX(\"11101\")": "1D",
|
||||
"=BIN2HEX({\"11101\"})": "1D",
|
||||
// BIN2OCT
|
||||
"=BIN2OCT(\"101\")": "5",
|
||||
"=BIN2OCT(\"0000000001\")": "1",
|
||||
"=BIN2OCT(\"10\",10)": "0000000002",
|
||||
"=BIN2OCT(\"1111111110\")": "7777777776",
|
||||
"=BIN2OCT(\"1110\")": "16",
|
||||
"=BIN2OCT({\"1110\"})": "16",
|
||||
// BITAND
|
||||
"=BITAND(13,14)": "12",
|
||||
"=BITAND(13,14)": "12",
|
||||
"=BITAND({13},14)": "12",
|
||||
"=BITAND(13,{14})": "12",
|
||||
"=BITAND({13},{14})": "12",
|
||||
// BITLSHIFT
|
||||
"=BITLSHIFT(5,2)": "20",
|
||||
"=BITLSHIFT(3,5)": "96",
|
||||
"=BITLSHIFT(5,2)": "20",
|
||||
"=BITLSHIFT({3},5)": "96",
|
||||
"=BITLSHIFT(3,5)": "96",
|
||||
"=BITLSHIFT(3,{5})": "96",
|
||||
"=BITLSHIFT({3},{5})": "96",
|
||||
// BITOR
|
||||
"=BITOR(9,12)": "13",
|
||||
"=BITOR(9,12)": "13",
|
||||
"=BITOR({9},12)": "13",
|
||||
"=BITOR(9,{12})": "13",
|
||||
"=BITOR({9},{12})": "13",
|
||||
// BITRSHIFT
|
||||
"=BITRSHIFT(20,2)": "5",
|
||||
"=BITRSHIFT(52,4)": "3",
|
||||
"=BITRSHIFT(20,2)": "5",
|
||||
"=BITRSHIFT(52,4)": "3",
|
||||
"=BITRSHIFT({52},4)": "3",
|
||||
"=BITRSHIFT(52,{4})": "3",
|
||||
"=BITRSHIFT({52},{4})": "3",
|
||||
// BITXOR
|
||||
"=BITXOR(5,6)": "3",
|
||||
"=BITXOR(9,12)": "5",
|
||||
"=BITXOR(5,6)": "3",
|
||||
"=BITXOR(9,12)": "5",
|
||||
"=BITXOR({9},12)": "5",
|
||||
"=BITXOR(9,{12})": "5",
|
||||
"=BITXOR({9},{12})": "5",
|
||||
// COMPLEX
|
||||
"=COMPLEX(5,2)": "5+2i",
|
||||
"=COMPLEX(5,-9)": "5-9i",
|
||||
|
@ -221,6 +254,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=HEX2OCT(\"8\",10)": "0000000010",
|
||||
"=HEX2OCT(\"FFFFFFFFF8\")": "7777777770",
|
||||
"=HEX2OCT(\"1F3\")": "763",
|
||||
"=HEX2OCT({\"1F3\"})": "763",
|
||||
// IMABS
|
||||
"=IMABS(\"2j\")": "2",
|
||||
"=IMABS(\"-1+2i\")": "2.23606797749979",
|
||||
|
@ -773,6 +807,7 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.6666666666667",
|
||||
"=SUM(1+ROW())": "2",
|
||||
"=SUM((SUM(2))+1)": "3",
|
||||
"=SUM({1,2,3,4,\"\"})": "10",
|
||||
// SUMIF
|
||||
`=SUMIF(F1:F5, "")`: "0",
|
||||
`=SUMIF(A1:A5, "3")`: "3",
|
||||
|
|
|
@ -102,12 +102,12 @@ type PivotTableField struct {
|
|||
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
|
||||
// region := []string{"East", "West", "North", "South"}
|
||||
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
|
||||
// for i := 0; i < 30; i++ {
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000))
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)])
|
||||
// for row := 2; row < 32; row++ {
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
|
||||
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
|
||||
// }
|
||||
// if err := f.AddPivotTable(&excelize.PivotTableOption{
|
||||
// DataRange: "Sheet1!$A$1:$E$31",
|
||||
|
|
|
@ -18,12 +18,12 @@ func TestAddPivotTable(t *testing.T) {
|
|||
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
|
||||
region := []string{"East", "West", "North", "South"}
|
||||
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
|
||||
for i := 0; i < 30; i++ {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000)))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)]))
|
||||
for row := 2; row < 32; row++ {
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)))
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
|
||||
}
|
||||
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
|
||||
DataRange: "Sheet1!$A$1:$E$31",
|
||||
|
|
10
sheet.go
10
sheet.go
|
@ -1069,12 +1069,12 @@ func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error
|
|||
return err
|
||||
}
|
||||
|
||||
// ProtectSheet provides a function to prevent other users from accidentally
|
||||
// or deliberately changing, moving, or deleting data in a worksheet. The
|
||||
// ProtectSheet provides a function to prevent other users from accidentally or
|
||||
// deliberately changing, moving, or deleting data in a worksheet. The
|
||||
// optional field AlgorithmName specified hash algorithm, support XOR, MD4,
|
||||
// MD5, SHA1, SHA256, SHA384, and SHA512 currently, if no hash algorithm
|
||||
// specified, will be using the XOR algorithm as default. For example,
|
||||
// protect Sheet1 with protection settings:
|
||||
// MD5, SHA-1, SHA2-56, SHA-384, and SHA-512 currently, if no hash algorithm
|
||||
// specified, will be using the XOR algorithm as default. For example, protect
|
||||
// Sheet1 with protection settings:
|
||||
//
|
||||
// err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
|
||||
// AlgorithmName: "SHA-512",
|
||||
|
|
Loading…
Reference in New Issue