Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,27 @@ type formulaFuncs struct {
sheet, cell string
}

// implicitIntersect applies Excel's implicit intersection to a matrix argument.
// For a non-array formula, when a whole-column or whole-row reference is passed
// to a scalar function, Excel resolves it to the single cell in the same row
// (or column) as the formula cell. If the argument is not a matrix, it is
// returned unchanged.
func (fn *formulaFuncs) implicitIntersect(arg formulaArg) formulaArg {
if arg.Type != ArgMatrix {
return arg
}
_, row, err := CellNameToCoordinates(fn.cell)
if err != nil {
return arg
}
// row is 1-based; matrix is 0-indexed
idx := row - 1
if idx >= 0 && idx < len(arg.Matrix) && len(arg.Matrix[idx]) > 0 {
return arg.Matrix[idx][0]
}
return arg
}

// 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
Expand Down Expand Up @@ -1922,7 +1943,14 @@ func formulaCriteriaEval(val formulaArg, criteria *formulaCriteria) (result bool
}
}
case criteriaRegexp:
return regexp.MatchString(criteria.Condition.Value(), val.Value())
pattern := criteria.Condition.Value()
if !strings.HasPrefix(pattern, "^") {
pattern = "^" + pattern
}
if !strings.HasSuffix(pattern, "$") {
pattern = pattern + "$"
}
return regexp.MatchString(pattern, val.Value())
}
return
}
Expand Down Expand Up @@ -3729,7 +3757,7 @@ func (fn *formulaFuncs) ABS(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ABS requires 1 numeric argument")
}
arg := argsList.Front().Value.(formulaArg).ToNumber()
arg := fn.implicitIntersect(argsList.Front().Value.(formulaArg)).ToNumber()
if arg.Type == ArgError {
return arg
}
Expand Down Expand Up @@ -11722,20 +11750,7 @@ func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument")
}
arg := argsList.Front().Value.(formulaArg)
if arg.Type == ArgMatrix {
var mtx [][]formulaArg
for _, row := range arg.Matrix {
var array []formulaArg
for _, val := range row {
if val.Type == ArgNumber {
array = append(array, newBoolFormulaArg(true))
}
array = append(array, newBoolFormulaArg(false))
}
mtx = append(mtx, array)
}
return newMatrixFormulaArg(mtx)
}
arg = fn.implicitIntersect(arg)
if arg.Type == ArgNumber {
return newBoolFormulaArg(true)
}
Expand Down Expand Up @@ -14878,7 +14893,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "IF accepts at most 3 arguments")
}
token := argsList.Front().Value.(formulaArg)
token := fn.implicitIntersect(argsList.Front().Value.(formulaArg))
var (
cond bool
err error
Expand All @@ -14897,7 +14912,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
return newBoolFormulaArg(cond)
}
if cond {
value := argsList.Front().Next().Value.(formulaArg)
value := fn.implicitIntersect(argsList.Front().Next().Value.(formulaArg))
switch value.Type {
case ArgNumber:
result = value.ToNumber()
Expand All @@ -14907,7 +14922,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
return result
}
if argsList.Len() == 3 {
value := argsList.Back().Value.(formulaArg)
value := fn.implicitIntersect(argsList.Back().Value.(formulaArg))
switch value.Type {
case ArgNumber:
result = value.ToNumber()
Expand Down
68 changes: 68 additions & 0 deletions calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5778,6 +5778,28 @@ func TestCalcSUMIFExactMatch(t *testing.T) {
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
cellData = [][]interface{}{
{"Category", "Amount"},
{"text", 10},
{"***text", 20},
{"text ***", 30},
{"TEXT", 40},
{5, 50},
{5.5, 60},
}
f = prepareCalcData(cellData)
for formula, expected := range map[string]string{
`SUMIF(A2:A7,"text",B2:B7)`: "50",
`SUMIF(A2:A7,"*text",B2:B7)`: "70",
`SUMIF(A2:A7,"text*",B2:B7)`: "80",
`SUMIF(A2:A7,5,B2:B7)`: "50",
`COUNTIF(A2:A7,"tex?")`: "2",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}

func TestCalcXIRR(t *testing.T) {
Expand Down Expand Up @@ -7006,3 +7028,49 @@ func TestCalcTrendGrowthRegression(t *testing.T) {
mtx := [][]float64{}
calcTrendGrowthRegression(false, false, 0, 0, 0, 0, 0, mtx, mtx, mtx, mtx)
}

func TestCalcImplicitIntersect(t *testing.T) {
f := NewFile()
for cell, value := range map[string]interface{}{
"A1": -5, "A2": 10, "A3": -3, "A4": "text", "A5": 7, "D1": 1, "D2": 0, "D3": 1,
} {
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
}
for cell, expected := range map[string]string{
"B1": "5", "B2": "10", "B3": "3", "B5": "7",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ABS(A1:A5)"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
for cell, expected := range map[string]string{
"C1": "TRUE", "C2": "TRUE", "C3": "TRUE", "C4": "FALSE", "C5": "TRUE",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "ISNUMBER(A1:A5)"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
for cell, expected := range map[string]string{
"E1": "yes", "E2": "no", "E3": "yes",
} {
assert.NoError(t, f.SetCellFormula("Sheet1", cell, "IF(D1:D3,\"yes\",\"no\")"))
result, err := f.CalcCellValue("Sheet1", cell)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "ABS(A1:A5)"))
_, err := f.CalcCellValue("Sheet1", "B10")
assert.NoError(t, err)
result, err := formulaCriteriaEval(newStringFormulaArg("text"), &formulaCriteria{
Type: criteriaRegexp,
Condition: newStringFormulaArg("text"),
})
assert.NoError(t, err)
assert.True(t, result)
fn := formulaFuncs{}
assert.Equal(t, ArgMatrix, fn.implicitIntersect(
newMatrixFormulaArg([][]formulaArg{{newNumberFormulaArg(1)}})).Type,
)
}
12 changes: 6 additions & 6 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var (
// ErrCoordinates defined the error message on invalid coordinates tuples
// length.
ErrCoordinates = errors.New("coordinates length must be 4")
// ErrCustomNumFmt defined the error message on receive the empty custom
// ErrCustomNumFmt defined the error message on receive the empty custom
// number format.
ErrCustomNumFmt = errors.New("custom number format can not be empty")
// ErrDataValidationFormulaLength defined the error message for receiving a
Expand All @@ -55,21 +55,21 @@ var (
ErrDefinedNameScope = errors.New("no defined name on the scope")
// ErrExistsSheet defined the error message on given sheet already exists.
ErrExistsSheet = errors.New("the same name sheet already exists")
// ErrExistsTableName defined the error message on given table already
// ErrExistsTableName defined the error message on given table already
// exists.
ErrExistsTableName = errors.New("the same name table already exists")
// ErrFillType defined the error message on receive an invalid fill type.
ErrFillType = errors.New("fill type value must be one of 'gradient' or 'pattern'")
// ErrFillGradientColor defined the error message on receive an invalid fill
// color for 'gradient' type.
ErrFillGradientColor = errors.New("fill color value must be an array of two colors for 'gradient' type")
// ErrFillGradientShading defined the error message on receive an invalid
// ErrFillGradientShading defined the error message on receive an invalid
// fill shading for 'gradient' type.
ErrFillGradientShading = errors.New("fill shading value must be between 0 and 16 for 'gradient' type")
// ErrFillPatternColor defined the error message on receive an invalid fill
// color for 'pattern' type.
ErrFillPatternColor = errors.New("fill color value must be empty or an array of one color for 'pattern' type")
// ErrFillPattern defined the error message on receive an invalid fill
// ErrFillPattern defined the error message on receive an invalid fill
// pattern.
ErrFillPattern = errors.New("fill pattern value must be between 0 and 18")
// ErrFontLength defined the error message on the length of the font
Expand All @@ -94,7 +94,7 @@ var (
// ErrMaxRowHeight defined the error message on receive an invalid row
// height.
ErrMaxRowHeight = fmt.Errorf("the height of the row must be less than or equal to %d points", MaxRowHeight)
// ErrMaxRows defined the error message on receive a row number exceeds
// ErrMaxRows defined the error message on receive a row number exceeds
// maximum limit.
ErrMaxRows = errors.New("row number exceeds maximum limit")
// ErrNameLength defined the error message on receiving the defined name or
Expand Down Expand Up @@ -186,7 +186,7 @@ var (
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
// hash algorithm.
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
// ErrUnsupportedNumberFormat defined the error message on unsupported
// ErrUnsupportedNumberFormat defined the error message on unsupported
// number format expression.
ErrUnsupportedNumberFormat = errors.New("unsupported number format token")
// ErrWorkbookFileFormat defined the error message on receive an
Expand Down