forked from p30928647/excelize
This improves compatibility for absolute XML path, Windows-style directory separator and inline namespace;
This commit is contained in:
parent
5a0d885315
commit
afe2ebc261
352
calc.go
352
calc.go
|
@ -234,10 +234,12 @@ var tokenPriority = map[string]int{
|
|||
// CEILING
|
||||
// CEILING.MATH
|
||||
// CEILING.PRECISE
|
||||
// CHAR
|
||||
// CHOOSE
|
||||
// CLEAN
|
||||
// CODE
|
||||
// COLUMN
|
||||
// COLUMNS
|
||||
// COMBIN
|
||||
// COMBINA
|
||||
// CONCAT
|
||||
|
@ -305,6 +307,8 @@ var tokenPriority = map[string]int{
|
|||
// MAX
|
||||
// MDETERM
|
||||
// MEDIAN
|
||||
// MID
|
||||
// MIDB
|
||||
// MIN
|
||||
// MINA
|
||||
// MOD
|
||||
|
@ -327,6 +331,8 @@ var tokenPriority = map[string]int{
|
|||
// RADIANS
|
||||
// RAND
|
||||
// RANDBETWEEN
|
||||
// REPLACE
|
||||
// REPLACEB
|
||||
// REPT
|
||||
// RIGHT
|
||||
// RIGHTB
|
||||
|
@ -334,6 +340,8 @@ var tokenPriority = map[string]int{
|
|||
// ROUND
|
||||
// ROUNDDOWN
|
||||
// ROUNDUP
|
||||
// ROW
|
||||
// ROWS
|
||||
// SEC
|
||||
// SECH
|
||||
// SHEET
|
||||
|
@ -352,6 +360,8 @@ var tokenPriority = map[string]int{
|
|||
// TRIM
|
||||
// TRUE
|
||||
// TRUNC
|
||||
// UNICHAR
|
||||
// UNICODE
|
||||
// UPPER
|
||||
// VLOOKUP
|
||||
//
|
||||
|
@ -932,8 +942,17 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
|
|||
cr := cellRef{}
|
||||
if len(tokens) == 2 { // have a worksheet name
|
||||
cr.Sheet = tokens[0]
|
||||
// cast to cell coordinates
|
||||
if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[1]); err != nil {
|
||||
return
|
||||
// cast to column
|
||||
if cr.Col, err = ColumnNameToNumber(tokens[1]); err != nil {
|
||||
// cast to row
|
||||
if cr.Row, err = strconv.Atoi(tokens[1]); err != nil {
|
||||
err = newInvalidColumnNameError(tokens[1])
|
||||
return
|
||||
}
|
||||
cr.Col = TotalColumns
|
||||
}
|
||||
}
|
||||
if refs.Len() > 0 {
|
||||
e := refs.Back()
|
||||
|
@ -943,9 +962,16 @@ func (f *File) parseReference(sheet, reference string) (arg formulaArg, err erro
|
|||
refs.PushBack(cr)
|
||||
continue
|
||||
}
|
||||
// cast to cell coordinates
|
||||
if cr.Col, cr.Row, err = CellNameToCoordinates(tokens[0]); err != nil {
|
||||
// cast to column
|
||||
if cr.Col, err = ColumnNameToNumber(tokens[0]); err != nil {
|
||||
return
|
||||
// cast to row
|
||||
if cr.Row, err = strconv.Atoi(tokens[0]); err != nil {
|
||||
err = newInvalidColumnNameError(tokens[0])
|
||||
return
|
||||
}
|
||||
cr.Col = TotalColumns
|
||||
}
|
||||
cellRanges.PushBack(cellRange{
|
||||
From: cellRef{Sheet: sheet, Col: cr.Col, Row: 1},
|
||||
|
@ -1329,7 +1355,7 @@ func (fn *formulaFuncs) bitwise(name string, argsList *list.List) formulaArg {
|
|||
"BITRSHIFT": func(a, b int) int { return a >> uint(b) },
|
||||
"BITXOR": func(a, b int) int { return a ^ b },
|
||||
}
|
||||
bitwiseFunc, _ := bitwiseFuncMap[name]
|
||||
bitwiseFunc := bitwiseFuncMap[name]
|
||||
return newNumberFormulaArg(float64(bitwiseFunc(int(num1.Number), int(num2.Number))))
|
||||
}
|
||||
|
||||
|
@ -4569,6 +4595,26 @@ func daysBetween(startDate, endDate int64) float64 {
|
|||
|
||||
// Text Functions
|
||||
|
||||
// CHAR function returns the character relating to a supplied character set
|
||||
// number (from 1 to 255). syntax of the function is:
|
||||
//
|
||||
// CHAR(number)
|
||||
//
|
||||
func (fn *formulaFuncs) CHAR(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "CHAR requires 1 argument")
|
||||
}
|
||||
arg := argsList.Front().Value.(formulaArg).ToNumber()
|
||||
if arg.Type != ArgNumber {
|
||||
return arg
|
||||
}
|
||||
num := int(arg.Number)
|
||||
if num < 0 || num > 255 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
return newStringFormulaArg(fmt.Sprintf("%c", num))
|
||||
}
|
||||
|
||||
// CLEAN removes all non-printable characters from a supplied text string. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
|
@ -4594,12 +4640,20 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
|
|||
// CODE(text)
|
||||
//
|
||||
func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg {
|
||||
return fn.code("CODE", argsList)
|
||||
}
|
||||
|
||||
// code is an implementation of the formula function CODE and UNICODE.
|
||||
func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "CODE requires 1 argument")
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name))
|
||||
}
|
||||
text := argsList.Front().Value.(formulaArg).Value()
|
||||
if len(text) == 0 {
|
||||
return newNumberFormulaArg(0)
|
||||
if name == "CODE" {
|
||||
return newNumberFormulaArg(0)
|
||||
}
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
return newNumberFormulaArg(float64(text[0]))
|
||||
}
|
||||
|
@ -4795,19 +4849,6 @@ func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg {
|
|||
return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String)))
|
||||
}
|
||||
|
||||
// TRIM removes extra spaces (i.e. all spaces except for single spaces between
|
||||
// words or characters) from a supplied text string. The syntax of the
|
||||
// function is:
|
||||
//
|
||||
// TRIM(text)
|
||||
//
|
||||
func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
|
||||
}
|
||||
return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String))
|
||||
}
|
||||
|
||||
// LOWER converts all characters in a supplied text string to lower case. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
|
@ -4820,6 +4861,56 @@ func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg {
|
|||
return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).String))
|
||||
}
|
||||
|
||||
// MID function returns a specified number of characters from the middle of a
|
||||
// supplied text string. The syntax of the function is:
|
||||
//
|
||||
// MID(text,start_num,num_chars)
|
||||
//
|
||||
func (fn *formulaFuncs) MID(argsList *list.List) formulaArg {
|
||||
return fn.mid("MID", argsList)
|
||||
}
|
||||
|
||||
// MIDB returns a specific number of characters from a text string, starting
|
||||
// at the position you specify, based on the number of bytes you specify. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
// MID(text,start_num,num_chars)
|
||||
//
|
||||
func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg {
|
||||
return fn.mid("MIDB", argsList)
|
||||
}
|
||||
|
||||
// mid is an implementation of the formula function MID and MIDB. TODO:
|
||||
// support DBCS include Japanese, Chinese (Simplified), Chinese
|
||||
// (Traditional), and Korean.
|
||||
func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 3 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 3 arguments", name))
|
||||
}
|
||||
text := argsList.Front().Value.(formulaArg).Value()
|
||||
startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if startNumArg.Type != ArgNumber {
|
||||
return startNumArg
|
||||
}
|
||||
if numCharsArg.Type != ArgNumber {
|
||||
return numCharsArg
|
||||
}
|
||||
startNum := int(startNumArg.Number)
|
||||
if startNum < 0 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
textLen := len(text)
|
||||
if startNum > textLen {
|
||||
return newStringFormulaArg("")
|
||||
}
|
||||
startNum--
|
||||
endNum := startNum + int(numCharsArg.Number)
|
||||
if endNum > textLen+1 {
|
||||
return newStringFormulaArg(text[startNum:])
|
||||
}
|
||||
return newStringFormulaArg(text[startNum:endNum])
|
||||
}
|
||||
|
||||
// PROPER converts all characters in a supplied text string to proper case
|
||||
// (i.e. all letters that do not immediately follow another letter are set to
|
||||
// upper case and all other characters are lower case). The syntax of the
|
||||
|
@ -4844,6 +4935,54 @@ func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg {
|
|||
return newStringFormulaArg(buf.String())
|
||||
}
|
||||
|
||||
// REPLACE function replaces all or part of a text string with another string.
|
||||
// The syntax of the function is:
|
||||
//
|
||||
// REPLACE(old_text,start_num,num_chars,new_text)
|
||||
//
|
||||
func (fn *formulaFuncs) REPLACE(argsList *list.List) formulaArg {
|
||||
return fn.replace("REPLACE", argsList)
|
||||
}
|
||||
|
||||
// REPLACEB replaces part of a text string, based on the number of bytes you
|
||||
// specify, with a different text string.
|
||||
//
|
||||
// REPLACEB(old_text,start_num,num_chars,new_text)
|
||||
//
|
||||
func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg {
|
||||
return fn.replace("REPLACEB", argsList)
|
||||
}
|
||||
|
||||
// replace is an implementation of the formula function REPLACE and REPLACEB.
|
||||
// TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
|
||||
// (Traditional), and Korean.
|
||||
func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 4 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 arguments", name))
|
||||
}
|
||||
oldText, newText := argsList.Front().Value.(formulaArg).Value(), argsList.Back().Value.(formulaArg).Value()
|
||||
startNumArg, numCharsArg := argsList.Front().Next().Value.(formulaArg).ToNumber(), argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
||||
if startNumArg.Type != ArgNumber {
|
||||
return startNumArg
|
||||
}
|
||||
if numCharsArg.Type != ArgNumber {
|
||||
return numCharsArg
|
||||
}
|
||||
oldTextLen, startIdx := len(oldText), int(startNumArg.Number)
|
||||
if startIdx > oldTextLen {
|
||||
startIdx = oldTextLen + 1
|
||||
}
|
||||
endIdx := startIdx + int(numCharsArg.Number)
|
||||
if endIdx > oldTextLen {
|
||||
endIdx = oldTextLen + 1
|
||||
}
|
||||
if startIdx < 1 || endIdx < 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
result := oldText[:startIdx-1] + newText + oldText[endIdx-1:]
|
||||
return newStringFormulaArg(result)
|
||||
}
|
||||
|
||||
// REPT function returns a supplied text string, repeated a specified number
|
||||
// of times. The syntax of the function is:
|
||||
//
|
||||
|
@ -4892,6 +5031,47 @@ func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg {
|
|||
return fn.leftRight("RIGHTB", argsList)
|
||||
}
|
||||
|
||||
// TRIM removes extra spaces (i.e. all spaces except for single spaces between
|
||||
// words or characters) from a supplied text string. The syntax of the
|
||||
// function is:
|
||||
//
|
||||
// TRIM(text)
|
||||
//
|
||||
func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
|
||||
}
|
||||
return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String))
|
||||
}
|
||||
|
||||
// UNICHAR returns the Unicode character that is referenced by the given
|
||||
// numeric value. The syntax of the function is:
|
||||
//
|
||||
// UNICHAR(number)
|
||||
//
|
||||
func (fn *formulaFuncs) UNICHAR(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "UNICHAR requires 1 argument")
|
||||
}
|
||||
numArg := argsList.Front().Value.(formulaArg).ToNumber()
|
||||
if numArg.Type != ArgNumber {
|
||||
return numArg
|
||||
}
|
||||
if numArg.Number <= 0 || numArg.Number > 55295 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||
}
|
||||
return newStringFormulaArg(string(rune(numArg.Number)))
|
||||
}
|
||||
|
||||
// UNICODE function returns the code point for the first character of a
|
||||
// supplied text string. The syntax of the function is:
|
||||
//
|
||||
// UNICODE(text)
|
||||
//
|
||||
func (fn *formulaFuncs) UNICODE(argsList *list.List) formulaArg {
|
||||
return fn.code("UNICODE", argsList)
|
||||
}
|
||||
|
||||
// UPPER converts all characters in a supplied text string to upper case. The
|
||||
// syntax of the function is:
|
||||
//
|
||||
|
@ -5133,6 +5313,63 @@ func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg {
|
|||
return newNumberFormulaArg(float64(col))
|
||||
}
|
||||
|
||||
// COLUMNS function receives an Excel range and returns the number of columns
|
||||
// that are contained within the range. The syntax of the function is:
|
||||
//
|
||||
// COLUMNS(array)
|
||||
//
|
||||
func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
|
||||
}
|
||||
var min, max int
|
||||
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
|
||||
crs := argsList.Front().Value.(formulaArg).cellRanges
|
||||
for cr := crs.Front(); cr != nil; cr = cr.Next() {
|
||||
if min == 0 {
|
||||
min = cr.Value.(cellRange).From.Col
|
||||
}
|
||||
if min > cr.Value.(cellRange).From.Col {
|
||||
min = cr.Value.(cellRange).From.Col
|
||||
}
|
||||
if min > cr.Value.(cellRange).To.Col {
|
||||
min = cr.Value.(cellRange).To.Col
|
||||
}
|
||||
if max < cr.Value.(cellRange).To.Col {
|
||||
max = cr.Value.(cellRange).To.Col
|
||||
}
|
||||
if max < cr.Value.(cellRange).From.Col {
|
||||
max = cr.Value.(cellRange).From.Col
|
||||
}
|
||||
}
|
||||
}
|
||||
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
|
||||
cr := argsList.Front().Value.(formulaArg).cellRefs
|
||||
for refs := cr.Front(); refs != nil; refs = refs.Next() {
|
||||
if min == 0 {
|
||||
min = refs.Value.(cellRef).Col
|
||||
}
|
||||
if min > refs.Value.(cellRef).Col {
|
||||
min = refs.Value.(cellRef).Col
|
||||
}
|
||||
if max < refs.Value.(cellRef).Col {
|
||||
max = refs.Value.(cellRef).Col
|
||||
}
|
||||
}
|
||||
}
|
||||
if max == TotalColumns {
|
||||
return newNumberFormulaArg(float64(TotalColumns))
|
||||
}
|
||||
result := max - min + 1
|
||||
if max == min {
|
||||
if min == 0 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
|
||||
}
|
||||
return newNumberFormulaArg(float64(1))
|
||||
}
|
||||
return newNumberFormulaArg(float64(result))
|
||||
}
|
||||
|
||||
// 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
|
||||
// array. The syntax of the function is:
|
||||
|
@ -5396,6 +5633,85 @@ func lookupCol(arr formulaArg) []formulaArg {
|
|||
return col
|
||||
}
|
||||
|
||||
// ROW function returns the first row number within a supplied reference or
|
||||
// the number of the current row. The syntax of the function is:
|
||||
//
|
||||
// ROW([reference])
|
||||
//
|
||||
func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg {
|
||||
if argsList.Len() > 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ROW 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.Row))
|
||||
}
|
||||
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).Row))
|
||||
}
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
|
||||
}
|
||||
_, row, _ := CellNameToCoordinates(fn.cell)
|
||||
return newNumberFormulaArg(float64(row))
|
||||
}
|
||||
|
||||
// ROWS function takes an Excel range and returns the number of rows that are
|
||||
// contained within the range. The syntax of the function is:
|
||||
//
|
||||
// ROWS(array)
|
||||
//
|
||||
func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg {
|
||||
if argsList.Len() != 1 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
|
||||
}
|
||||
var min, max int
|
||||
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
|
||||
crs := argsList.Front().Value.(formulaArg).cellRanges
|
||||
for cr := crs.Front(); cr != nil; cr = cr.Next() {
|
||||
if min == 0 {
|
||||
min = cr.Value.(cellRange).From.Row
|
||||
}
|
||||
if min > cr.Value.(cellRange).From.Row {
|
||||
min = cr.Value.(cellRange).From.Row
|
||||
}
|
||||
if min > cr.Value.(cellRange).To.Row {
|
||||
min = cr.Value.(cellRange).To.Row
|
||||
}
|
||||
if max < cr.Value.(cellRange).To.Row {
|
||||
max = cr.Value.(cellRange).To.Row
|
||||
}
|
||||
if max < cr.Value.(cellRange).From.Row {
|
||||
max = cr.Value.(cellRange).From.Row
|
||||
}
|
||||
}
|
||||
}
|
||||
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
|
||||
cr := argsList.Front().Value.(formulaArg).cellRefs
|
||||
for refs := cr.Front(); refs != nil; refs = refs.Next() {
|
||||
if min == 0 {
|
||||
min = refs.Value.(cellRef).Row
|
||||
}
|
||||
if min > refs.Value.(cellRef).Row {
|
||||
min = refs.Value.(cellRef).Row
|
||||
}
|
||||
if max < refs.Value.(cellRef).Row {
|
||||
max = refs.Value.(cellRef).Row
|
||||
}
|
||||
}
|
||||
}
|
||||
if max == TotalRows {
|
||||
return newStringFormulaArg(strconv.Itoa(TotalRows))
|
||||
}
|
||||
result := max - min + 1
|
||||
if max == min {
|
||||
if min == 0 {
|
||||
return newErrorFormulaArg(formulaErrorVALUE, "invalid reference")
|
||||
}
|
||||
return newNumberFormulaArg(float64(1))
|
||||
}
|
||||
return newStringFormulaArg(strconv.Itoa(result))
|
||||
}
|
||||
|
||||
// Web Functions
|
||||
|
||||
// ENCODEURL function returns a URL-encoded string, replacing certain
|
||||
|
|
131
calc_test.go
131
calc_test.go
|
@ -712,6 +712,11 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=DATE(2020,10,21)": "2020-10-21 00:00:00 +0000 UTC",
|
||||
"=DATE(1900,1,1)": "1899-12-31 00:00:00 +0000 UTC",
|
||||
// Text Functions
|
||||
// CHAR
|
||||
"=CHAR(65)": "A",
|
||||
"=CHAR(97)": "a",
|
||||
"=CHAR(63)": "?",
|
||||
"=CHAR(51)": "3",
|
||||
// CLEAN
|
||||
"=CLEAN(\"\u0009clean text\")": "clean text",
|
||||
"=CLEAN(0)": "0",
|
||||
|
@ -761,19 +766,38 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// LENB
|
||||
"=LENB(\"\")": "0",
|
||||
"=LENB(D1)": "5",
|
||||
// TRIM
|
||||
"=TRIM(\" trim text \")": "trim text",
|
||||
"=TRIM(0)": "0",
|
||||
// LOWER
|
||||
"=LOWER(\"test\")": "test",
|
||||
"=LOWER(\"TEST\")": "test",
|
||||
"=LOWER(\"Test\")": "test",
|
||||
"=LOWER(\"TEST 123\")": "test 123",
|
||||
// MID
|
||||
"=MID(\"Original Text\",7,1)": "a",
|
||||
"=MID(\"Original Text\",4,7)": "ginal T",
|
||||
"=MID(\"255 years\",3,1)": "5",
|
||||
"=MID(\"text\",3,6)": "xt",
|
||||
"=MID(\"text\",6,0)": "",
|
||||
// MIDB
|
||||
"=MIDB(\"Original Text\",7,1)": "a",
|
||||
"=MIDB(\"Original Text\",4,7)": "ginal T",
|
||||
"=MIDB(\"255 years\",3,1)": "5",
|
||||
"=MIDB(\"text\",3,6)": "xt",
|
||||
"=MIDB(\"text\",6,0)": "",
|
||||
// PROPER
|
||||
"=PROPER(\"this is a test sentence\")": "This Is A Test Sentence",
|
||||
"=PROPER(\"THIS IS A TEST SENTENCE\")": "This Is A Test Sentence",
|
||||
"=PROPER(\"123tEST teXT\")": "123Test Text",
|
||||
"=PROPER(\"Mr. SMITH's address\")": "Mr. Smith'S Address",
|
||||
// REPLACE
|
||||
"=REPLACE(\"test string\",7,3,\"X\")": "test sXng",
|
||||
"=REPLACE(\"second test string\",8,4,\"XXX\")": "second XXX string",
|
||||
"=REPLACE(\"text\",5,0,\" and char\")": "text and char",
|
||||
"=REPLACE(\"text\",1,20,\"char and \")": "char and ",
|
||||
// REPLACEB
|
||||
"=REPLACEB(\"test string\",7,3,\"X\")": "test sXng",
|
||||
"=REPLACEB(\"second test string\",8,4,\"XXX\")": "second XXX string",
|
||||
"=REPLACEB(\"text\",5,0,\" and char\")": "text and char",
|
||||
"=REPLACEB(\"text\",1,20,\"char and \")": "char and ",
|
||||
// REPT
|
||||
"=REPT(\"*\",0)": "",
|
||||
"=REPT(\"*\",1)": "*",
|
||||
|
@ -790,6 +814,19 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=RIGHTB(\"Original Text\",0)": "",
|
||||
"=RIGHTB(\"Original Text\",13)": "Original Text",
|
||||
"=RIGHTB(\"Original Text\",20)": "Original Text",
|
||||
// TRIM
|
||||
"=TRIM(\" trim text \")": "trim text",
|
||||
"=TRIM(0)": "0",
|
||||
// UNICHAR
|
||||
"=UNICHAR(65)": "A",
|
||||
"=UNICHAR(97)": "a",
|
||||
"=UNICHAR(63)": "?",
|
||||
"=UNICHAR(51)": "3",
|
||||
// UNICODE
|
||||
"=UNICODE(\"Alpha\")": "65",
|
||||
"=UNICODE(\"alpha\")": "97",
|
||||
"=UNICODE(\"?\")": "63",
|
||||
"=UNICODE(\"3\")": "51",
|
||||
// UPPER
|
||||
"=UPPER(\"test\")": "TEST",
|
||||
"=UPPER(\"TEST\")": "TEST",
|
||||
|
@ -812,6 +849,15 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=COLUMN(Sheet1!A1:B1:C1)": "1",
|
||||
"=COLUMN(Sheet1!F1:G1)": "6",
|
||||
"=COLUMN(H1)": "8",
|
||||
// COLUMNS
|
||||
"=COLUMNS(B1)": "1",
|
||||
"=COLUMNS(1:1)": "16384",
|
||||
"=COLUMNS(Sheet1!1:1)": "16384",
|
||||
"=COLUMNS(B1:E5)": "4",
|
||||
"=COLUMNS(Sheet1!E5:H7:B1)": "7",
|
||||
"=COLUMNS(E5:H7:B1:C1:Z1:C1:B1)": "25",
|
||||
"=COLUMNS(E5:B1)": "4",
|
||||
"=COLUMNS(EM38:HZ81)": "92",
|
||||
// HLOOKUP
|
||||
"=HLOOKUP(D2,D2:D8,1,FALSE)": "Jan",
|
||||
"=HLOOKUP(F3,F3:F8,3,FALSE)": "34440",
|
||||
|
@ -832,6 +878,21 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=LOOKUP(F8,F8:F9,F8:F9)": "32080",
|
||||
"=LOOKUP(F8,F8:F9,D8:D9)": "Feb",
|
||||
"=LOOKUP(1,MUNIT(1),MUNIT(1))": "1",
|
||||
// ROW
|
||||
"=ROW()": "1",
|
||||
"=ROW(Sheet1!A1)": "1",
|
||||
"=ROW(Sheet1!A1:B2:C3)": "1",
|
||||
"=ROW(Sheet1!F5:G6)": "5",
|
||||
"=ROW(A8)": "8",
|
||||
// ROWS
|
||||
"=ROWS(B1)": "1",
|
||||
"=ROWS(B:B)": "1048576",
|
||||
"=ROWS(Sheet1!B:B)": "1048576",
|
||||
"=ROWS(B1:E5)": "5",
|
||||
"=ROWS(Sheet1!E5:H7:B1)": "7",
|
||||
"=ROWS(E5:H8:B2:C3:Z26:C3:B2)": "25",
|
||||
"=ROWS(E5:B1)": "5",
|
||||
"=ROWS(EM38:HZ81)": "44",
|
||||
// Web Functions
|
||||
// ENCODEURL
|
||||
"=ENCODEURL(\"https://xuri.me/excelize/en/?q=Save As\")": "https%3A%2F%2Fxuri.me%2Fexcelize%2Fen%2F%3Fq%3DSave%20As",
|
||||
|
@ -1348,6 +1409,11 @@ func TestCalcCellValue(t *testing.T) {
|
|||
`=DATE(2020,"text",21)`: "DATE requires 3 number arguments",
|
||||
`=DATE(2020,10,"text")`: "DATE requires 3 number arguments",
|
||||
// Text Functions
|
||||
// CHAR
|
||||
"=CHAR()": "CHAR requires 1 argument",
|
||||
"=CHAR(-1)": "#VALUE!",
|
||||
"=CHAR(256)": "#VALUE!",
|
||||
"=CHAR(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// CLEAN
|
||||
"=CLEAN()": "CLEAN requires 1 argument",
|
||||
"=CLEAN(1,2)": "CLEAN requires 1 argument",
|
||||
|
@ -1387,18 +1453,32 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=LEN()": "LEN requires 1 string argument",
|
||||
// LENB
|
||||
"=LENB()": "LENB requires 1 string argument",
|
||||
// TRIM
|
||||
"=TRIM()": "TRIM requires 1 argument",
|
||||
"=TRIM(1,2)": "TRIM requires 1 argument",
|
||||
// LOWER
|
||||
"=LOWER()": "LOWER requires 1 argument",
|
||||
"=LOWER(1,2)": "LOWER requires 1 argument",
|
||||
// UPPER
|
||||
"=UPPER()": "UPPER requires 1 argument",
|
||||
"=UPPER(1,2)": "UPPER requires 1 argument",
|
||||
// MID
|
||||
"=MID()": "MID requires 3 arguments",
|
||||
"=MID(\"\",-1,1)": "#VALUE!",
|
||||
"=MID(\"\",\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=MID(\"\",1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// MIDB
|
||||
"=MIDB()": "MIDB requires 3 arguments",
|
||||
"=MIDB(\"\",-1,1)": "#VALUE!",
|
||||
"=MIDB(\"\",\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=MIDB(\"\",1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// PROPER
|
||||
"=PROPER()": "PROPER requires 1 argument",
|
||||
"=PROPER(1,2)": "PROPER requires 1 argument",
|
||||
// REPLACE
|
||||
"=REPLACE()": "REPLACE requires 4 arguments",
|
||||
"=REPLACE(\"text\",0,4,\"string\")": "#VALUE!",
|
||||
"=REPLACE(\"text\",\"\",0,\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=REPLACE(\"text\",1,\"\",\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// REPLACEB
|
||||
"=REPLACEB()": "REPLACEB requires 4 arguments",
|
||||
"=REPLACEB(\"text\",0,4,\"string\")": "#VALUE!",
|
||||
"=REPLACEB(\"text\",\"\",0,\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=REPLACEB(\"text\",1,\"\",\"string\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
// REPT
|
||||
"=REPT()": "REPT requires 2 arguments",
|
||||
"=REPT(INT(0),2)": "REPT requires first argument to be a string",
|
||||
|
@ -1414,6 +1494,20 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=RIGHTB(\"\",2,3)": "RIGHTB allows at most 2 arguments",
|
||||
"=RIGHTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=RIGHTB(\"\",-1)": "#VALUE!",
|
||||
// TRIM
|
||||
"=TRIM()": "TRIM requires 1 argument",
|
||||
"=TRIM(1,2)": "TRIM requires 1 argument",
|
||||
// UNICHAR
|
||||
"=UNICHAR()": "UNICHAR requires 1 argument",
|
||||
"=UNICHAR(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||
"=UNICHAR(55296)": "#VALUE!",
|
||||
"=UNICHAR(0)": "#VALUE!",
|
||||
// UNICODE
|
||||
"=UNICODE()": "UNICODE requires 1 argument",
|
||||
"=UNICODE(\"\")": "#VALUE!",
|
||||
// UPPER
|
||||
"=UPPER()": "UPPER requires 1 argument",
|
||||
"=UPPER(1,2)": "UPPER requires 1 argument",
|
||||
// Conditional Functions
|
||||
// IF
|
||||
"=IF()": "IF requires at least 1 argument",
|
||||
|
@ -1429,6 +1523,13 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=COLUMN(\"\")": "invalid reference",
|
||||
"=COLUMN(Sheet1)": "invalid column name \"Sheet1\"",
|
||||
"=COLUMN(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
|
||||
// COLUMNS
|
||||
"=COLUMNS()": "COLUMNS requires 1 argument",
|
||||
"=COLUMNS(1)": "invalid reference",
|
||||
"=COLUMNS(\"\")": "invalid reference",
|
||||
"=COLUMNS(Sheet1)": "invalid column name \"Sheet1\"",
|
||||
"=COLUMNS(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
|
||||
"=COLUMNS(Sheet1!Sheet1)": "invalid column name \"Sheet1\"",
|
||||
// HLOOKUP
|
||||
"=HLOOKUP()": "HLOOKUP requires at least 3 arguments",
|
||||
"=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array",
|
||||
|
@ -1460,6 +1561,18 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array",
|
||||
"=LOOKUP(D2,D1,D2,FALSE)": "LOOKUP requires at most 3 arguments",
|
||||
"=LOOKUP(D1,MUNIT(1),MUNIT(1))": "LOOKUP no result found",
|
||||
// ROW
|
||||
"=ROW(1,2)": "ROW requires at most 1 argument",
|
||||
"=ROW(\"\")": "invalid reference",
|
||||
"=ROW(Sheet1)": "invalid column name \"Sheet1\"",
|
||||
"=ROW(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
|
||||
// ROWS
|
||||
"=ROWS()": "ROWS requires 1 argument",
|
||||
"=ROWS(1)": "invalid reference",
|
||||
"=ROWS(\"\")": "invalid reference",
|
||||
"=ROWS(Sheet1)": "invalid column name \"Sheet1\"",
|
||||
"=ROWS(Sheet1!A1!B1)": "invalid column name \"Sheet1\"",
|
||||
"=ROWS(Sheet1!Sheet1)": "invalid column name \"Sheet1\"",
|
||||
// Web Functions
|
||||
// ENCODEURL
|
||||
"=ENCODEURL()": "ENCODEURL requires 1 argument",
|
||||
|
|
6
lib.go
6
lib.go
|
@ -33,14 +33,14 @@ func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
|
|||
fileList := make(map[string][]byte, len(r.File))
|
||||
worksheets := 0
|
||||
for _, v := range r.File {
|
||||
fileName := v.Name
|
||||
if partName, ok := docPart[strings.ToLower(v.Name)]; ok {
|
||||
fileName := strings.Replace(v.Name, "\\", "/", -1)
|
||||
if partName, ok := docPart[strings.ToLower(fileName)]; ok {
|
||||
fileName = partName
|
||||
}
|
||||
if fileList[fileName], err = readFile(v); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if strings.HasPrefix(v.Name, "xl/worksheets/sheet") {
|
||||
if strings.HasPrefix(fileName, "xl/worksheets/sheet") {
|
||||
worksheets++
|
||||
}
|
||||
}
|
||||
|
|
9
sheet.go
9
sheet.go
|
@ -157,6 +157,9 @@ func (f *File) workSheetWriter() {
|
|||
for k, v := range sheet.SheetData.Row {
|
||||
f.Sheet[p].SheetData.Row[k].C = trimCell(v.C)
|
||||
}
|
||||
if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil {
|
||||
f.addNameSpaces(p, SourceRelationship)
|
||||
}
|
||||
// reusing buffer
|
||||
_ = encoder.Encode(sheet)
|
||||
f.saveFileList(p, replaceRelationshipsBytes(f.replaceNameSpaceBytes(p, buffer.Bytes())))
|
||||
|
@ -455,7 +458,11 @@ func (f *File) getSheetMap() map[string]string {
|
|||
// path, compatible with different types of relative paths in
|
||||
// workbook.xml.rels, for example: worksheets/sheet%d.xml
|
||||
// and /xl/worksheets/sheet%d.xml
|
||||
path := filepath.ToSlash(strings.TrimPrefix(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), rel.Target)), "/"))
|
||||
path := filepath.ToSlash(strings.TrimPrefix(
|
||||
strings.Replace(filepath.Clean(fmt.Sprintf("%s/%s", filepath.Dir(f.getWorkbookPath()), rel.Target)), "\\", "/", -1), "/"))
|
||||
if strings.HasPrefix(rel.Target, "/") {
|
||||
path = filepath.ToSlash(strings.TrimPrefix(strings.Replace(filepath.Clean(rel.Target), "\\", "/", -1), "/"))
|
||||
}
|
||||
if _, ok := f.XLSX[path]; ok {
|
||||
maps[v.Name] = path
|
||||
}
|
||||
|
|
BIN
test/Book1.xlsx
BIN
test/Book1.xlsx
Binary file not shown.
Loading…
Reference in New Issue