This closes #1681, closes #1683

- Fix incorrect formula calculation result
- Introduce new exported function `SetCellUint`
- Updates unit test
- Typo fixed
This commit is contained in:
xuri 2023-10-08 00:06:11 +08:00
parent 07f2c6831a
commit 99df1a7343
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 162 additions and 106 deletions

175
calc.go
View File

@ -193,7 +193,7 @@ var (
return fmt.Sprintf("R[%d]C[%d]", row, col), nil
},
}
formularFormats = []*regexp.Regexp{
formulaFormats = []*regexp.Regexp{
regexp.MustCompile(`^(\d+)$`),
regexp.MustCompile(`^=(.*)$`),
regexp.MustCompile(`^<>(.*)$`),
@ -202,7 +202,7 @@ var (
regexp.MustCompile(`^<(.*)$`),
regexp.MustCompile(`^>(.*)$`),
}
formularCriterias = []byte{
formulaCriterias = []byte{
criteriaEq,
criteriaEq,
criteriaNe,
@ -238,7 +238,7 @@ type cellRange struct {
// formulaCriteria defined formula criteria parser result.
type formulaCriteria struct {
Type byte
Condition string
Condition formulaArg
}
// ArgType is the type of formula argument type.
@ -1698,65 +1698,66 @@ func callFuncByName(receiver interface{}, name string, params []reflect.Value) (
}
// formulaCriteriaParser parse formula criteria.
func formulaCriteriaParser(exp string) (fc *formulaCriteria) {
fc = &formulaCriteria{}
if exp == "" {
return
}
for i, re := range formularFormats {
if match := re.FindStringSubmatch(exp); len(match) > 1 {
fc.Type, fc.Condition = formularCriterias[i], match[1]
return
}
}
if strings.Contains(exp, "?") {
exp = strings.ReplaceAll(exp, "?", ".")
}
if strings.Contains(exp, "*") {
exp = strings.ReplaceAll(exp, "*", ".*")
}
fc.Type, fc.Condition = criteriaRegexp, exp
return
}
// formulaCriteriaEval evaluate formula criteria expression.
func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, err error) {
var value, expected float64
var e error
prepareValue := func(val, cond string) (value float64, expected float64, err error) {
func formulaCriteriaParser(exp formulaArg) *formulaCriteria {
prepareValue := func(cond string) (expected float64, err error) {
percentile := 1.0
if strings.HasSuffix(cond, "%") {
cond = strings.TrimSuffix(cond, "%")
percentile /= 100
}
if value, err = strconv.ParseFloat(val, 64); err != nil {
return
}
if expected, err = strconv.ParseFloat(cond, 64); err != nil {
return
}
expected *= percentile
return
}
fc, val := &formulaCriteria{}, exp.Value()
if val == "" {
return fc
}
for i, re := range formulaFormats {
if match := re.FindStringSubmatch(val); len(match) > 1 {
fc.Condition = newStringFormulaArg(match[1])
if num, err := prepareValue(match[1]); err == nil {
fc.Condition = newNumberFormulaArg(num)
}
fc.Type = formulaCriterias[i]
return fc
}
}
if strings.Contains(val, "?") {
val = strings.ReplaceAll(val, "?", ".")
}
if strings.Contains(val, "*") {
val = strings.ReplaceAll(val, "*", ".*")
}
fc.Type, fc.Condition = criteriaRegexp, newStringFormulaArg(val)
if num := fc.Condition.ToNumber(); num.Type == ArgNumber {
fc.Condition = num
}
return fc
}
// formulaCriteriaEval evaluate formula criteria expression.
func formulaCriteriaEval(val formulaArg, criteria *formulaCriteria) (result bool, err error) {
s := NewStack()
tokenCalcFunc := map[byte]func(rOpd, lOpd formulaArg, opdStack *Stack) error{
criteriaEq: calcEq,
criteriaNe: calcNEq,
criteriaL: calcL,
criteriaLe: calcLe,
criteriaG: calcG,
criteriaGe: calcGe,
}
switch criteria.Type {
case criteriaEq:
return val == criteria.Condition, err
case criteriaLe:
value, expected, e = prepareValue(val, criteria.Condition)
return value <= expected && e == nil, err
case criteriaGe:
value, expected, e = prepareValue(val, criteria.Condition)
return value >= expected && e == nil, err
case criteriaNe:
return val != criteria.Condition, err
case criteriaL:
value, expected, e = prepareValue(val, criteria.Condition)
return value < expected && e == nil, err
case criteriaG:
value, expected, e = prepareValue(val, criteria.Condition)
return value > expected && e == nil, err
case criteriaEq, criteriaLe, criteriaGe, criteriaNe, criteriaL, criteriaG:
if fn, ok := tokenCalcFunc[criteria.Type]; ok {
if _ = fn(criteria.Condition, val, s); s.Len() > 0 {
return s.Pop().(formulaArg).Number == 1, err
}
}
case criteriaRegexp:
return regexp.MatchString(criteria.Condition, val)
return regexp.MatchString(criteria.Condition.Value(), val.Value())
}
return
}
@ -5821,7 +5822,7 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 arguments")
}
criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
rangeMtx := argsList.Front().Value.(formulaArg).Matrix
var sumRange [][]formulaArg
if argsList.Len() == 3 {
@ -5835,7 +5836,7 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
if arg.Type == ArgEmpty {
continue
}
if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok {
if ok, _ := formulaCriteriaEval(arg, criteria); ok {
if argsList.Len() == 3 {
if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx {
arg = sumRange[rowIdx][colIdx]
@ -6169,7 +6170,7 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments")
}
var (
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).Value())
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
rangeMtx = argsList.Front().Value.(formulaArg).Matrix
cellRange [][]formulaArg
args []formulaArg
@ -6183,10 +6184,13 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg {
for rowIdx, row := range rangeMtx {
for colIdx, col := range row {
fromVal := col.Value()
if col.Value() == "" {
if fromVal == "" {
continue
}
ok, _ = formulaCriteriaEval(fromVal, criteria)
if col.Type == ArgString && criteria.Condition.Type != ArgString {
continue
}
ok, _ = formulaCriteriaEval(col, criteria)
if ok {
if argsList.Len() == 3 {
if len(cellRange) > rowIdx && len(cellRange[rowIdx]) > colIdx {
@ -7880,11 +7884,14 @@ func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "COUNTIF requires 2 arguments")
}
var (
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg))
count float64
)
for _, cell := range argsList.Front().Value.(formulaArg).ToList() {
if ok, _ := formulaCriteriaEval(cell.Value(), criteria); ok {
if cell.Type == ArgString && criteria.Condition.Type != ArgString {
continue
}
if ok, _ := formulaCriteriaEval(cell, criteria); ok {
count++
}
}
@ -7895,11 +7902,11 @@ func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg {
func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) {
for i := 0; i < len(args)-1; i += 2 {
var match []cellRef
matrix, criteria := args[i].Matrix, formulaCriteriaParser(args[i+1].Value())
matrix, criteria := args[i].Matrix, formulaCriteriaParser(args[i+1])
if i == 0 {
for rowIdx, row := range matrix {
for colIdx, col := range row {
if ok, _ := formulaCriteriaEval(col.Value(), criteria); ok {
if ok, _ := formulaCriteriaEval(col, criteria); ok {
match = append(match, cellRef{Col: colIdx, Row: rowIdx})
}
}
@ -7908,7 +7915,7 @@ func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) {
match = []cellRef{}
for _, ref := range cellRefs {
value := matrix[ref.Row][ref.Col]
if ok, _ := formulaCriteriaEval(value.Value(), criteria); ok {
if ok, _ := formulaCriteriaEval(value, criteria); ok {
match = append(match, ref)
}
}
@ -14830,43 +14837,43 @@ func (fn *formulaFuncs) HYPERLINK(argsList *list.List) formulaArg {
// calcMatch returns the position of the value by given match type, criteria
// and lookup array for the formula function MATCH.
func calcMatch(matchType int, criteria *formulaCriteria, lookupArray []formulaArg) formulaArg {
idx := -1
switch matchType {
case 0:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok {
if ok, _ := formulaCriteriaEval(arg, criteria); ok {
return newNumberFormulaArg(float64(i + 1))
}
}
case -1:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok {
return newNumberFormulaArg(float64(i + 1))
}
if ok, _ := formulaCriteriaEval(arg.Value(), &formulaCriteria{
Type: criteriaL, Condition: criteria.Condition,
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaGe, Condition: criteria.Condition,
}); ok {
if i == 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(float64(i))
idx = i
continue
}
if criteria.Condition.Type == ArgNumber {
break
}
}
case 1:
for i, arg := range lookupArray {
if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok {
return newNumberFormulaArg(float64(i + 1))
}
if ok, _ := formulaCriteriaEval(arg.Value(), &formulaCriteria{
Type: criteriaG, Condition: criteria.Condition,
if ok, _ := formulaCriteriaEval(arg, &formulaCriteria{
Type: criteriaLe, Condition: criteria.Condition,
}); ok {
if i == 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(float64(i))
idx = i
continue
}
if criteria.Condition.Type == ArgNumber {
break
}
}
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
if idx == -1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(float64(idx + 1))
}
// MATCH function looks up a value in an array, and returns the position of
@ -14904,7 +14911,7 @@ func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg {
default:
return newErrorFormulaArg(formulaErrorNA, lookupArrayErr)
}
return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).Value()), lookupArray)
return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg)), lookupArray)
}
// TRANSPOSE function 'transposes' an array of cells (i.e. the function copies
@ -14970,7 +14977,7 @@ start:
}
}
if matchMode.Number == matchModeMinGreater || matchMode.Number == matchModeMaxLess {
matchIdx = int(calcMatch(int(matchMode.Number), formulaCriteriaParser(lookupValue.Value()), tableArray).Number)
matchIdx = int(calcMatch(int(matchMode.Number), formulaCriteriaParser(lookupValue), tableArray).Number)
continue
}
}
@ -18390,12 +18397,12 @@ func (db *calcDatabase) criteriaEval() bool {
for i := 1; !matched && i < rows; i++ {
matched = true
for j := 0; matched && j < columns; j++ {
criteriaExp := db.criteria[i][j].Value()
if criteriaExp == "" {
criteriaExp := db.criteria[i][j]
if criteriaExp.Value() == "" {
continue
}
criteria := formulaCriteriaParser(criteriaExp)
cell := db.database[db.row][db.indexMap[j]].Value()
cell := db.database[db.row][db.indexMap[j]]
matched, _ = formulaCriteriaEval(cell, criteria)
}
}

View File

@ -4913,9 +4913,9 @@ func TestCalcAVERAGEIF(t *testing.T) {
{4, 50},
{5, 100},
{1, 50},
{"TRUE", 200},
{"TRUE", 250},
{"FALSE", 50},
{true, 200},
{true, 250},
{false, 50},
})
for formula, expected := range map[string]string{
"=AVERAGEIF(A1:A14,\"Thursday\",B1:B14)": "150",
@ -5622,6 +5622,7 @@ func TestCalcMATCH(t *testing.T) {
"=MATCH(8,C1:C6,1)": "3",
"=MATCH(6,B1:B6,-1)": "1",
"=MATCH(10,D1:D6,-1)": "3",
"=MATCH(-10,D1:D6,-1)": "6",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))

53
cell.go
View File

@ -216,15 +216,15 @@ func (f *File) setCellIntFunc(sheet, cell string, value interface{}) error {
case int64:
err = f.SetCellInt(sheet, cell, int(v))
case uint:
err = f.SetCellInt(sheet, cell, int(v))
err = f.SetCellUint(sheet, cell, uint64(v))
case uint8:
err = f.SetCellInt(sheet, cell, int(v))
err = f.SetCellUint(sheet, cell, uint64(v))
case uint16:
err = f.SetCellInt(sheet, cell, int(v))
err = f.SetCellUint(sheet, cell, uint64(v))
case uint32:
err = f.SetCellInt(sheet, cell, int(v))
err = f.SetCellUint(sheet, cell, uint64(v))
case uint64:
err = f.SetCellInt(sheet, cell, int(v))
err = f.SetCellUint(sheet, cell, v)
}
return err
}
@ -307,13 +307,41 @@ func (f *File) SetCellInt(sheet, cell string, value int) error {
return f.removeFormula(c, ws, sheet)
}
// setCellInt prepares cell type and string type cell value by a given
// integer.
// setCellInt prepares cell type and string type cell value by a given integer.
func setCellInt(value int) (t string, v string) {
v = strconv.Itoa(value)
return
}
// SetCellUint provides a function to set uint type value of a cell by given
// worksheet name, cell reference and cell value.
func (f *File) SetCellUint(sheet, cell string, value uint64) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell)
if err != nil {
return err
}
c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellUint(value)
c.IS = nil
return f.removeFormula(c, ws, sheet)
}
// setCellUint prepares cell type and string type cell value by a given unsigned
// integer.
func setCellUint(value uint64) (t string, v string) {
v = strconv.FormatUint(value, 10)
return
}
// SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell reference and cell value.
func (f *File) SetCellBool(sheet, cell string, value bool) error {
@ -336,8 +364,8 @@ func (f *File) SetCellBool(sheet, cell string, value bool) error {
return f.removeFormula(c, ws, sheet)
}
// setCellBool prepares cell type and string type cell value by a given
// boolean value.
// setCellBool prepares cell type and string type cell value by a given boolean
// value.
func setCellBool(value bool) (t string, v string) {
t = "b"
if value {
@ -376,8 +404,8 @@ func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSiz
return f.removeFormula(c, ws, sheet)
}
// setCellFloat prepares cell type and string type cell value by a given
// float value.
// setCellFloat prepares cell type and string type cell value by a given float
// value.
func setCellFloat(value float64, precision, bitSize int) (t string, v string) {
v = strconv.FormatFloat(value, 'f', precision, bitSize)
return
@ -407,8 +435,7 @@ func (f *File) SetCellStr(sheet, cell, value string) error {
return f.removeFormula(c, ws, sheet)
}
// setCellString provides a function to set string type to shared string
// table.
// setCellString provides a function to set string type to shared string table.
func (f *File) setCellString(value string) (t, v string, err error) {
if utf8.RuneCountInString(value) > TotalCellChars {
value = string([]rune(value)[:TotalCellChars])

View File

@ -3,6 +3,7 @@ package excelize
import (
"fmt"
_ "image/jpeg"
"math"
"os"
"path/filepath"
"reflect"
@ -176,6 +177,26 @@ func TestSetCellFloat(t *testing.T) {
assert.EqualError(t, f.SetCellFloat("Sheet:1", "A1", 123.42, -1, 64), ErrSheetNameInvalid.Error())
}
func TestSetCellUint(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint8(math.MaxUint8)))
result, err := f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "255", result)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint16)))
result, err = f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "65535", result)
assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", uint(math.MaxUint32)))
result, err = f.GetCellValue("Sheet1", "A1")
assert.Equal(t, "4294967295", result)
assert.NoError(t, err)
// Test uint cell value not exists worksheet
assert.EqualError(t, f.SetCellUint("SheetN", "A1", 1), "sheet SheetN does not exist")
// Test uint cell value with illegal cell reference
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellUint("Sheet1", "A", 1))
}
func TestSetCellValuesMultiByte(t *testing.T) {
f := NewFile()
row := []interface{}{

View File

@ -217,7 +217,7 @@ func TestColumnVisibility(t *testing.T) {
assert.Equal(t, true, visible)
assert.NoError(t, err)
// Test get column visible on an inexistent worksheet
// Test get column visible on not exists worksheet
_, err = f.GetColVisible("SheetN", "F")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test get column visible with invalid sheet name

View File

@ -567,15 +567,15 @@ func setCellIntFunc(c *xlsxC, val interface{}) (err error) {
case int64:
c.T, c.V = setCellInt(int(val))
case uint:
c.T, c.V = setCellInt(int(val))
c.T, c.V = setCellUint(uint64(val))
case uint8:
c.T, c.V = setCellInt(int(val))
c.T, c.V = setCellUint(uint64(val))
case uint16:
c.T, c.V = setCellInt(int(val))
c.T, c.V = setCellUint(uint64(val))
case uint32:
c.T, c.V = setCellInt(int(val))
c.T, c.V = setCellUint(uint64(val))
case uint64:
c.T, c.V = setCellInt(int(val))
c.T, c.V = setCellUint(val)
default:
}
return