Skip to content

Commit

Permalink
This closes #998
Browse files Browse the repository at this point in the history
- Support text comparison in the formula, also ref #65
- `GetCellValue`, `GetRows`, `GetCols`, `Rows` and `Cols` support to specify read cell with raw value, ref #621
- Add missing properties for the cell formula
- Update the unit test for the `CalcCellValue`
  • Loading branch information
xuri committed Sep 5, 2021
1 parent 2616aa8 commit 32b23ef
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 101 deletions.
92 changes: 40 additions & 52 deletions calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,57 +777,25 @@ func calcNEq(rOpd, lOpd string, opdStack *Stack) error {

// calcL evaluate less than arithmetic operations.
func calcL(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal > lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcLe evaluate less than or equal arithmetic operations.
func calcLe(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal >= lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcG evaluate greater than or equal arithmetic operations.
func calcG(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal < lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

// calcGe evaluate greater than or equal arithmetic operations.
func calcGe(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
if err != nil {
return err
}
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpdVal <= lOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}

Expand Down Expand Up @@ -1214,7 +1182,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
if cell, err = CoordinatesToCellName(col, row); err != nil {
return
}
if value, err = f.GetCellValue(sheet, cell); err != nil {
if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil {
return
}
matrixRow = append(matrixRow, formulaArg{
Expand All @@ -1233,7 +1201,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
return
}
if arg.String, err = f.GetCellValue(cr.Sheet, cell); err != nil {
if arg.String, err = f.GetCellValue(cr.Sheet, cell, Options{RawCellValue: true}); err != nil {
return
}
arg.Type = ArgString
Expand Down Expand Up @@ -7749,28 +7717,33 @@ func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int
return
}

// LOOKUP function performs an approximate match lookup in a one-column or
// one-row range, and returns the corresponding value from another one-column
// or one-row range. The syntax of the function is:
//
// LOOKUP(lookup_value,lookup_vector,[result_vector])
//
func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg {
// checkLookupArgs checking arguments, prepare lookup value, and data for the
// formula function LOOKUP.
func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVector, errArg formulaArg) {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments")
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments")
return
}
if argsList.Len() > 3 {
return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
return
}
lookupValue := argsList.Front().Value.(formulaArg)
lookupVector := argsList.Front().Next().Value.(formulaArg)
lookupValue = argsList.Front().Value.(formulaArg)
lookupVector = argsList.Front().Next().Value.(formulaArg)
if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList {
return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
return
}
arrayForm := lookupVector.Type == ArgMatrix
arrayForm = lookupVector.Type == ArgMatrix
if arrayForm && len(lookupVector.Matrix) == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires not empty range as second argument")
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires not empty range as second argument")
}
return
}

// iterateLookupArgs iterate arguments to extract columns and calculate match
// index for the formula function LOOKUP.
func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int, bool) {
cols, matchIdx, ok := lookupCol(lookupVector, 0), -1, false
for idx, col := range cols {
lhs := lookupValue
Expand All @@ -7796,6 +7769,21 @@ func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg {
matchIdx = idx - 1
}
}
return cols, matchIdx, ok
}

// LOOKUP function performs an approximate match lookup in a one-column or
// one-row range, and returns the corresponding value from another one-column
// or one-row range. The syntax of the function is:
//
// LOOKUP(lookup_value,lookup_vector,[result_vector])
//
func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg {
arrayForm, lookupValue, lookupVector, errArg := checkLookupArgs(argsList)
if errArg.Type == ArgError {
return errArg
}
cols, matchIdx, ok := iterateLookupArgs(lookupValue, lookupVector)
if ok && matchIdx == -1 {
matchIdx = len(cols) - 1
}
Expand Down
26 changes: 18 additions & 8 deletions calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2452,17 +2452,27 @@ func TestCalcWithDefinedName(t *testing.T) {
}

func TestCalcArithmeticOperations(t *testing.T) {
opdStack := NewStack()
for _, test := range [][]string{{"1", "text", "FALSE"}, {"text", "1", "TRUE"}} {
assert.NoError(t, calcL(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
assert.NoError(t, calcLe(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
}
for _, test := range [][]string{{"1", "text", "TRUE"}, {"text", "1", "FALSE"}} {
assert.NoError(t, calcG(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
assert.NoError(t, calcGe(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
}

err := `strconv.ParseFloat: parsing "text": invalid syntax`
assert.EqualError(t, calcPow("1", "text", nil), err)
assert.EqualError(t, calcPow("text", "1", nil), err)
assert.EqualError(t, calcL("1", "text", nil), err)
assert.EqualError(t, calcL("text", "1", nil), err)
assert.EqualError(t, calcLe("1", "text", nil), err)
assert.EqualError(t, calcLe("text", "1", nil), err)
assert.EqualError(t, calcG("1", "text", nil), err)
assert.EqualError(t, calcG("text", "1", nil), err)
assert.EqualError(t, calcGe("1", "text", nil), err)
assert.EqualError(t, calcGe("text", "1", nil), err)
assert.EqualError(t, calcAdd("1", "text", nil), err)
assert.EqualError(t, calcAdd("text", "1", nil), err)
assert.EqualError(t, calcAdd("1", "text", nil), err)
Expand Down
47 changes: 42 additions & 5 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const (
// format to the cell value, it will do so, if not then an error will be
// returned, along with the raw value of the cell. All cells' values will be
// the same in a merged range.
func (f *File) GetCellValue(sheet, axis string) (string, error) {
func (f *File) GetCellValue(sheet, axis string, opts ...Options) (string, error) {
return f.getCellStringFunc(sheet, axis, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
val, err := c.getValueFrom(f, f.sharedStringsReader())
val, err := c.getValueFrom(f, f.sharedStringsReader(), parseOptions(opts...).RawCellValue)
return val, true, err
})
}
Expand Down Expand Up @@ -440,14 +440,48 @@ type FormulaOpts struct {
// err := f.SetCellFormula("Sheet1", "A3", "=A1:A2",
// excelize.FormulaOpts{Ref: &ref, Type: &formulaType})
//
//
// Example 6, set shared formula "=A1+B1" for the cell "C1:C5"
// on "Sheet1", "C1" is the master cell:
//
// formulaType, ref := excelize.STCellFormulaTypeShared, "C1:C5"
// err := f.SetCellFormula("Sheet1", "C1", "=A1+B1",
// excelize.FormulaOpts{Ref: &ref, Type: &formulaType})
//
// Example 7, set table formula "=SUM(Table1[[A]:[B]])" for the cell "C2"
// on "Sheet1":
//
// package main
//
// import (
// "fmt"
//
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} {
// if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil {
// fmt.Println(err)
// return
// }
// }
// if err := f.AddTable("Sheet1", "A1", "C2",
// `{"table_name":"Table1","table_style":"TableStyleMedium2"}`); err != nil {
// fmt.Println(err)
// return
// }
// formulaType := excelize.STCellFormulaTypeDataTable
// if err := f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])",
// excelize.FormulaOpts{Type: &formulaType}); err != nil {
// fmt.Println(err)
// return
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
//
func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
Expand All @@ -471,6 +505,9 @@ func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts)

for _, o := range opts {
if o.Type != nil {
if *o.Type == STCellFormulaTypeDataTable {
return err
}
cellData.F.T = *o.Type
if cellData.F.T == STCellFormulaTypeShared {
if err = ws.setSharedFormula(*o.Ref); err != nil {
Expand Down Expand Up @@ -955,8 +992,8 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
// formattedValue provides a function to returns a value after formatted. If
// it is possible to apply a format to the cell value, it will do so, if not
// then an error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(s int, v string) string {
if s == 0 {
func (f *File) formattedValue(s int, v string, raw bool) string {
if s == 0 || raw {
return v
}
styleSheet := f.stylesReader()
Expand Down
24 changes: 17 additions & 7 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,16 @@ func TestSetCellFormula(t *testing.T) {
ref = ""
assert.EqualError(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}), ErrParameterInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula5.xlsx")))

// Test set table formula for the cells.
f = NewFile()
for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} {
assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row))
}
assert.NoError(t, f.AddTable("Sheet1", "A1", "C2", `{"table_name":"Table1","table_style":"TableStyleMedium2"}`))
formulaType = STCellFormulaTypeDataTable
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx")))
}

func TestGetCellRichText(t *testing.T) {
Expand Down Expand Up @@ -503,41 +513,41 @@ func TestSetCellRichText(t *testing.T) {

func TestFormattedValue2(t *testing.T) {
f := NewFile()
v := f.formattedValue(0, "43528")
v := f.formattedValue(0, "43528", false)
assert.Equal(t, "43528", v)

v = f.formattedValue(15, "43528")
v = f.formattedValue(15, "43528", false)
assert.Equal(t, "43528", v)

v = f.formattedValue(1, "43528")
v = f.formattedValue(1, "43528", false)
assert.Equal(t, "43528", v)
customNumFmt := "[$-409]MM/DD/YYYY"
_, err := f.NewStyle(&Style{
CustomNumFmt: &customNumFmt,
})
assert.NoError(t, err)
v = f.formattedValue(1, "43528")
v = f.formattedValue(1, "43528", false)
assert.Equal(t, "03/04/2019", v)

// formatted value with no built-in number format ID
numFmtID := 5
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
v = f.formattedValue(2, "43528")
v = f.formattedValue(2, "43528", false)
assert.Equal(t, "43528", v)

// formatted value with invalid number format ID
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: nil,
})
_ = f.formattedValue(3, "43528")
_ = f.formattedValue(3, "43528", false)

// formatted value with empty number format
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
v = f.formattedValue(1, "43528")
v = f.formattedValue(1, "43528", false)
assert.Equal(t, "43528", v)
}
10 changes: 6 additions & 4 deletions col.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
type Cols struct {
err error
curCol, totalCol, stashCol, totalRow int
rawCellValue bool
sheet string
f *File
sheetXML []byte
Expand All @@ -54,14 +55,14 @@ type Cols struct {
// fmt.Println()
// }
//
func (f *File) GetCols(sheet string) ([][]string, error) {
func (f *File) GetCols(sheet string, opts ...Options) ([][]string, error) {
cols, err := f.Cols(sheet)
if err != nil {
return nil, err
}
results := make([][]string, 0, 64)
for cols.Next() {
col, _ := cols.Rows()
col, _ := cols.Rows(opts...)
results = append(results, col)
}
return results, nil
Expand All @@ -79,7 +80,7 @@ func (cols *Cols) Error() error {
}

// Rows return the current column's row values.
func (cols *Cols) Rows() ([]string, error) {
func (cols *Cols) Rows(opts ...Options) ([]string, error) {
var (
err error
inElement string
Expand All @@ -89,6 +90,7 @@ func (cols *Cols) Rows() ([]string, error) {
if cols.stashCol >= cols.curCol {
return rows, err
}
cols.rawCellValue = parseOptions(opts...).RawCellValue
d := cols.f.sharedStringsReader()
decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
for {
Expand Down Expand Up @@ -123,7 +125,7 @@ func (cols *Cols) Rows() ([]string, error) {
if cellCol == cols.curCol {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(cols.f, d)
val, _ := colCell.getValueFrom(cols.f, d, cols.rawCellValue)
rows = append(rows, val)
}
}
Expand Down
Loading

0 comments on commit 32b23ef

Please sign in to comment.