handle default underline type on get rich text; #65 fn: CODE, COLUMN, FIND, FINDB

This commit is contained in:
xuri 2021-02-24 00:37:44 +08:00
parent d84050921e
commit 5a0d885315
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
3 changed files with 169 additions and 18 deletions

132
calc.go
View File

@ -101,14 +101,15 @@ const (
// formulaArg is the argument of a formula or function. // formulaArg is the argument of a formula or function.
type formulaArg struct { type formulaArg struct {
SheetName string SheetName string
Number float64 Number float64
String string String string
List []formulaArg List []formulaArg
Matrix [][]formulaArg Matrix [][]formulaArg
Boolean bool Boolean bool
Error string Error string
Type ArgType Type ArgType
cellRefs, cellRanges *list.List
} }
// Value returns a string data type of the formula argument. // Value returns a string data type of the formula argument.
@ -181,8 +182,8 @@ func (fa formulaArg) ToList() []formulaArg {
// formulaFuncs is the type of the formula functions. // formulaFuncs is the type of the formula functions.
type formulaFuncs struct { type formulaFuncs struct {
f *File f *File
sheet string sheet, cell string
} }
// tokenPriority defined basic arithmetic operator priority. // tokenPriority defined basic arithmetic operator priority.
@ -235,6 +236,8 @@ var tokenPriority = map[string]int{
// CEILING.PRECISE // CEILING.PRECISE
// CHOOSE // CHOOSE
// CLEAN // CLEAN
// CODE
// COLUMN
// COMBIN // COMBIN
// COMBINA // COMBINA
// CONCAT // CONCAT
@ -261,6 +264,8 @@ var tokenPriority = map[string]int{
// FACT // FACT
// FACTDOUBLE // FACTDOUBLE
// FALSE // FALSE
// FIND
// FINDB
// FISHER // FISHER
// FISHERINV // FISHERINV
// FLOOR // FLOOR
@ -363,7 +368,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
if tokens == nil { if tokens == nil {
return return
} }
if token, err = f.evalInfixExp(sheet, tokens); err != nil { if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
return return
} }
result = token.TValue result = token.TValue
@ -446,7 +451,7 @@ func newEmptyFormulaArg() formulaArg {
// //
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union // TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
// //
func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error) { func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, 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()
for i := 0; i < len(tokens); i++ { for i := 0; i < len(tokens); i++ {
@ -537,7 +542,7 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText { if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue)) argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
} }
if err = f.evalInfixExpFunc(sheet, 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 efp.Token{}, err return efp.Token{}, err
} }
} }
@ -556,7 +561,7 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
} }
// evalInfixExpFunc evaluate formula function in the infix expression. // evalInfixExpFunc evaluate formula function in the infix expression.
func (f *File) evalInfixExpFunc(sheet string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error { func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
if !isFunctionStopToken(token) { if !isFunctionStopToken(token) {
return nil return nil
} }
@ -575,7 +580,7 @@ func (f *File) evalInfixExpFunc(sheet string, token, nextToken efp.Token, opfSta
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue)) argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
} }
// call formula function to evaluate // call formula function to evaluate
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet}, strings.NewReplacer( arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue), "_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))}) []reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
if arg.Type == ArgError && opfStack.Len() == 1 { if arg.Type == ArgError && opfStack.Len() == 1 {
@ -1007,6 +1012,7 @@ func prepareValueRef(cr cellRef, valueRange []int) {
// This function will not ignore the empty cell. For example, A1:A2:A2:B3 will // This function will not ignore the empty cell. For example, A1:A2:A2:B3 will
// be reference A1:B3. // be reference A1:B3.
func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, err error) { func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, err error) {
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
// value range order: from row, to row, from column, to column // value range order: from row, to row, from column, to column
valueRange := []int{0, 0, 0, 0} valueRange := []int{0, 0, 0, 0}
var sheet string var sheet string
@ -4581,6 +4587,23 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
return newStringFormulaArg(b.String()) return newStringFormulaArg(b.String())
} }
// CODE function converts the first character of a supplied text string into
// the associated numeric character set code used by your computer. The
// syntax of the function is:
//
// CODE(text)
//
func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "CODE requires 1 argument")
}
text := argsList.Front().Value.(formulaArg).Value()
if len(text) == 0 {
return newNumberFormulaArg(0)
}
return newNumberFormulaArg(float64(text[0]))
}
// CONCAT function joins together a series of supplied text strings into one // CONCAT function joins together a series of supplied text strings into one
// combined text string. // combined text string.
// //
@ -4639,6 +4662,63 @@ func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg {
return newBoolFormulaArg(text1 == text2) return newBoolFormulaArg(text1 == text2)
} }
// FIND function returns the position of a specified character or sub-string
// within a supplied text string. The function is case-sensitive. The syntax
// of the function is:
//
// FIND(find_text,within_text,[start_num])
//
func (fn *formulaFuncs) FIND(argsList *list.List) formulaArg {
return fn.find("FIND", argsList)
}
// FINDB counts each double-byte character as 2 when you have enabled the
// editing of a language that supports DBCS and then set it as the default
// language. Otherwise, FINDB counts each character as 1. The syntax of the
// function is:
//
// FINDB(find_text,within_text,[start_num])
//
func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg {
return fn.find("FINDB", argsList)
}
// find is an implementation of the formula function FIND and FINDB.
func (fn *formulaFuncs) find(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))
}
findText := argsList.Front().Value.(formulaArg).Value()
withinText := argsList.Front().Next().Value.(formulaArg).Value()
startNum, result := 1, 1
if argsList.Len() == 3 {
numArg := argsList.Back().Value.(formulaArg).ToNumber()
if numArg.Type != ArgNumber {
return numArg
}
if numArg.Number < 0 {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
startNum = int(numArg.Number)
}
if findText == "" {
return newNumberFormulaArg(float64(startNum))
}
for idx := range withinText {
if result < startNum {
result++
}
if strings.Index(withinText[idx:], findText) == 0 {
return newNumberFormulaArg(float64(result))
}
result++
}
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
// LEFT function returns a specified number of characters from the start of a // LEFT function returns a specified number of characters from the start of a
// supplied text string. The syntax of the function is: // supplied text string. The syntax of the function is:
// //
@ -5031,6 +5111,28 @@ func compareFormulaArgMatrix(lhs, rhs formulaArg, caseSensitive, exactMatch bool
return criteriaEq return criteriaEq
} }
// COLUMN function returns the first column number within a supplied reference
// or the number of the current column. The syntax of the function is:
//
// COLUMN([reference])
//
func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg {
if argsList.Len() > 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COLUMN requires at most 1 argument")
}
if argsList.Len() == 1 {
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRanges.Front().Value.(cellRange).From.Col))
}
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
return newNumberFormulaArg(float64(argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef).Col))
}
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
}
col, _, _ := CellNameToCoordinates(fn.cell)
return newNumberFormulaArg(float64(col))
}
// HLOOKUP function 'looks up' a given value in the top row of a data array // HLOOKUP function 'looks up' a given value in the top row of a data array
// (or table), and returns the corresponding value from another row of the // (or table), and returns the corresponding value from another row of the
// array. The syntax of the function is: // array. The syntax of the function is:

View File

@ -715,6 +715,12 @@ func TestCalcCellValue(t *testing.T) {
// CLEAN // CLEAN
"=CLEAN(\"\u0009clean text\")": "clean text", "=CLEAN(\"\u0009clean text\")": "clean text",
"=CLEAN(0)": "0", "=CLEAN(0)": "0",
// CODE
"=CODE(\"Alpha\")": "65",
"=CODE(\"alpha\")": "97",
"=CODE(\"?\")": "63",
"=CODE(\"3\")": "51",
"=CODE(\"\")": "0",
// CONCAT // CONCAT
"=CONCAT(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02", "=CONCAT(TRUE(),1,FALSE(),\"0\",INT(2))": "TRUE1FALSE02",
// CONCATENATE // CONCATENATE
@ -723,6 +729,20 @@ func TestCalcCellValue(t *testing.T) {
"=EXACT(1,\"1\")": "TRUE", "=EXACT(1,\"1\")": "TRUE",
"=EXACT(1,1)": "TRUE", "=EXACT(1,1)": "TRUE",
"=EXACT(\"A\",\"a\")": "FALSE", "=EXACT(\"A\",\"a\")": "FALSE",
// FIND
"=FIND(\"T\",\"Original Text\")": "10",
"=FIND(\"t\",\"Original Text\")": "13",
"=FIND(\"i\",\"Original Text\")": "3",
"=FIND(\"i\",\"Original Text\",4)": "5",
"=FIND(\"\",\"Original Text\")": "1",
"=FIND(\"\",\"Original Text\",2)": "2",
// FINDB
"=FINDB(\"T\",\"Original Text\")": "10",
"=FINDB(\"t\",\"Original Text\")": "13",
"=FINDB(\"i\",\"Original Text\")": "3",
"=FINDB(\"i\",\"Original Text\",4)": "5",
"=FINDB(\"\",\"Original Text\")": "1",
"=FINDB(\"\",\"Original Text\",2)": "2",
// LEFT // LEFT
"=LEFT(\"Original Text\")": "O", "=LEFT(\"Original Text\")": "O",
"=LEFT(\"Original Text\",4)": "Orig", "=LEFT(\"Original Text\",4)": "Orig",
@ -786,6 +806,12 @@ func TestCalcCellValue(t *testing.T) {
"=CHOOSE(4,\"red\",\"blue\",\"green\",\"brown\")": "brown", "=CHOOSE(4,\"red\",\"blue\",\"green\",\"brown\")": "brown",
"=CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red", "=CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red",
"=SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))": "9", "=SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))": "9",
// COLUMN
"=COLUMN()": "3",
"=COLUMN(Sheet1!A1)": "1",
"=COLUMN(Sheet1!A1:B1:C1)": "1",
"=COLUMN(Sheet1!F1:G1)": "6",
"=COLUMN(H1)": "8",
// HLOOKUP // HLOOKUP
"=HLOOKUP(D2,D2:D8,1,FALSE)": "Jan", "=HLOOKUP(D2,D2:D8,1,FALSE)": "Jan",
"=HLOOKUP(F3,F3:F8,3,FALSE)": "34440", "=HLOOKUP(F3,F3:F8,3,FALSE)": "34440",
@ -1325,6 +1351,9 @@ func TestCalcCellValue(t *testing.T) {
// CLEAN // CLEAN
"=CLEAN()": "CLEAN requires 1 argument", "=CLEAN()": "CLEAN requires 1 argument",
"=CLEAN(1,2)": "CLEAN requires 1 argument", "=CLEAN(1,2)": "CLEAN requires 1 argument",
// CODE
"=CODE()": "CODE requires 1 argument",
"=CODE(1,2)": "CODE requires 1 argument",
// CONCAT // CONCAT
"=CONCAT(MUNIT(2))": "CONCAT requires arguments to be strings", "=CONCAT(MUNIT(2))": "CONCAT requires arguments to be strings",
// CONCATENATE // CONCATENATE
@ -1332,6 +1361,18 @@ func TestCalcCellValue(t *testing.T) {
// EXACT // EXACT
"=EXACT()": "EXACT requires 2 arguments", "=EXACT()": "EXACT requires 2 arguments",
"=EXACT(1,2,3)": "EXACT requires 2 arguments", "=EXACT(1,2,3)": "EXACT requires 2 arguments",
// FIND
"=FIND()": "FIND requires at least 2 arguments",
"=FIND(1,2,3,4)": "FIND allows at most 3 arguments",
"=FIND(\"x\",\"\")": "#VALUE!",
"=FIND(\"x\",\"x\",-1)": "#VALUE!",
"=FIND(\"x\",\"x\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
// FINDB
"=FINDB()": "FINDB requires at least 2 arguments",
"=FINDB(1,2,3,4)": "FINDB allows at most 3 arguments",
"=FINDB(\"x\",\"\")": "#VALUE!",
"=FINDB(\"x\",\"x\",-1)": "#VALUE!",
"=FINDB(\"x\",\"x\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
// LEFT // LEFT
"=LEFT()": "LEFT requires at least 1 argument", "=LEFT()": "LEFT requires at least 1 argument",
"=LEFT(\"\",2,3)": "LEFT allows at most 2 arguments", "=LEFT(\"\",2,3)": "LEFT allows at most 2 arguments",
@ -1383,6 +1424,11 @@ func TestCalcCellValue(t *testing.T) {
"=CHOOSE()": "CHOOSE requires 2 arguments", "=CHOOSE()": "CHOOSE requires 2 arguments",
"=CHOOSE(\"index_num\",0)": "CHOOSE requires first argument of type number", "=CHOOSE(\"index_num\",0)": "CHOOSE requires first argument of type number",
"=CHOOSE(2,0)": "index_num should be <= to the number of values", "=CHOOSE(2,0)": "index_num should be <= to the number of values",
// COLUMN
"=COLUMN(1,2)": "COLUMN requires at most 1 argument",
"=COLUMN(\"\")": "invalid reference",
"=COLUMN(Sheet1)": "invalid column name \"Sheet1\"",
"=COLUMN(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
// HLOOKUP // HLOOKUP
"=HLOOKUP()": "HLOOKUP requires at least 3 arguments", "=HLOOKUP()": "HLOOKUP requires at least 3 arguments",
"=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array", "=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array",

View File

@ -519,11 +519,14 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
Text: v.T.Val, Text: v.T.Val,
} }
if nil != v.RPr { if nil != v.RPr {
font := Font{} font := Font{Underline: "none"}
font.Bold = v.RPr.B != nil font.Bold = v.RPr.B != nil
font.Italic = v.RPr.I != nil font.Italic = v.RPr.I != nil
if v.RPr.U != nil && v.RPr.U.Val != nil { if v.RPr.U != nil {
font.Underline = *v.RPr.U.Val font.Underline = "single"
if v.RPr.U.Val != nil {
font.Underline = *v.RPr.U.Val
}
} }
if v.RPr.RFont != nil && v.RPr.RFont.Val != nil { if v.RPr.RFont != nil && v.RPr.RFont.Val != nil {
font.Family = *v.RPr.RFont.Val font.Family = *v.RPr.RFont.Val