|
|
@ -57,6 +57,17 @@ const (
|
|
|
|
criteriaG
|
|
|
|
criteriaG
|
|
|
|
criteriaErr
|
|
|
|
criteriaErr
|
|
|
|
criteriaRegexp
|
|
|
|
criteriaRegexp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
matchModeExact = 0
|
|
|
|
|
|
|
|
matchModeMinGreater = 1
|
|
|
|
|
|
|
|
matchModeMaxLess = -1
|
|
|
|
|
|
|
|
matchModeWildcard = 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
searchModeLinear = 1
|
|
|
|
|
|
|
|
searchModeReverseLinear = -1
|
|
|
|
|
|
|
|
searchModeAscBinary = 2
|
|
|
|
|
|
|
|
searchModeDescBinary = -2
|
|
|
|
|
|
|
|
|
|
|
|
maxFinancialIterations = 128
|
|
|
|
maxFinancialIterations = 128
|
|
|
|
financialPercision = 1.0e-08
|
|
|
|
financialPercision = 1.0e-08
|
|
|
|
// Date and time format regular expressions
|
|
|
|
// Date and time format regular expressions
|
|
|
@ -860,7 +871,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
|
|
|
opftStack.Pop() // remove current function separator
|
|
|
|
opftStack.Pop() // remove current function separator
|
|
|
|
opfStack.Pop()
|
|
|
|
opfStack.Pop()
|
|
|
|
if opfStack.Len() > 0 { // still in function stack
|
|
|
|
if opfStack.Len() > 0 { // still in function stack
|
|
|
|
if nextToken.TType == efp.TokenTypeOperatorInfix || opftStack.Len() > 1 {
|
|
|
|
if nextToken.TType == efp.TokenTypeOperatorInfix || (opftStack.Len() > 1 && opfdStack.Len() > 0) {
|
|
|
|
// mathematics calculate in formula function
|
|
|
|
// mathematics calculate in formula function
|
|
|
|
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
|
|
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -5227,7 +5238,7 @@ func (fn *formulaFuncs) COUNTIF(argsList *list.List) formulaArg {
|
|
|
|
return newNumberFormulaArg(count)
|
|
|
|
return newNumberFormulaArg(count)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// formulaIfsMatch function returns cells reference array which match criterias.
|
|
|
|
// formulaIfsMatch function returns cells reference array which match criteria.
|
|
|
|
func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) {
|
|
|
|
func formulaIfsMatch(args []formulaArg) (cellRefs []cellRef) {
|
|
|
|
for i := 0; i < len(args)-1; i += 2 {
|
|
|
|
for i := 0; i < len(args)-1; i += 2 {
|
|
|
|
match := []cellRef{}
|
|
|
|
match := []cellRef{}
|
|
|
@ -9013,7 +9024,7 @@ func matchPattern(pattern, name string) (matched bool) {
|
|
|
|
// compareFormulaArg compares the left-hand sides and the right-hand sides
|
|
|
|
// compareFormulaArg compares the left-hand sides and the right-hand sides
|
|
|
|
// formula arguments by given conditions such as case sensitive, if exact
|
|
|
|
// formula arguments by given conditions such as case sensitive, if exact
|
|
|
|
// match, and make compare result as formula criteria condition type.
|
|
|
|
// match, and make compare result as formula criteria condition type.
|
|
|
|
func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
|
|
|
|
func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
|
|
|
|
if lhs.Type != rhs.Type {
|
|
|
|
if lhs.Type != rhs.Type {
|
|
|
|
return criteriaErr
|
|
|
|
return criteriaErr
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9031,35 +9042,26 @@ func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte
|
|
|
|
if !caseSensitive {
|
|
|
|
if !caseSensitive {
|
|
|
|
ls, rs = strings.ToLower(ls), strings.ToLower(rs)
|
|
|
|
ls, rs = strings.ToLower(ls), strings.ToLower(rs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if exactMatch {
|
|
|
|
if matchMode.Number == matchModeWildcard {
|
|
|
|
match := matchPattern(rs, ls)
|
|
|
|
if matchPattern(rs, ls) {
|
|
|
|
if match {
|
|
|
|
|
|
|
|
return criteriaEq
|
|
|
|
return criteriaEq
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return criteriaG
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.Compare(ls, rs) {
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
|
|
return criteriaG
|
|
|
|
|
|
|
|
case -1:
|
|
|
|
|
|
|
|
return criteriaL
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
|
|
return criteriaEq
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return criteriaErr
|
|
|
|
return map[int]byte{1: criteriaG, -1: criteriaL, 0: criteriaEq}[strings.Compare(ls, rs)]
|
|
|
|
case ArgEmpty:
|
|
|
|
case ArgEmpty:
|
|
|
|
return criteriaEq
|
|
|
|
return criteriaEq
|
|
|
|
case ArgList:
|
|
|
|
case ArgList:
|
|
|
|
return compareFormulaArgList(lhs, rhs, caseSensitive, exactMatch)
|
|
|
|
return compareFormulaArgList(lhs, rhs, matchMode, caseSensitive)
|
|
|
|
case ArgMatrix:
|
|
|
|
case ArgMatrix:
|
|
|
|
return compareFormulaArgMatrix(lhs, rhs, caseSensitive, exactMatch)
|
|
|
|
return compareFormulaArgMatrix(lhs, rhs, matchMode, caseSensitive)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return criteriaErr
|
|
|
|
return criteriaErr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// compareFormulaArgList compares the left-hand sides and the right-hand sides
|
|
|
|
// compareFormulaArgList compares the left-hand sides and the right-hand sides
|
|
|
|
// list type formula arguments.
|
|
|
|
// list type formula arguments.
|
|
|
|
func compareFormulaArgList(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
|
|
|
|
func compareFormulaArgList(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
|
|
|
|
if len(lhs.List) < len(rhs.List) {
|
|
|
|
if len(lhs.List) < len(rhs.List) {
|
|
|
|
return criteriaL
|
|
|
|
return criteriaL
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9067,7 +9069,7 @@ func compareFormulaArgList(lhs, rhs formulaArg, caseSensitive, exactMatch bool)
|
|
|
|
return criteriaG
|
|
|
|
return criteriaG
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for arg := range lhs.List {
|
|
|
|
for arg := range lhs.List {
|
|
|
|
criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], caseSensitive, exactMatch)
|
|
|
|
criteria := compareFormulaArg(lhs.List[arg], rhs.List[arg], matchMode, caseSensitive)
|
|
|
|
if criteria != criteriaEq {
|
|
|
|
if criteria != criteriaEq {
|
|
|
|
return criteria
|
|
|
|
return criteria
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9077,7 +9079,7 @@ func compareFormulaArgList(lhs, rhs formulaArg, caseSensitive, exactMatch bool)
|
|
|
|
|
|
|
|
|
|
|
|
// compareFormulaArgMatrix compares the left-hand sides and the right-hand sides
|
|
|
|
// compareFormulaArgMatrix compares the left-hand sides and the right-hand sides
|
|
|
|
// matrix type formula arguments.
|
|
|
|
// matrix type formula arguments.
|
|
|
|
func compareFormulaArgMatrix(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte {
|
|
|
|
func compareFormulaArgMatrix(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
|
|
|
|
if len(lhs.Matrix) < len(rhs.Matrix) {
|
|
|
|
if len(lhs.Matrix) < len(rhs.Matrix) {
|
|
|
|
return criteriaL
|
|
|
|
return criteriaL
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9094,7 +9096,7 @@ func compareFormulaArgMatrix(lhs, rhs formulaArg, caseSensitive, exactMatch bool
|
|
|
|
return criteriaG
|
|
|
|
return criteriaG
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for arg := range left {
|
|
|
|
for arg := range left {
|
|
|
|
criteria := compareFormulaArg(left[arg], right[arg], caseSensitive, exactMatch)
|
|
|
|
criteria := compareFormulaArg(left[arg], right[arg], matchMode, caseSensitive)
|
|
|
|
if criteria != criteriaEq {
|
|
|
|
if criteria != criteriaEq {
|
|
|
|
return criteria
|
|
|
|
return criteria
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9190,7 +9192,7 @@ func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg {
|
|
|
|
|
|
|
|
|
|
|
|
// checkHVLookupArgs checking arguments, prepare extract mode, lookup value,
|
|
|
|
// checkHVLookupArgs checking arguments, prepare extract mode, lookup value,
|
|
|
|
// and data for the formula functions HLOOKUP and VLOOKUP.
|
|
|
|
// and data for the formula functions HLOOKUP and VLOOKUP.
|
|
|
|
func checkHVLookupArgs(name string, argsList *list.List) (idx, matchIdx int, wasExact, exactMatch bool, lookupValue, tableArray, errArg formulaArg) {
|
|
|
|
func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue, tableArray, matchMode, errArg formulaArg) {
|
|
|
|
unit := map[string]string{
|
|
|
|
unit := map[string]string{
|
|
|
|
"HLOOKUP": "row",
|
|
|
|
"HLOOKUP": "row",
|
|
|
|
"VLOOKUP": "col",
|
|
|
|
"VLOOKUP": "col",
|
|
|
@ -9214,7 +9216,7 @@ func checkHVLookupArgs(name string, argsList *list.List) (idx, matchIdx int, was
|
|
|
|
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit))
|
|
|
|
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit))
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
idx, matchIdx = int(arg.Number)-1, -1
|
|
|
|
idx, matchMode = int(arg.Number)-1, newNumberFormulaArg(matchModeMaxLess)
|
|
|
|
if argsList.Len() == 4 {
|
|
|
|
if argsList.Len() == 4 {
|
|
|
|
rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
|
|
|
|
rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
|
|
|
|
if rangeLookup.Type == ArgError {
|
|
|
|
if rangeLookup.Type == ArgError {
|
|
|
@ -9222,7 +9224,7 @@ func checkHVLookupArgs(name string, argsList *list.List) (idx, matchIdx int, was
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if rangeLookup.Number == 0 {
|
|
|
|
if rangeLookup.Number == 0 {
|
|
|
|
exactMatch = true
|
|
|
|
matchMode = newNumberFormulaArg(matchModeWildcard)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
return
|
|
|
@ -9235,35 +9237,16 @@ func checkHVLookupArgs(name string, argsList *list.List) (idx, matchIdx int, was
|
|
|
|
// HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup])
|
|
|
|
// HLOOKUP(lookup_value,table_array,row_index_num,[range_lookup])
|
|
|
|
//
|
|
|
|
//
|
|
|
|
func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
rowIdx, matchIdx, wasExact, exactMatch,
|
|
|
|
rowIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("HLOOKUP", argsList)
|
|
|
|
lookupValue, tableArray, errArg := checkHVLookupArgs("HLOOKUP", argsList)
|
|
|
|
|
|
|
|
if errArg.Type == ArgError {
|
|
|
|
if errArg.Type == ArgError {
|
|
|
|
return errArg
|
|
|
|
return errArg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
row := tableArray.Matrix[0]
|
|
|
|
var matchIdx int
|
|
|
|
if exactMatch || len(tableArray.Matrix) == TotalRows {
|
|
|
|
var wasExact bool
|
|
|
|
start:
|
|
|
|
if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows {
|
|
|
|
for idx, mtx := range row {
|
|
|
|
matchIdx, wasExact = lookupLinearSearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear))
|
|
|
|
lhs := mtx
|
|
|
|
|
|
|
|
switch lookupValue.Type {
|
|
|
|
|
|
|
|
case ArgNumber:
|
|
|
|
|
|
|
|
if !lookupValue.Boolean {
|
|
|
|
|
|
|
|
lhs = mtx.ToNumber()
|
|
|
|
|
|
|
|
if lhs.Type == ArgError {
|
|
|
|
|
|
|
|
lhs = mtx
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
case ArgMatrix:
|
|
|
|
|
|
|
|
lhs = tableArray
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq {
|
|
|
|
|
|
|
|
matchIdx = idx
|
|
|
|
|
|
|
|
wasExact = true
|
|
|
|
|
|
|
|
break start
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
matchIdx, wasExact = hlookupBinarySearch(row, lookupValue)
|
|
|
|
matchIdx, wasExact = lookupBinarySearch(false, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if matchIdx == -1 {
|
|
|
|
if matchIdx == -1 {
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
|
|
|
@ -9271,8 +9254,8 @@ func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) {
|
|
|
|
if rowIdx < 0 || rowIdx >= len(tableArray.Matrix) {
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP has invalid row index")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
row = tableArray.Matrix[rowIdx]
|
|
|
|
row := tableArray.Matrix[rowIdx]
|
|
|
|
if wasExact || !exactMatch {
|
|
|
|
if wasExact || matchMode.Number == matchModeWildcard {
|
|
|
|
return row[matchIdx]
|
|
|
|
return row[matchIdx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found")
|
|
|
@ -9391,6 +9374,43 @@ func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg {
|
|
|
|
return newMatrixFormulaArg(mtx)
|
|
|
|
return newMatrixFormulaArg(mtx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// lookupLinearSearch sequentially checks each look value of the lookup array until
|
|
|
|
|
|
|
|
// a match is found or the whole list has been searched.
|
|
|
|
|
|
|
|
func lookupLinearSearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (int, bool) {
|
|
|
|
|
|
|
|
tableArray := []formulaArg{}
|
|
|
|
|
|
|
|
if vertical {
|
|
|
|
|
|
|
|
for _, row := range lookupArray.Matrix {
|
|
|
|
|
|
|
|
tableArray = append(tableArray, row[0])
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
tableArray = lookupArray.Matrix[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
matchIdx, wasExact := -1, false
|
|
|
|
|
|
|
|
start:
|
|
|
|
|
|
|
|
for i, cell := range tableArray {
|
|
|
|
|
|
|
|
lhs := cell
|
|
|
|
|
|
|
|
if lookupValue.Type == ArgNumber {
|
|
|
|
|
|
|
|
if lhs = cell.ToNumber(); lhs.Type == ArgError {
|
|
|
|
|
|
|
|
lhs = cell
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if lookupValue.Type == ArgMatrix {
|
|
|
|
|
|
|
|
lhs = lookupArray
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if compareFormulaArg(lhs, lookupValue, matchMode, false) == criteriaEq {
|
|
|
|
|
|
|
|
matchIdx = i
|
|
|
|
|
|
|
|
wasExact = true
|
|
|
|
|
|
|
|
if searchMode.Number == searchModeLinear {
|
|
|
|
|
|
|
|
break start
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if matchMode.Number == matchModeMinGreater || matchMode.Number == matchModeMaxLess {
|
|
|
|
|
|
|
|
matchIdx = int(calcMatch(int(matchMode.Number), formulaCriteriaParser(lookupValue.Value()), tableArray).Number)
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return matchIdx, wasExact
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// VLOOKUP function 'looks up' a given value in the left-hand column of a
|
|
|
|
// VLOOKUP function 'looks up' a given value in the left-hand column of a
|
|
|
|
// data array (or table), and returns the corresponding value from another
|
|
|
|
// data array (or table), and returns the corresponding value from another
|
|
|
|
// column of the array. The syntax of the function is:
|
|
|
|
// column of the array. The syntax of the function is:
|
|
|
@ -9398,34 +9418,16 @@ func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg {
|
|
|
|
// VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup])
|
|
|
|
// VLOOKUP(lookup_value,table_array,col_index_num,[range_lookup])
|
|
|
|
//
|
|
|
|
//
|
|
|
|
func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
colIdx, matchIdx, wasExact, exactMatch,
|
|
|
|
colIdx, lookupValue, tableArray, matchMode, errArg := checkHVLookupArgs("VLOOKUP", argsList)
|
|
|
|
lookupValue, tableArray, errArg := checkHVLookupArgs("VLOOKUP", argsList)
|
|
|
|
|
|
|
|
if errArg.Type == ArgError {
|
|
|
|
if errArg.Type == ArgError {
|
|
|
|
return errArg
|
|
|
|
return errArg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if exactMatch || len(tableArray.Matrix) == TotalRows {
|
|
|
|
var matchIdx int
|
|
|
|
start:
|
|
|
|
var wasExact bool
|
|
|
|
for idx, mtx := range tableArray.Matrix {
|
|
|
|
if matchMode.Number == matchModeWildcard || len(tableArray.Matrix) == TotalRows {
|
|
|
|
lhs := mtx[0]
|
|
|
|
matchIdx, wasExact = lookupLinearSearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeLinear))
|
|
|
|
switch lookupValue.Type {
|
|
|
|
|
|
|
|
case ArgNumber:
|
|
|
|
|
|
|
|
if !lookupValue.Boolean {
|
|
|
|
|
|
|
|
lhs = mtx[0].ToNumber()
|
|
|
|
|
|
|
|
if lhs.Type == ArgError {
|
|
|
|
|
|
|
|
lhs = mtx[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
case ArgMatrix:
|
|
|
|
|
|
|
|
lhs = tableArray
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq {
|
|
|
|
|
|
|
|
matchIdx = idx
|
|
|
|
|
|
|
|
wasExact = true
|
|
|
|
|
|
|
|
break start
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
matchIdx, wasExact = vlookupBinarySearch(tableArray, lookupValue)
|
|
|
|
matchIdx, wasExact = lookupBinarySearch(true, lookupValue, tableArray, matchMode, newNumberFormulaArg(searchModeAscBinary))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if matchIdx == -1 {
|
|
|
|
if matchIdx == -1 {
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
|
|
|
@ -9434,67 +9436,52 @@ func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
if colIdx < 0 || colIdx >= len(mtx) {
|
|
|
|
if colIdx < 0 || colIdx >= len(mtx) {
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if wasExact || !exactMatch {
|
|
|
|
if wasExact || matchMode.Number == matchModeWildcard {
|
|
|
|
return mtx[colIdx]
|
|
|
|
return mtx[colIdx]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// vlookupBinarySearch finds the position of a target value when range lookup
|
|
|
|
// lookupBinarySearch finds the position of a target value when range lookup
|
|
|
|
// is TRUE, if the data of table array can't guarantee be sorted, it will
|
|
|
|
// is TRUE, if the data of table array can't guarantee be sorted, it will
|
|
|
|
// return wrong result.
|
|
|
|
// return wrong result.
|
|
|
|
func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
|
|
|
func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, searchMode formulaArg) (matchIdx int, wasExact bool) {
|
|
|
|
var low, high, lastMatchIdx int = 0, len(tableArray.Matrix) - 1, -1
|
|
|
|
tableArray := []formulaArg{}
|
|
|
|
|
|
|
|
if vertical {
|
|
|
|
|
|
|
|
for _, row := range lookupArray.Matrix {
|
|
|
|
|
|
|
|
tableArray = append(tableArray, row[0])
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
tableArray = lookupArray.Matrix[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var low, high, lastMatchIdx int = 0, len(tableArray) - 1, -1
|
|
|
|
|
|
|
|
count := high
|
|
|
|
for low <= high {
|
|
|
|
for low <= high {
|
|
|
|
mid := low + (high-low)/2
|
|
|
|
mid := low + (high-low)/2
|
|
|
|
mtx := tableArray.Matrix[mid]
|
|
|
|
cell := tableArray[mid]
|
|
|
|
lhs := mtx[0]
|
|
|
|
lhs := cell
|
|
|
|
switch lookupValue.Type {
|
|
|
|
if lookupValue.Type == ArgNumber {
|
|
|
|
case ArgNumber:
|
|
|
|
if lhs = cell.ToNumber(); lhs.Type == ArgError {
|
|
|
|
if !lookupValue.Boolean {
|
|
|
|
lhs = cell
|
|
|
|
lhs = mtx[0].ToNumber()
|
|
|
|
|
|
|
|
if lhs.Type == ArgError {
|
|
|
|
|
|
|
|
lhs = mtx[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if lookupValue.Type == ArgMatrix && vertical {
|
|
|
|
|
|
|
|
lhs = lookupArray
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ArgMatrix:
|
|
|
|
result := compareFormulaArg(lhs, lookupValue, matchMode, false)
|
|
|
|
lhs = tableArray
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
result := compareFormulaArg(lhs, lookupValue, false, false)
|
|
|
|
|
|
|
|
if result == criteriaEq {
|
|
|
|
if result == criteriaEq {
|
|
|
|
matchIdx, wasExact = mid, true
|
|
|
|
matchIdx, wasExact = mid, true
|
|
|
|
|
|
|
|
if searchMode.Number == searchModeDescBinary {
|
|
|
|
|
|
|
|
matchIdx = count - matchIdx
|
|
|
|
|
|
|
|
}
|
|
|
|
return
|
|
|
|
return
|
|
|
|
} else if result == criteriaG {
|
|
|
|
} else if result == criteriaG {
|
|
|
|
high = mid - 1
|
|
|
|
high = mid - 1
|
|
|
|
} else if result == criteriaL {
|
|
|
|
} else if result == criteriaL {
|
|
|
|
matchIdx, low = mid, mid+1
|
|
|
|
matchIdx = mid
|
|
|
|
if lhs.Value() != "" {
|
|
|
|
if lhs.Value() != "" {
|
|
|
|
lastMatchIdx = matchIdx
|
|
|
|
lastMatchIdx = matchIdx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
low = mid + 1
|
|
|
|
return -1, false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
matchIdx, wasExact = lastMatchIdx, true
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// vlookupBinarySearch finds the position of a target value when range lookup
|
|
|
|
|
|
|
|
// is TRUE, if the data of table array can't guarantee be sorted, it will
|
|
|
|
|
|
|
|
// return wrong result.
|
|
|
|
|
|
|
|
func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int, wasExact bool) {
|
|
|
|
|
|
|
|
var low, high, lastMatchIdx int = 0, len(row) - 1, -1
|
|
|
|
|
|
|
|
for low <= high {
|
|
|
|
|
|
|
|
mid := low + (high-low)/2
|
|
|
|
|
|
|
|
mtx := row[mid]
|
|
|
|
|
|
|
|
result := compareFormulaArg(mtx, lookupValue, false, false)
|
|
|
|
|
|
|
|
if result == criteriaEq {
|
|
|
|
|
|
|
|
matchIdx, wasExact = mid, true
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
} else if result == criteriaG {
|
|
|
|
|
|
|
|
high = mid - 1
|
|
|
|
|
|
|
|
} else if result == criteriaL {
|
|
|
|
|
|
|
|
low, lastMatchIdx = mid+1, mid
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return -1, false
|
|
|
|
return -1, false
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -9542,7 +9529,7 @@ func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
compare := compareFormulaArg(lhs, col, false, false)
|
|
|
|
compare := compareFormulaArg(lhs, col, newNumberFormulaArg(matchModeMaxLess), false)
|
|
|
|
// Find exact match
|
|
|
|
// Find exact match
|
|
|
|
if compare == criteriaEq {
|
|
|
|
if compare == criteriaEq {
|
|
|
|
matchIdx = idx
|
|
|
|
matchIdx = idx
|
|
|
@ -9584,6 +9571,139 @@ func (fn *formulaFuncs) index(array formulaArg, rowIdx, colIdx int) formulaArg {
|
|
|
|
return newListFormulaArg(cells)
|
|
|
|
return newListFormulaArg(cells)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// validateMatchMode check the number of match mode if be equal to 0, 1, -1 or
|
|
|
|
|
|
|
|
// 2.
|
|
|
|
|
|
|
|
func validateMatchMode(mode float64) bool {
|
|
|
|
|
|
|
|
return mode == matchModeExact || mode == matchModeMinGreater || mode == matchModeMaxLess || mode == matchModeWildcard
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// validateSearchMode check the number of search mode if be equal to 1, -1, 2
|
|
|
|
|
|
|
|
// or -2.
|
|
|
|
|
|
|
|
func validateSearchMode(mode float64) bool {
|
|
|
|
|
|
|
|
return mode == searchModeLinear || mode == searchModeReverseLinear || mode == searchModeAscBinary || mode == searchModeDescBinary
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// prepareXlookupArgs checking and prepare arguments for the formula function
|
|
|
|
|
|
|
|
// XLOOKUP.
|
|
|
|
|
|
|
|
func (fn *formulaFuncs) prepareXlookupArgs(argsList *list.List) formulaArg {
|
|
|
|
|
|
|
|
if argsList.Len() < 3 {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP requires at least 3 arguments")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if argsList.Len() > 6 {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, "XLOOKUP allows at most 6 arguments")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lookupValue := argsList.Front().Value.(formulaArg)
|
|
|
|
|
|
|
|
lookupArray := argsList.Front().Next().Value.(formulaArg)
|
|
|
|
|
|
|
|
returnArray := argsList.Front().Next().Next().Value.(formulaArg)
|
|
|
|
|
|
|
|
ifNotFond := newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
|
|
|
|
|
|
matchMode, searchMode := newNumberFormulaArg(matchModeExact), newNumberFormulaArg(searchModeLinear)
|
|
|
|
|
|
|
|
if argsList.Len() > 3 {
|
|
|
|
|
|
|
|
ifNotFond = argsList.Front().Next().Next().Next().Value.(formulaArg)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if argsList.Len() > 4 {
|
|
|
|
|
|
|
|
if matchMode = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); matchMode.Type != ArgNumber {
|
|
|
|
|
|
|
|
return matchMode
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if argsList.Len() > 5 {
|
|
|
|
|
|
|
|
if searchMode = argsList.Back().Value.(formulaArg).ToNumber(); searchMode.Type != ArgNumber {
|
|
|
|
|
|
|
|
return searchMode
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if lookupArray.Type != ArgMatrix || returnArray.Type != ArgMatrix {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if !validateMatchMode(matchMode.Number) || !validateSearchMode(searchMode.Number) {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return newListFormulaArg([]formulaArg{lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// xlookup is an implementation of the formula function XLOOKUP.
|
|
|
|
|
|
|
|
func (fn *formulaFuncs) xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx int,
|
|
|
|
|
|
|
|
condition1, condition2, condition3, condition4 bool, returnArray formulaArg) formulaArg {
|
|
|
|
|
|
|
|
result := [][]formulaArg{}
|
|
|
|
|
|
|
|
for rowIdx, row := range returnArray.Matrix {
|
|
|
|
|
|
|
|
for colIdx, cell := range row {
|
|
|
|
|
|
|
|
if condition1 {
|
|
|
|
|
|
|
|
if condition2 {
|
|
|
|
|
|
|
|
result = append(result, []formulaArg{cell})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if returnArrayRows > 1 && returnArrayCols > 1 {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if condition3 {
|
|
|
|
|
|
|
|
if returnArrayCols != lookupCols {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if colIdx == matchIdx {
|
|
|
|
|
|
|
|
result = append(result, []formulaArg{cell})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if condition4 {
|
|
|
|
|
|
|
|
if returnArrayRows != lookupRows {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if rowIdx == matchIdx {
|
|
|
|
|
|
|
|
if len(result) == 0 {
|
|
|
|
|
|
|
|
result = append(result, []formulaArg{cell})
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
result[0] = append(result[0], cell)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
array := newMatrixFormulaArg(result)
|
|
|
|
|
|
|
|
cells := array.ToList()
|
|
|
|
|
|
|
|
if len(cells) == 1 {
|
|
|
|
|
|
|
|
return cells[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return array
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// XLOOKUP function searches a range or an array, and then returns the item
|
|
|
|
|
|
|
|
// corresponding to the first match it finds. If no match exists, then
|
|
|
|
|
|
|
|
// XLOOKUP can return the closest (approximate) match. The syntax of the
|
|
|
|
|
|
|
|
// function is:
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// XLOOKUP(lookup_value,lookup_array,return_array,[if_not_found],[match_mode],[search_mode])
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
func (fn *formulaFuncs) XLOOKUP(argsList *list.List) formulaArg {
|
|
|
|
|
|
|
|
args := fn.prepareXlookupArgs(argsList)
|
|
|
|
|
|
|
|
if args.Type != ArgList {
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lookupValue, lookupArray, returnArray, ifNotFond, matchMode, searchMode := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]
|
|
|
|
|
|
|
|
lookupRows, lookupCols := len(lookupArray.Matrix), 0
|
|
|
|
|
|
|
|
if lookupRows > 0 {
|
|
|
|
|
|
|
|
lookupCols = len(lookupArray.Matrix[0])
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if lookupRows != 1 && lookupCols != 1 {
|
|
|
|
|
|
|
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
verticalLookup := lookupRows >= lookupCols
|
|
|
|
|
|
|
|
var matchIdx int
|
|
|
|
|
|
|
|
switch searchMode.Number {
|
|
|
|
|
|
|
|
case searchModeLinear, searchModeReverseLinear:
|
|
|
|
|
|
|
|
matchIdx, _ = lookupLinearSearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode)
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
matchIdx, _ = lookupBinarySearch(verticalLookup, lookupValue, lookupArray, matchMode, searchMode)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if matchIdx == -1 {
|
|
|
|
|
|
|
|
return ifNotFond
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
returnArrayRows, returnArrayCols := len(returnArray.Matrix), len(returnArray.Matrix[0])
|
|
|
|
|
|
|
|
condition1 := lookupRows == 1 && lookupCols == 1
|
|
|
|
|
|
|
|
condition2 := returnArrayRows == 1 || returnArrayCols == 1
|
|
|
|
|
|
|
|
condition3 := lookupRows == 1 && lookupCols > 1
|
|
|
|
|
|
|
|
condition4 := lookupRows > 1 && lookupCols == 1
|
|
|
|
|
|
|
|
return fn.xlookup(lookupRows, lookupCols, returnArrayRows, returnArrayCols, matchIdx, condition1, condition2, condition3, condition4, returnArray)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// INDEX function returns a reference to a cell that lies in a specified row
|
|
|
|
// INDEX function returns a reference to a cell that lies in a specified row
|
|
|
|
// and column of a range of cells. The syntax of the function is:
|
|
|
|
// and column of a range of cells. The syntax of the function is:
|
|
|
|
//
|
|
|
|
//
|
|
|
|