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:
xuri 2022-05-26 00:15:28 +08:00
parent afb2d27c90
commit 1c167b96a3
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 114 additions and 43 deletions

10
.github/dependabot.yml vendored Normal file
View File

@ -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
View File

@ -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
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",