From d84050921eddf5c4221f8723477be2ac93f56ecc Mon Sep 17 00:00:00 2001 From: xuri Date: Tue, 23 Feb 2021 00:05:19 +0800 Subject: [PATCH] check empty rich text run properties; new formula fn: LEFT, LEFTB, RIGHT, RIGHTB --- calc.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ calc_test.go | 44 +++++++++++++++++++++++++++++++++ cell.go | 6 ++--- sheet.go | 2 ++ 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/calc.go b/calc.go index 629aacf9..a2efbdbb 100644 --- a/calc.go +++ b/calc.go @@ -288,6 +288,8 @@ var tokenPriority = map[string]int{ // ISO.CEILING // KURT // LCM +// LEFT +// LEFTB // LEN // LENB // LN @@ -321,6 +323,8 @@ var tokenPriority = map[string]int{ // RAND // RANDBETWEEN // REPT +// RIGHT +// RIGHTB // ROMAN // ROUND // ROUNDDOWN @@ -4635,6 +4639,54 @@ func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg { return newBoolFormulaArg(text1 == text2) } +// LEFT function returns a specified number of characters from the start of a +// supplied text string. The syntax of the function is: +// +// LEFT(text,[num_chars]) +// +func (fn *formulaFuncs) LEFT(argsList *list.List) formulaArg { + return fn.leftRight("LEFT", argsList) +} + +// LEFTB returns the first character or characters in a text string, based on +// the number of bytes you specify. The syntax of the function is: +// +// LEFTB(text,[num_bytes]) +// +func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg { + return fn.leftRight("LEFTB", argsList) +} + +// leftRight is an implementation of the formula function LEFT, LEFTB, RIGHT, +// RIGHTB. TODO: support DBCS include Japanese, Chinese (Simplified), Chinese +// (Traditional), and Korean. +func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 1 argument", name)) + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s allows at most 2 arguments", name)) + } + text, numChars := argsList.Front().Value.(formulaArg).Value(), 1 + if argsList.Len() == 2 { + numArg := argsList.Back().Value.(formulaArg).ToNumber() + if numArg.Type != ArgNumber { + return numArg + } + if numArg.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + numChars = int(numArg.Number) + } + if len(text) > numChars { + if name == "LEFT" || name == "LEFTB" { + return newStringFormulaArg(text[:numChars]) + } + return newStringFormulaArg(text[len(text)-numChars:]) + } + return newStringFormulaArg(text) +} + // LEN returns the length of a supplied text string. The syntax of the // function is: // @@ -4742,6 +4794,24 @@ func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg { return newStringFormulaArg(buf.String()) } +// RIGHT function returns a specified number of characters from the end of a +// supplied text string. The syntax of the function is: +// +// RIGHT(text,[num_chars]) +// +func (fn *formulaFuncs) RIGHT(argsList *list.List) formulaArg { + return fn.leftRight("RIGHT", argsList) +} + +// RIGHTB returns the last character or characters in a text string, based on +// the number of bytes you specify. The syntax of the function is: +// +// RIGHTB(text,[num_bytes]) +// +func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg { + return fn.leftRight("RIGHTB", argsList) +} + // UPPER converts all characters in a supplied text string to upper case. The // syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index 49af5238..1b04df82 100644 --- a/calc_test.go +++ b/calc_test.go @@ -723,6 +723,18 @@ func TestCalcCellValue(t *testing.T) { "=EXACT(1,\"1\")": "TRUE", "=EXACT(1,1)": "TRUE", "=EXACT(\"A\",\"a\")": "FALSE", + // LEFT + "=LEFT(\"Original Text\")": "O", + "=LEFT(\"Original Text\",4)": "Orig", + "=LEFT(\"Original Text\",0)": "", + "=LEFT(\"Original Text\",13)": "Original Text", + "=LEFT(\"Original Text\",20)": "Original Text", + // LEFTB + "=LEFTB(\"Original Text\")": "O", + "=LEFTB(\"Original Text\",4)": "Orig", + "=LEFTB(\"Original Text\",0)": "", + "=LEFTB(\"Original Text\",13)": "Original Text", + "=LEFTB(\"Original Text\",20)": "Original Text", // LEN "=LEN(\"\")": "0", "=LEN(D1)": "5", @@ -746,6 +758,18 @@ func TestCalcCellValue(t *testing.T) { "=REPT(\"*\",0)": "", "=REPT(\"*\",1)": "*", "=REPT(\"**\",2)": "****", + // RIGHT + "=RIGHT(\"Original Text\")": "t", + "=RIGHT(\"Original Text\",4)": "Text", + "=RIGHT(\"Original Text\",0)": "", + "=RIGHT(\"Original Text\",13)": "Original Text", + "=RIGHT(\"Original Text\",20)": "Original Text", + // RIGHTB + "=RIGHTB(\"Original Text\")": "t", + "=RIGHTB(\"Original Text\",4)": "Text", + "=RIGHTB(\"Original Text\",0)": "", + "=RIGHTB(\"Original Text\",13)": "Original Text", + "=RIGHTB(\"Original Text\",20)": "Original Text", // UPPER "=UPPER(\"test\")": "TEST", "=UPPER(\"TEST\")": "TEST", @@ -1308,6 +1332,16 @@ func TestCalcCellValue(t *testing.T) { // EXACT "=EXACT()": "EXACT requires 2 arguments", "=EXACT(1,2,3)": "EXACT requires 2 arguments", + // LEFT + "=LEFT()": "LEFT requires at least 1 argument", + "=LEFT(\"\",2,3)": "LEFT allows at most 2 arguments", + "=LEFT(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=LEFT(\"\",-1)": "#VALUE!", + // LEFTB + "=LEFTB()": "LEFTB requires at least 1 argument", + "=LEFTB(\"\",2,3)": "LEFTB allows at most 2 arguments", + "=LEFTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=LEFTB(\"\",-1)": "#VALUE!", // LEN "=LEN()": "LEN requires 1 string argument", // LENB @@ -1329,6 +1363,16 @@ func TestCalcCellValue(t *testing.T) { "=REPT(INT(0),2)": "REPT requires first argument to be a string", "=REPT(\"*\",\"*\")": "REPT requires second argument to be a number", "=REPT(\"*\",-1)": "REPT requires second argument to be >= 0", + // RIGHT + "=RIGHT()": "RIGHT requires at least 1 argument", + "=RIGHT(\"\",2,3)": "RIGHT allows at most 2 arguments", + "=RIGHT(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=RIGHT(\"\",-1)": "#VALUE!", + // RIGHTB + "=RIGHTB()": "RIGHTB requires at least 1 argument", + "=RIGHTB(\"\",2,3)": "RIGHTB allows at most 2 arguments", + "=RIGHTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=RIGHTB(\"\",-1)": "#VALUE!", // Conditional Functions // IF "=IF()": "IF requires at least 1 argument", diff --git a/cell.go b/cell.go index ea4e1d0c..892a9067 100644 --- a/cell.go +++ b/cell.go @@ -522,13 +522,13 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro font := Font{} font.Bold = v.RPr.B != nil font.Italic = v.RPr.I != nil - if nil != v.RPr.U { + if v.RPr.U != nil && v.RPr.U.Val != nil { font.Underline = *v.RPr.U.Val } - if nil != v.RPr.RFont { + if v.RPr.RFont != nil && v.RPr.RFont.Val != nil { font.Family = *v.RPr.RFont.Val } - if nil != v.RPr.Sz { + if v.RPr.Sz != nil && v.RPr.Sz.Val != nil { font.Size = *v.RPr.Sz.Val } font.Strike = v.RPr.Strike != nil diff --git a/sheet.go b/sheet.go index 26c0081e..d4362b12 100644 --- a/sheet.go +++ b/sheet.go @@ -1762,6 +1762,7 @@ func prepareSheetXML(ws *xlsxWorksheet, col int, row int) { fillColumns(rowData, col, row) } +// fillColumns fill cells in the column of the row as contiguous. func fillColumns(rowData *xlsxRow, col, row int) { cellCount := len(rowData.C) if cellCount < col { @@ -1772,6 +1773,7 @@ func fillColumns(rowData *xlsxRow, col, row int) { } } +// makeContiguousColumns make columns in specific rows as contiguous. func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) { for ; fromRow < toRow; fromRow++ { rowData := &ws.SheetData.Row[fromRow-1]