This closes #1260, fixes compiling issue under 32-bit, and new formula functions
- ref #65, new formula functions: DCOUNT and DCOUNTA - support percentile symbol in condition criteria expression - this update dependencies module
This commit is contained in:
parent
2e1b0efadc
commit
301f7bc217
170
calc.go
170
calc.go
|
@ -315,9 +315,10 @@ type formulaFuncs struct {
|
|||
sheet, cell string
|
||||
}
|
||||
|
||||
// CalcCellValue provides a function to get calculated cell value. This
|
||||
// feature is currently in working processing. Array formula, table formula
|
||||
// and some other formulas are not supported currently.
|
||||
// CalcCellValue provides a function to get calculated cell value. This feature
|
||||
// is currently in working processing. Iterative calculation, implicit
|
||||
// intersection, explicit intersection, array formula, table formula and some
|
||||
// other formulas are not supported currently.
|
||||
//
|
||||
// Supported formula functions:
|
||||
//
|
||||
|
@ -421,6 +422,8 @@ type formulaFuncs struct {
|
|||
// DAYS
|
||||
// DAYS360
|
||||
// DB
|
||||
// DCOUNT
|
||||
// DCOUNTA
|
||||
// DDB
|
||||
// DEC2BIN
|
||||
// DEC2HEX
|
||||
|
@ -488,6 +491,7 @@ type formulaFuncs struct {
|
|||
// HEX2OCT
|
||||
// HLOOKUP
|
||||
// HOUR
|
||||
// HYPERLINK
|
||||
// HYPGEOM.DIST
|
||||
// HYPGEOMDIST
|
||||
// IF
|
||||
|
@ -1602,12 +1606,18 @@ func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, er
|
|||
var value, expected float64
|
||||
var e error
|
||||
prepareValue := func(val, cond string) (value float64, expected float64, err error) {
|
||||
percential := 1.0
|
||||
if strings.HasSuffix(cond, "%") {
|
||||
cond = strings.TrimSuffix(cond, "%")
|
||||
percential /= 100
|
||||
}
|
||||
if value, err = strconv.ParseFloat(val, 64); err != nil {
|
||||
return
|
||||
}
|
||||
if expected, err = strconv.ParseFloat(criteria.Condition, 64); err != nil {
|
||||
if expected, err = strconv.ParseFloat(cond, 64); err != nil {
|
||||
return
|
||||
}
|
||||
expected *= percential
|
||||
return
|
||||
}
|
||||
switch criteria.Type {
|
||||
|
@ -17957,3 +17967,155 @@ func (fn *formulaFuncs) YIELDMAT(argsList *list.List) formulaArg {
|
|||
result /= dsm.Number
|
||||
return newNumberFormulaArg(result)
|
||||
}
|
||||
|
||||
// Database Functions
|
||||
|
||||
// calcDatabase defines the structure for formula database.
|
||||
type calcDatabase struct {
|
||||
col, row int
|
||||
indexMap map[int]int
|
||||
database [][]formulaArg
|
||||
criteria [][]formulaArg
|
||||
}
|
||||
|
||||
// newCalcDatabase function returns formula database by given data range of
|
||||
// cells containing the database, field and criteria range.
|
||||
func newCalcDatabase(database, field, criteria formulaArg) *calcDatabase {
|
||||
db := calcDatabase{
|
||||
indexMap: make(map[int]int),
|
||||
database: database.Matrix,
|
||||
criteria: criteria.Matrix,
|
||||
}
|
||||
exp := len(database.Matrix) < 2 || len(database.Matrix[0]) < 1 ||
|
||||
len(criteria.Matrix) < 2 || len(criteria.Matrix[0]) < 1
|
||||
if field.Type != ArgEmpty {
|
||||
if db.col = db.columnIndex(database.Matrix, field); exp || db.col < 0 || len(db.database[0]) <= db.col {
|
||||
return nil
|
||||
}
|
||||
return &db
|
||||
}
|
||||
if db.col = -1; exp {
|
||||
return nil
|
||||
}
|
||||
return &db
|
||||
}
|
||||
|
||||
// columnIndex return index by specifies column field within the database for
|
||||
// which user want to return the count of non-blank cells.
|
||||
func (db *calcDatabase) columnIndex(database [][]formulaArg, field formulaArg) int {
|
||||
num := field.ToNumber()
|
||||
if num.Type != ArgNumber && len(database) > 0 {
|
||||
for i := 0; i < len(database[0]); i++ {
|
||||
if title := database[0][i]; strings.EqualFold(title.Value(), field.Value()) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
return int(num.Number - 1)
|
||||
}
|
||||
|
||||
// criteriaEval evaluate formula criteria expression.
|
||||
func (db *calcDatabase) criteriaEval() bool {
|
||||
var (
|
||||
columns, rows = len(db.criteria[0]), len(db.criteria)
|
||||
criteria = db.criteria
|
||||
k int
|
||||
matched bool
|
||||
)
|
||||
if len(db.indexMap) == 0 {
|
||||
fields := criteria[0]
|
||||
for j := 0; j < columns; j++ {
|
||||
if k = db.columnIndex(db.database, fields[j]); k < 0 {
|
||||
return false
|
||||
}
|
||||
db.indexMap[j] = k
|
||||
}
|
||||
}
|
||||
for i := 1; !matched && i < rows; i++ {
|
||||
matched = true
|
||||
for j := 0; matched && j < columns; j++ {
|
||||
criteriaExp := db.criteria[i][j].Value()
|
||||
if criteriaExp == "" {
|
||||
continue
|
||||
}
|
||||
criteria := formulaCriteriaParser(criteriaExp)
|
||||
cell := db.database[db.row][db.indexMap[j]].Value()
|
||||
matched, _ = formulaCriteriaEval(cell, criteria)
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// value returns the current cell value.
|
||||
func (db *calcDatabase) value() formulaArg {
|
||||
if db.col == -1 {
|
||||
return db.database[db.row][len(db.database[db.row])-1]
|
||||
}
|
||||
return db.database[db.row][db.col]
|
||||
}
|
||||
|
||||
// next will return true if find the matched cell in the database.
|
||||
func (db *calcDatabase) next() bool {
|
||||
matched, rows := false, len(db.database)
|
||||
for !matched && db.row < rows {
|
||||
if db.row++; db.row < rows {
|
||||
matched = db.criteriaEval()
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// dcount is an implementation of the formula functions DCOUNT and DCOUNTA.
|
||||
func (fn *formulaFuncs) dcount(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() < 2 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
|
||||
}
|
||||
if argsList.Len() > 3 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 3 arguments", name))
|
||||
}
|
||||
field := newEmptyFormulaArg()
|
||||
criteria := argsList.Back().Value.(formulaArg)
|
||||
if argsList.Len() > 2 {
|
||||
field = argsList.Front().Next().Value.(formulaArg)
|
||||
}
|
||||
var count float64
|
||||
database := argsList.Front().Value.(formulaArg)
|
||||
db := newCalcDatabase(database, field, criteria)
|
||||
if db == nil {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
for db.next() {
|
||||
cell := db.value()
|
||||
if cell.Value() == "" {
|
||||
continue
|
||||
}
|
||||
if num := cell.ToNumber(); name == "DCOUNT" && num.Type != ArgNumber {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
}
|
||||
return newNumberFormulaArg(count)
|
||||
}
|
||||
|
||||
// DOUNT function returns the number of cells containing numeric values, in a
|
||||
// field (column) of a database for selected records only. The records to be
|
||||
// included in the count are those that satisfy a set of one or more
|
||||
// user-specified criteria. The syntax of the function is:
|
||||
//
|
||||
// DCOUNT(database,[field],criteria)
|
||||
//
|
||||
func (fn *formulaFuncs) DCOUNT(argsList *list.List) formulaArg {
|
||||
return fn.dcount("DCOUNT", argsList)
|
||||
}
|
||||
|
||||
// DCOUNTA function returns the number of non-blank cells, in a field
|
||||
// (column) of a database for selected records only. The records to be
|
||||
// included in the count are those that satisfy a set of one or more
|
||||
// user-specified criteria. The syntax of the function is:
|
||||
//
|
||||
// DCOUNTA(database,[field],criteria)
|
||||
//
|
||||
func (fn *formulaFuncs) DCOUNTA(argsList *list.List) formulaArg {
|
||||
return fn.dcount("DCOUNTA", argsList)
|
||||
}
|
||||
|
|
57
calc_test.go
57
calc_test.go
|
@ -4603,6 +4603,63 @@ func TestCalcCOVAR(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCalcDCOUNTandDCOUNTA(t *testing.T) {
|
||||
cellData := [][]interface{}{
|
||||
{"Tree", "Height", "Age", "Yield", "Profit", "Height"},
|
||||
{"=Apple", ">1000%", nil, nil, nil, "<16"},
|
||||
{"=Pear"},
|
||||
{"Tree", "Height", "Age", "Yield", "Profit"},
|
||||
{"Apple", 18, 20, 14, 105},
|
||||
{"Pear", 12, 12, 10, 96},
|
||||
{"Cherry", 13, 14, 9, 105},
|
||||
{"Apple", 14, nil, 10, 75},
|
||||
{"Pear", 9, 8, 8, 77},
|
||||
{"Apple", 12, 11, 6, 45},
|
||||
}
|
||||
f := prepareCalcData(cellData)
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "=\"=Apple\""))
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "=\"=Pear\""))
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "C8", "=NA()"))
|
||||
formulaList := map[string]string{
|
||||
"=DCOUNT(A4:E10,\"Age\",A1:F2)": "1",
|
||||
"=DCOUNT(A4:E10,,A1:F2)": "2",
|
||||
"=DCOUNT(A4:E10,\"Profit\",A1:F2)": "2",
|
||||
"=DCOUNT(A4:E10,\"Tree\",A1:F2)": "0",
|
||||
"=DCOUNT(A4:E10,\"Age\",A2:F3)": "0",
|
||||
"=DCOUNTA(A4:E10,\"Age\",A1:F2)": "1",
|
||||
"=DCOUNTA(A4:E10,,A1:F2)": "2",
|
||||
"=DCOUNTA(A4:E10,\"Profit\",A1:F2)": "2",
|
||||
"=DCOUNTA(A4:E10,\"Tree\",A1:F2)": "2",
|
||||
"=DCOUNTA(A4:E10,\"Age\",A2:F3)": "0",
|
||||
}
|
||||
for formula, expected := range formulaList {
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula))
|
||||
result, err := f.CalcCellValue("Sheet1", "A11")
|
||||
assert.NoError(t, err, formula)
|
||||
assert.Equal(t, expected, result, formula)
|
||||
}
|
||||
calcError := map[string]string{
|
||||
"=DCOUNT()": "DCOUNT requires at least 2 arguments",
|
||||
"=DCOUNT(A4:E10,\"Age\",A1:F2,\"\")": "DCOUNT allows at most 3 arguments",
|
||||
"=DCOUNT(A4,\"Age\",A1:F2)": "#VALUE!",
|
||||
"=DCOUNT(A4:E10,NA(),A1:F2)": "#VALUE!",
|
||||
"=DCOUNT(A4:E4,,A1:F2)": "#VALUE!",
|
||||
"=DCOUNT(A4:E10,\"x\",A2:F3)": "#VALUE!",
|
||||
"=DCOUNTA()": "DCOUNTA requires at least 2 arguments",
|
||||
"=DCOUNTA(A4:E10,\"Age\",A1:F2,\"\")": "DCOUNTA allows at most 3 arguments",
|
||||
"=DCOUNTA(A4,\"Age\",A1:F2)": "#VALUE!",
|
||||
"=DCOUNTA(A4:E10,NA(),A1:F2)": "#VALUE!",
|
||||
"=DCOUNTA(A4:E4,,A1:F2)": "#VALUE!",
|
||||
"=DCOUNTA(A4:E10,\"x\",A2:F3)": "#VALUE!",
|
||||
}
|
||||
for formula, expected := range calcError {
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A11", formula))
|
||||
result, err := f.CalcCellValue("Sheet1", "A11")
|
||||
assert.EqualError(t, err, expected, formula)
|
||||
assert.Equal(t, "", result, formula)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalcFORMULATEXT(t *testing.T) {
|
||||
f, formulaText := NewFile(), "=SUM(B1:C1)"
|
||||
assert.NoError(t, f.SetCellFormula("Sheet1", "A1", formulaText))
|
||||
|
|
8
crypt.go
8
crypt.go
|
@ -1226,10 +1226,10 @@ func (c *cfb) Writer(encryptionInfoBuffer, encryptedPackage []byte) []byte {
|
|||
}
|
||||
MSAT = c.writeMSAT(MSATBlocks, SATBlocks, MSAT)
|
||||
blocks, SAT := c.writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks, SAT)
|
||||
storage.writeUint32(0xE011CFD0)
|
||||
storage.writeUint32(0xE11AB1A1)
|
||||
storage.writeUint64(0x00)
|
||||
storage.writeUint64(0x00)
|
||||
for i := 0; i < 8; i++ {
|
||||
storage.writeBytes([]byte{oleIdentifier[i]})
|
||||
}
|
||||
storage.writeBytes(make([]byte, 16))
|
||||
storage.writeUint16(0x003E)
|
||||
storage.writeUint16(0x0003)
|
||||
storage.writeUint16(-2)
|
||||
|
|
6
go.mod
6
go.mod
|
@ -8,11 +8,11 @@ require (
|
|||
github.com/richardlehane/mscfb v1.0.4
|
||||
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/text v0.3.7
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
)
|
||||
|
|
14
go.sum
14
go.sum
|
@ -13,21 +13,21 @@ github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 h1:3X7aE0iLKJ5j+tz58BpvIZkXNV7Yq4jC93Z/rbN2Fxk=
|
||||
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
|
||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
|
||||
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
|
Loading…
Reference in New Issue