- 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`
pull/2/head
xuri 3 years ago
parent 2616aa88cb
commit 32b23ef42d
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -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
}
@ -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{
@ -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
@ -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
@ -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
}

@ -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)

@ -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
})
}
@ -440,7 +440,6 @@ 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:
//
@ -448,6 +447,41 @@ type FormulaOpts struct {
// 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 {
@ -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 {
@ -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()

@ -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) {
@ -503,20 +513,20 @@ 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
@ -524,20 +534,20 @@ func TestFormattedValue2(t *testing.T) {
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)
}

@ -34,6 +34,7 @@ const (
type Cols struct {
err error
curCol, totalCol, stashCol, totalRow int
rawCellValue bool
sheet string
f *File
sheetXML []byte
@ -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
@ -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
@ -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 {
@ -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)
}
}

@ -58,9 +58,12 @@ type File struct {
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
// Options define the options for open spreadsheet.
// Options define the options for open and reading spreadsheet. RawCellValue
// specify if apply the number format for the cell value or get the raw
// value.
type Options struct {
Password string
RawCellValue bool
UnzipSizeLimit int64
}
@ -119,12 +122,10 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
return nil, err
}
f := newFile()
for i := range opt {
f.options = &opt[i]
f.options = parseOptions(opt...)
if f.options.UnzipSizeLimit == 0 {
f.options.UnzipSizeLimit = UnzipSizeLimit
}
}
if bytes.Contains(b, oleIdentifier) {
b, err = Decrypt(b, f.options)
if err != nil {
@ -150,6 +151,16 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
return f, nil
}
// parseOptions provides a function to parse the optional settings for open
// and reading spreadsheet.
func parseOptions(opts ...Options) *Options {
opt := &Options{}
for _, o := range opts {
opt = &o
}
return opt
}
// CharsetTranscoder Set user defined codepage transcoder function for open
// XLSX from non UTF-8 encoding.
func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f }

@ -43,7 +43,7 @@ import (
// fmt.Println()
// }
//
func (f *File) GetRows(sheet string) ([][]string, error) {
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
rows, err := f.Rows(sheet)
if err != nil {
return nil, err
@ -51,7 +51,7 @@ func (f *File) GetRows(sheet string) ([][]string, error) {
results, cur, max := make([][]string, 0, 64), 0, 0
for rows.Next() {
cur++
row, err := rows.Columns()
row, err := rows.Columns(opts...)
if err != nil {
break
}
@ -67,6 +67,7 @@ func (f *File) GetRows(sheet string) ([][]string, error) {
type Rows struct {
err error
curRow, totalRow, stashRow int
rawCellValue bool
sheet string
f *File
decoder *xml.Decoder
@ -84,11 +85,12 @@ func (rows *Rows) Error() error {
}
// Columns return the current row's column values.
func (rows *Rows) Columns() ([]string, error) {
func (rows *Rows) Columns(opts ...Options) ([]string, error) {
var rowIterator rowXMLIterator
if rows.stashRow >= rows.curRow {
return rowIterator.columns, rowIterator.err
}
rows.rawCellValue = parseOptions(opts...).RawCellValue
rowIterator.rows = rows
rowIterator.d = rows.f.sharedStringsReader()
for {
@ -109,7 +111,7 @@ func (rows *Rows) Columns() ([]string, error) {
return rowIterator.columns, rowIterator.err
}
}
rowXMLHandler(&rowIterator, &xmlElement)
rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue)
if rowIterator.err != nil {
return rowIterator.columns, rowIterator.err
}
@ -157,7 +159,7 @@ type rowXMLIterator struct {
}
// rowXMLHandler parse the row XML element of the worksheet.
func rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement) {
func rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, raw bool) {
rowIterator.err = nil
if rowIterator.inElement == "c" {
rowIterator.cellCol++
@ -169,7 +171,7 @@ func rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement) {
}
}
blank := rowIterator.cellCol - len(rowIterator.columns)
val, _ := colCell.getValueFrom(rowIterator.rows.f, rowIterator.d)
val, _ := colCell.getValueFrom(rowIterator.rows.f, rowIterator.d, raw)
if val != "" || colCell.F != nil {
rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val)
}
@ -361,7 +363,7 @@ func (f *File) sharedStringsReader() *xlsxSST {
// getValueFrom return a value from a column/row cell, this function is
// inteded to be used with for range on rows an argument with the spreadsheet
// opened file.
func (c *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
f.Lock()
defer f.Unlock()
switch c.T {
@ -370,26 +372,26 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(c.V)
if len(d.SI) > xlsxSI {
return f.formattedValue(c.S, d.SI[xlsxSI].String()), nil
return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil
}
}
return f.formattedValue(c.S, c.V), nil
return f.formattedValue(c.S, c.V, raw), nil
case "str":
return f.formattedValue(c.S, c.V), nil
return f.formattedValue(c.S, c.V, raw), nil
case "inlineStr":
if c.IS != nil {
return f.formattedValue(c.S, c.IS.String()), nil
return f.formattedValue(c.S, c.IS.String(), raw), nil
}
return f.formattedValue(c.S, c.V), nil
return f.formattedValue(c.S, c.V, raw), nil
default:
isNum, precision := isNumeric(c.V)
if isNum && precision > 10 {
val, _ := roundPrecision(c.V)
if val != c.V {
return f.formattedValue(c.S, val), nil
return f.formattedValue(c.S, val, raw), nil
}
}
return f.formattedValue(c.S, c.V), nil
return f.formattedValue(c.S, c.V, raw), nil
}
}

@ -845,7 +845,7 @@ func TestGetValueFromInlineStr(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d)
val, err := c.getValueFrom(f, d, false)
assert.NoError(t, err)
assert.Equal(t, "", val)
}
@ -865,7 +865,7 @@ func TestGetValueFromNumber(t *testing.T) {
"2.220000ddsf0000000002-r": "2.220000ddsf0000000002-r",
} {
c.V = input
val, err := c.getValueFrom(f, d)
val, err := c.getValueFrom(f, d, false)
assert.NoError(t, err)
assert.Equal(t, expected, val)
}

@ -880,7 +880,7 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
if inElement == "c" {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(f, d)
val, _ := colCell.getValueFrom(f, d, false)
if regSearch {
regex := regexp.MustCompile(value)
if !regex.MatchString(val) {

@ -474,8 +474,17 @@ type xlsxC struct {
type xlsxF struct {
Content string `xml:",chardata"`
T string `xml:"t,attr,omitempty"` // Formula type
Aca bool `xml:"aca,attr,omitempty"`
Ref string `xml:"ref,attr,omitempty"` // Shared formula ref
Dt2D bool `xml:"dt2D,attr,omitempty"`
Dtr bool `xml:"dtr,attr,omitempty"`
Del1 bool `xml:"del1,attr,omitempty"`
Del2 bool `xml:"del2,attr,omitempty"`
R1 string `xml:"r1,attr,omitempty"`
R2 string `xml:"r2,attr,omitempty"`
Ca bool `xml:"ca,attr,omitempty"`
Si *int `xml:"si,attr"` // Shared formula index
Bx bool `xml:"bx,attr,omitempty"`
}
// xlsxSheetProtection collection expresses the sheet protection options to

Loading…
Cancel
Save