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
pull/2/head
xuri 3 years ago
parent afb2d27c90
commit 1c167b96a3
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
- package-ecosystem: gomod
directory: /
schedule:
interval: monthly

@ -829,6 +829,8 @@ func newEmptyFormulaArg() formulaArg {
func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) { func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) {
var err error var err error
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack() 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++ { for i := 0; i < len(tokens); i++ {
token := tokens[i] token := tokens[i]
@ -841,6 +843,14 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg,
// function start // function start
if isFunctionStartToken(token) { if isFunctionStartToken(token) {
if token.TValue == "ARRAY" {
inArray = true
continue
}
if token.TValue == "ARRAYROW" {
inArrayRow = true
continue
}
opfStack.Push(token) opfStack.Push(token)
argsStack.Push(list.New().Init()) argsStack.Push(list.New().Init())
opftStack.Push(token) // to know which operators belong to a function use the function as a separator 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 { if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical {
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue)) 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 { if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
return newEmptyFormulaArg(), err 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) 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 // parseToken parse basic arithmetic operator priority and evaluate based on
// operators and operands. // operators and operands.
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error { 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 // opd
if isOperand(token) { if isOperand(token) {
if token.TSubType == efp.TokenSubTypeNumber { opdStack.Push(tokenToFormulaArg(token))
num, _ := strconv.ParseFloat(token.TValue, 64)
opdStack.Push(newNumberFormulaArg(num))
} else {
opdStack.Push(newStringFormulaArg(token.TValue))
}
} }
return nil return nil
} }

@ -60,55 +60,88 @@ func TestCalcCellValue(t *testing.T) {
"=1&2": "12", "=1&2": "12",
"=15%": "0.15", "=15%": "0.15",
"=1+20%": "1.2", "=1+20%": "1.2",
"={1}+2": "3",
"=1+{2}": "3",
"={1}+{2}": "3",
`="A"="A"`: "TRUE", `="A"="A"`: "TRUE",
`="A"<>"A"`: "FALSE", `="A"<>"A"`: "FALSE",
// Engineering Functions // Engineering Functions
// BESSELI // BESSELI
"=BESSELI(4.5,1)": "15.3892227537359", "=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",
"=BESSELI({32},{1})": "5502845511211.25",
// BESSELJ // 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
"=BESSELK(0.05,0)": "3.11423403428966", "=BESSELK(0.05,0)": "3.11423403428966",
"=BESSELK(0.05,1)": "19.9096743272486", "=BESSELK(0.05,1)": "19.9096743272486",
"=BESSELK(0.05,2)": "799.501207124235", "=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",
"=BESSELK({3},{2})": "0.0615104585619118",
// BESSELY // BESSELY
"=BESSELY(0.05,0)": "-1.97931100684153", "=BESSELY(0.05,0)": "-1.97931100684153",
"=BESSELY(0.05,1)": "-12.789855163794", "=BESSELY(0.05,1)": "-12.789855163794",
"=BESSELY(0.05,2)": "-509.61489554492", "=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",
"=BESSELY({9},{2})": "-0.229082087487741",
// BIN2DEC // BIN2DEC
"=BIN2DEC(\"10\")": "2", "=BIN2DEC(\"10\")": "2",
"=BIN2DEC(\"11\")": "3", "=BIN2DEC(\"11\")": "3",
"=BIN2DEC(\"0000000010\")": "2", "=BIN2DEC(\"0000000010\")": "2",
"=BIN2DEC(\"1111111110\")": "-2", "=BIN2DEC(\"1111111110\")": "-2",
"=BIN2DEC(\"110\")": "6", "=BIN2DEC(\"110\")": "6",
"=BIN2DEC({\"110\"})": "6",
// BIN2HEX // BIN2HEX
"=BIN2HEX(\"10\")": "2", "=BIN2HEX(\"10\")": "2",
"=BIN2HEX(\"0000000001\")": "1", "=BIN2HEX(\"0000000001\")": "1",
"=BIN2HEX(\"10\",10)": "0000000002", "=BIN2HEX(\"10\",10)": "0000000002",
"=BIN2HEX(\"1111111110\")": "FFFFFFFFFE", "=BIN2HEX(\"1111111110\")": "FFFFFFFFFE",
"=BIN2HEX(\"11101\")": "1D", "=BIN2HEX(\"11101\")": "1D",
"=BIN2HEX({\"11101\"})": "1D",
// BIN2OCT // BIN2OCT
"=BIN2OCT(\"101\")": "5", "=BIN2OCT(\"101\")": "5",
"=BIN2OCT(\"0000000001\")": "1", "=BIN2OCT(\"0000000001\")": "1",
"=BIN2OCT(\"10\",10)": "0000000002", "=BIN2OCT(\"10\",10)": "0000000002",
"=BIN2OCT(\"1111111110\")": "7777777776", "=BIN2OCT(\"1111111110\")": "7777777776",
"=BIN2OCT(\"1110\")": "16", "=BIN2OCT(\"1110\")": "16",
"=BIN2OCT({\"1110\"})": "16",
// BITAND // BITAND
"=BITAND(13,14)": "12", "=BITAND(13,14)": "12",
"=BITAND({13},14)": "12",
"=BITAND(13,{14})": "12",
"=BITAND({13},{14})": "12",
// BITLSHIFT // BITLSHIFT
"=BITLSHIFT(5,2)": "20", "=BITLSHIFT(5,2)": "20",
"=BITLSHIFT(3,5)": "96", "=BITLSHIFT({3},5)": "96",
"=BITLSHIFT(3,5)": "96",
"=BITLSHIFT(3,{5})": "96",
"=BITLSHIFT({3},{5})": "96",
// BITOR // BITOR
"=BITOR(9,12)": "13", "=BITOR(9,12)": "13",
"=BITOR({9},12)": "13",
"=BITOR(9,{12})": "13",
"=BITOR({9},{12})": "13",
// BITRSHIFT // BITRSHIFT
"=BITRSHIFT(20,2)": "5", "=BITRSHIFT(20,2)": "5",
"=BITRSHIFT(52,4)": "3", "=BITRSHIFT(52,4)": "3",
"=BITRSHIFT({52},4)": "3",
"=BITRSHIFT(52,{4})": "3",
"=BITRSHIFT({52},{4})": "3",
// BITXOR // BITXOR
"=BITXOR(5,6)": "3", "=BITXOR(5,6)": "3",
"=BITXOR(9,12)": "5", "=BITXOR(9,12)": "5",
"=BITXOR({9},12)": "5",
"=BITXOR(9,{12})": "5",
"=BITXOR({9},{12})": "5",
// COMPLEX // COMPLEX
"=COMPLEX(5,2)": "5+2i", "=COMPLEX(5,2)": "5+2i",
"=COMPLEX(5,-9)": "5-9i", "=COMPLEX(5,-9)": "5-9i",
@ -221,6 +254,7 @@ func TestCalcCellValue(t *testing.T) {
"=HEX2OCT(\"8\",10)": "0000000010", "=HEX2OCT(\"8\",10)": "0000000010",
"=HEX2OCT(\"FFFFFFFFF8\")": "7777777770", "=HEX2OCT(\"FFFFFFFFF8\")": "7777777770",
"=HEX2OCT(\"1F3\")": "763", "=HEX2OCT(\"1F3\")": "763",
"=HEX2OCT({\"1F3\"})": "763",
// IMABS // IMABS
"=IMABS(\"2j\")": "2", "=IMABS(\"2j\")": "2",
"=IMABS(\"-1+2i\")": "2.23606797749979", "=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", "=1+SUM(SUM(1,2*3),4)*4/3+5+(4+2)*3": "38.6666666666667",
"=SUM(1+ROW())": "2", "=SUM(1+ROW())": "2",
"=SUM((SUM(2))+1)": "3", "=SUM((SUM(2))+1)": "3",
"=SUM({1,2,3,4,\"\"})": "10",
// SUMIF // SUMIF
`=SUMIF(F1:F5, "")`: "0", `=SUMIF(F1:F5, "")`: "0",
`=SUMIF(A1:A5, "3")`: "3", `=SUMIF(A1:A5, "3")`: "3",

@ -102,12 +102,12 @@ type PivotTableField struct {
// types := []string{"Meat", "Dairy", "Beverages", "Produce"} // types := []string{"Meat", "Dairy", "Beverages", "Produce"}
// region := []string{"East", "West", "North", "South"} // region := []string{"East", "West", "North", "South"}
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}) // f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
// for i := 0; i < 30; i++ { // for row := 2; row < 32; row++ {
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)]) // f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)]) // f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)]) // f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000)) // f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)]) // f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
// } // }
// if err := f.AddPivotTable(&excelize.PivotTableOption{ // if err := f.AddPivotTable(&excelize.PivotTableOption{
// DataRange: "Sheet1!$A$1:$E$31", // DataRange: "Sheet1!$A$1:$E$31",

@ -18,12 +18,12 @@ func TestAddPivotTable(t *testing.T) {
types := []string{"Meat", "Dairy", "Beverages", "Produce"} types := []string{"Meat", "Dairy", "Beverages", "Produce"}
region := []string{"East", "West", "North", "South"} region := []string{"East", "West", "North", "South"}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})) assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
for i := 0; i < 30; i++ { for row := 2; row < 32; row++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)])) 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", i+2), year[rand.Intn(3)])) 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", i+2), types[rand.Intn(4)])) 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", i+2), rand.Intn(5000))) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)])) assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
} }
assert.NoError(t, f.AddPivotTable(&PivotTableOption{ assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31", DataRange: "Sheet1!$A$1:$E$31",

@ -1069,12 +1069,12 @@ func (f *File) SetHeaderFooter(sheet string, settings *FormatHeaderFooter) error
return err return err
} }
// ProtectSheet provides a function to prevent other users from accidentally // ProtectSheet provides a function to prevent other users from accidentally or
// or deliberately changing, moving, or deleting data in a worksheet. The // deliberately changing, moving, or deleting data in a worksheet. The
// optional field AlgorithmName specified hash algorithm, support XOR, MD4, // optional field AlgorithmName specified hash algorithm, support XOR, MD4,
// MD5, SHA1, SHA256, SHA384, and SHA512 currently, if no hash algorithm // 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, // specified, will be using the XOR algorithm as default. For example, protect
// protect Sheet1 with protection settings: // Sheet1 with protection settings:
// //
// err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{ // err := f.ProtectSheet("Sheet1", &excelize.FormatSheetProtection{
// AlgorithmName: "SHA-512", // AlgorithmName: "SHA-512",

Loading…
Cancel
Save