OSDN Git Service

ref #65, support _xlfn.ANCHORARRAY formula function (#1784)
author3zmx <mudaocc@gmail.com>
Thu, 18 Jan 2024 07:31:43 +0000 (15:31 +0800)
committerGitHub <noreply@github.com>
Thu, 18 Jan 2024 07:31:43 +0000 (15:31 +0800)
- Initial formula array calculation support
- Update unit test and documentation

62 files changed:
adjust.go
adjust_test.go
calc.go
calc_test.go
calcchain.go
cell.go
cell_test.go
chart.go
col.go
crypt.go
crypt_test.go
datavalidation.go
datavalidation_test.go
date.go
docProps.go
docProps_test.go
drawing.go
drawing_test.go
errors.go
excelize.go
file.go
lib.go
merge.go
numfmt.go
picture.go
picture_test.go
pivotTable.go
rows.go
rows_test.go
shape.go
sheet.go
sheet_test.go
sheetpr.go
sheetview.go
slicer.go
sparkline.go
stream.go
styles.go
table.go
templates.go
vml.go
vmlDrawing.go
vml_test.go
workbook.go
xmlApp.go
xmlCalcChain.go
xmlChart.go
xmlChartSheet.go
xmlComments.go
xmlContentTypes.go
xmlCore.go
xmlDecodeDrawing.go
xmlDrawing.go
xmlPivotCache.go
xmlPivotTable.go
xmlSharedStrings.go
xmlSlicers.go
xmlStyles.go
xmlTable.go
xmlTheme.go
xmlWorkbook.go
xmlWorksheet.go

index 35ecdd1..bae49bc 100644 (file)
--- a/adjust.go
+++ b/adjust.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
@@ -165,7 +165,7 @@ func (f *File) adjustColDimensions(sheet string, ws *xlsxWorksheet, col, offset
                                                worksheet.SheetData.Row[rowIdx].C[colIdx].R, _ = CoordinatesToCellName(newCol, cellRow)
                                        }
                                }
-                               if err := f.adjustFormula(sheet, sheetN, worksheet.SheetData.Row[rowIdx].C[colIdx].F, columns, col, offset, false); err != nil {
+                               if err := f.adjustFormula(sheet, sheetN, &worksheet.SheetData.Row[rowIdx].C[colIdx], columns, col, offset, false); err != nil {
                                        return err
                                }
                        }
@@ -228,8 +228,8 @@ func (r *xlsxRow) adjustSingleRowDimensions(offset int) {
 
 // adjustSingleRowFormulas provides a function to adjust single row formulas.
 func (f *File) adjustSingleRowFormulas(sheet, sheetN string, r *xlsxRow, num, offset int, si bool) error {
-       for _, col := range r.C {
-               if err := f.adjustFormula(sheet, sheetN, col.F, rows, num, offset, si); err != nil {
+       for i := 0; i < len(r.C); i++ {
+               if err := f.adjustFormula(sheet, sheetN, &r.C[i], rows, num, offset, si); err != nil {
                        return err
                }
        }
@@ -273,37 +273,32 @@ func (f *File) adjustCellRef(ref string, dir adjustDirection, num, offset int) (
 
 // adjustFormula provides a function to adjust formula reference and shared
 // formula reference.
-func (f *File) adjustFormula(sheet, sheetN string, formula *xlsxF, dir adjustDirection, num, offset int, si bool) error {
-       if formula == nil {
+func (f *File) adjustFormula(sheet, sheetN string, cell *xlsxC, dir adjustDirection, num, offset int, si bool) error {
+       var err error
+       if cell.f != "" {
+               if cell.f, err = f.adjustFormulaRef(sheet, sheetN, cell.f, false, dir, num, offset); err != nil {
+                       return err
+               }
+       }
+       if cell.F == nil {
                return nil
        }
-       var err error
-       if formula.Ref != "" && sheet == sheetN {
-               if formula.Ref, _, err = f.adjustCellRef(formula.Ref, dir, num, offset); err != nil {
+       if cell.F.Ref != "" && sheet == sheetN {
+               if cell.F.Ref, _, err = f.adjustCellRef(cell.F.Ref, dir, num, offset); err != nil {
                        return err
                }
-               if si && formula.Si != nil {
-                       formula.Si = intPtr(*formula.Si + 1)
+               if si && cell.F.Si != nil {
+                       cell.F.Si = intPtr(*cell.F.Si + 1)
                }
        }
-       if formula.Content != "" {
-               if formula.Content, err = f.adjustFormulaRef(sheet, sheetN, formula.Content, false, dir, num, offset); err != nil {
+       if cell.F.Content != "" {
+               if cell.F.Content, err = f.adjustFormulaRef(sheet, sheetN, cell.F.Content, false, dir, num, offset); err != nil {
                        return err
                }
        }
        return nil
 }
 
-// isFunctionStop provides a function to check if token is a function stop.
-func isFunctionStop(token efp.Token) bool {
-       return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop
-}
-
-// isFunctionStart provides a function to check if token is a function start.
-func isFunctionStart(token efp.Token) bool {
-       return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart
-}
-
 // escapeSheetName enclose sheet name in single quotation marks if the giving
 // worksheet name includes spaces or non-alphabetical characters.
 func escapeSheetName(name string) string {
@@ -442,11 +437,11 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
                        val += operand
                        continue
                }
-               if isFunctionStart(token) {
+               if isFunctionStartToken(token) {
                        val += token.TValue + string(efp.ParenOpen)
                        continue
                }
-               if isFunctionStop(token) {
+               if isFunctionStopToken(token) {
                        val += token.TValue + string(efp.ParenClose)
                        continue
                }
@@ -459,6 +454,115 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool
        return val, nil
 }
 
+// arrayFormulaOperandToken defines meta fields for transforming the array
+// formula to the normal formula.
+type arrayFormulaOperandToken struct {
+       operandTokenIndex, topLeftCol, topLeftRow, bottomRightCol, bottomRightRow int
+       sheetName, sourceCellRef, targetCellRef                                   string
+}
+
+// setCoordinates convert each corner cell reference in the array formula cell
+// range to the coordinate number.
+func (af *arrayFormulaOperandToken) setCoordinates() error {
+       for i, ref := range strings.Split(af.sourceCellRef, ":") {
+               cellRef, col, row, err := parseRef(ref)
+               if err != nil {
+                       return err
+               }
+               var c, r int
+               if col {
+                       if cellRef.Row = TotalRows; i == 1 {
+                               cellRef.Row = 1
+                       }
+               }
+               if row {
+                       if cellRef.Col = MaxColumns; i == 1 {
+                               cellRef.Col = 1
+                       }
+               }
+               if c, r = cellRef.Col, cellRef.Row; cellRef.Sheet != "" {
+                       af.sheetName = cellRef.Sheet + "!"
+               }
+               if af.topLeftCol == 0 || c < af.topLeftCol {
+                       af.topLeftCol = c
+               }
+               if af.topLeftRow == 0 || r < af.topLeftRow {
+                       af.topLeftRow = r
+               }
+               if c > af.bottomRightCol {
+                       af.bottomRightCol = c
+               }
+               if r > af.bottomRightRow {
+                       af.bottomRightRow = r
+               }
+       }
+       return nil
+}
+
+// transformArrayFormula transforms an array formula to the normal formula by
+// giving a formula tokens list and formula operand tokens list.
+func transformArrayFormula(tokens []efp.Token, afs []arrayFormulaOperandToken) string {
+       var val string
+       for i, token := range tokens {
+               var skip bool
+               for _, af := range afs {
+                       if af.operandTokenIndex == i {
+                               val += af.sheetName + af.targetCellRef
+                               skip = true
+                               break
+                       }
+               }
+               if skip {
+                       continue
+               }
+               if isFunctionStartToken(token) {
+                       val += token.TValue + string(efp.ParenOpen)
+                       continue
+               }
+               if isFunctionStopToken(token) {
+                       val += token.TValue + string(efp.ParenClose)
+                       continue
+               }
+               if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
+                       val += string(efp.QuoteDouble) + strings.ReplaceAll(token.TValue, "\"", "\"\"") + string(efp.QuoteDouble)
+                       continue
+               }
+               val += token.TValue
+       }
+       return val
+}
+
+// getArrayFormulaTokens returns parsed formula token and operand related token
+// list for in array formula.
+func getArrayFormulaTokens(sheet, formula string, definedNames []DefinedName) ([]efp.Token, []arrayFormulaOperandToken, error) {
+       var (
+               ps                        = efp.ExcelParser()
+               tokens                    = ps.Parse(formula)
+               arrayFormulaOperandTokens []arrayFormulaOperandToken
+       )
+       for i, token := range tokens {
+               if token.TSubType == efp.TokenSubTypeRange && token.TType == efp.TokenTypeOperand {
+                       tokenVal := token.TValue
+                       for _, definedName := range definedNames {
+                               if (definedName.Scope == "Workbook" || definedName.Scope == sheet) && definedName.Name == tokenVal {
+                                       tokenVal = definedName.RefersTo
+                               }
+                       }
+                       if len(strings.Split(tokenVal, ":")) > 1 {
+                               arrayFormulaOperandToken := arrayFormulaOperandToken{
+                                       operandTokenIndex: i,
+                                       sourceCellRef:     tokenVal,
+                               }
+                               if err := arrayFormulaOperandToken.setCoordinates(); err != nil {
+                                       return tokens, arrayFormulaOperandTokens, err
+                               }
+                               arrayFormulaOperandTokens = append(arrayFormulaOperandTokens, arrayFormulaOperandToken)
+                       }
+               }
+       }
+       return tokens, arrayFormulaOperandTokens, nil
+}
+
 // adjustHyperlinks provides a function to update hyperlinks when inserting or
 // deleting rows or columns.
 func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
index a8dd2ff..bfaa61c 100644 (file)
@@ -557,9 +557,9 @@ func TestAdjustFormula(t *testing.T) {
        assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustFormula.xlsx")))
        assert.NoError(t, f.Close())
 
-       assert.NoError(t, f.adjustFormula("Sheet1", "Sheet1", nil, rows, 0, 0, false))
-       assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false))
-       assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false))
+       assert.NoError(t, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{}, rows, 0, 0, false))
+       assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "-"}}, rows, 0, 0, false))
+       assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxC{F: &xlsxF{Ref: "XFD1:XFD1"}}, columns, 0, 1, false))
 
        _, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", false, columns, 0, 1)
        assert.Equal(t, ErrColumnNumber, err)
@@ -940,6 +940,26 @@ func TestAdjustFormula(t *testing.T) {
                assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
                assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
        })
+       t.Run("for_array_formula_cell", func(t *testing.T) {
+               f := NewFile()
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
+               formulaType, ref := STCellFormulaTypeArray, "C1:C2"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
+               assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
+               assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
+               result, err := f.CalcCellValue("Sheet1", "D2")
+               assert.NoError(t, err)
+               assert.Equal(t, "2", result)
+               result, err = f.CalcCellValue("Sheet1", "D3")
+               assert.NoError(t, err)
+               assert.Equal(t, "12", result)
+
+               // Test adjust array formula with invalid range reference
+               formulaType, ref = STCellFormulaTypeArray, "E1:E2"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "E1", "XFD1:XFD1", FormulaOpts{Ref: &ref, Type: &formulaType}))
+               assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), "the column number must be greater than or equal to 1 and less than or equal to 16384")
+       })
 }
 
 func TestAdjustVolatileDeps(t *testing.T) {
diff --git a/calc.go b/calc.go
index 7fc87ba..2488eda 100644 (file)
--- a/calc.go
+++ b/calc.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
@@ -838,7 +838,7 @@ func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string
 // reference.
 func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result formulaArg, err error) {
        var formula string
-       if formula, err = f.GetCellFormula(sheet, cell); err != nil {
+       if formula, err = f.getCellFormula(sheet, cell, true); err != nil {
                return
        }
        ps := efp.ExcelParser()
@@ -1467,7 +1467,7 @@ func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdSt
 }
 
 // parseRef parse reference for a cell, column name or row number.
-func (f *File) parseRef(ref string) (cellRef, bool, bool, error) {
+func parseRef(ref string) (cellRef, bool, bool, error) {
        var (
                err, colErr, rowErr error
                cr                  cellRef
@@ -1526,7 +1526,7 @@ func (f *File) parseReference(ctx *calcContext, sheet, reference string) (formul
        if len(ranges) > 1 {
                var cr cellRange
                for i, ref := range ranges {
-                       cellRef, col, row, err := f.parseRef(ref)
+                       cellRef, col, row, err := parseRef(ref)
                        if err != nil {
                                return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
                        }
@@ -1550,7 +1550,7 @@ func (f *File) parseReference(ctx *calcContext, sheet, reference string) (formul
                cellRanges.PushBack(cr)
                return f.rangeResolver(ctx, cellRefs, cellRanges)
        }
-       cellRef, _, _, err := f.parseRef(reference)
+       cellRef, _, _, err := parseRef(reference)
        if err != nil {
                return newErrorFormulaArg(formulaErrorNAME, "invalid reference"), errors.New("invalid reference")
        }
@@ -1601,7 +1601,7 @@ func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, e
                err   error
        )
        ref := fmt.Sprintf("%s!%s", sheet, cell)
-       if formula, _ := f.GetCellFormula(sheet, cell); len(formula) != 0 {
+       if formula, _ := f.getCellFormula(sheet, cell, true); len(formula) != 0 {
                ctx.mu.Lock()
                if ctx.entry != ref {
                        if ctx.iterations[ref] <= f.options.MaxCalcIterations {
@@ -14505,6 +14505,48 @@ func (fn *formulaFuncs) ADDRESS(argsList *list.List) formulaArg {
        return newStringFormulaArg(fmt.Sprintf("%s%s", sheetText, addr))
 }
 
+// ANCHORARRAY function returns the entire spilled range for the dynamic array
+// in cell. The syntax of the function is:
+//
+//     ANCHORARRAY(cell)
+func (fn *formulaFuncs) ANCHORARRAY(argsList *list.List) formulaArg {
+       if argsList.Len() != 1 {
+               return newErrorFormulaArg(formulaErrorVALUE, "ANCHORARRAY requires 1 numeric argument")
+       }
+       ws, err := fn.f.workSheetReader(fn.sheet)
+       if err != nil {
+               return newErrorFormulaArg(formulaErrorVALUE, err.Error())
+       }
+       ref := argsList.Front().Value.(formulaArg).cellRefs.Front().Value.(cellRef)
+       cell := ws.SheetData.Row[ref.Row-1].C[ref.Col-1]
+       if cell.F == nil {
+               return newEmptyFormulaArg()
+       }
+       coordinates, err := rangeRefToCoordinates(cell.F.Ref)
+       if err != nil {
+               return newErrorFormulaArg(formulaErrorVALUE, err.Error())
+       }
+       _ = sortCoordinates(coordinates)
+       var mtx [][]formulaArg
+       for c := coordinates[0]; c <= coordinates[2]; c++ {
+               var row []formulaArg
+               for r := coordinates[1]; r <= coordinates[3]; r++ {
+                       cellName, _ := CoordinatesToCellName(c, r)
+                       result, err := fn.f.CalcCellValue(ref.Sheet, cellName, Options{RawCellValue: true})
+                       if err != nil {
+                               return newErrorFormulaArg(formulaErrorVALUE, err.Error())
+                       }
+                       arg := newStringFormulaArg(result)
+                       if num := arg.ToNumber(); num.Type == ArgNumber {
+                               arg = num
+                       }
+                       row = append(row, arg)
+               }
+               mtx = append(mtx, row)
+       }
+       return newMatrixFormulaArg(mtx)
+}
+
 // CHOOSE function returns a value from an array, that corresponds to a
 // supplied index number (position). The syntax of the function is:
 //
index a3a6a83..99b1eb2 100644 (file)
@@ -4689,7 +4689,7 @@ func TestCalcCellValue(t *testing.T) {
        assert.EqualError(t, err, "sheet SheetN does not exist")
        // Test get calculated cell value with invalid sheet name
        _, err = f.CalcCellValue("Sheet:1", "A1")
-       assert.EqualError(t, err, ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, err)
        // Test get calculated cell value with not support formula
        f = prepareCalcData(cellData)
        assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=UNSUPPORT(A1)"))
@@ -4823,6 +4823,119 @@ func TestCalcCompareFormulaArgMatrix(t *testing.T) {
        assert.Equal(t, compareFormulaArgMatrix(lhs, rhs, newNumberFormulaArg(matchModeMaxLess), false), criteriaG)
 }
 
+func TestCalcANCHORARRAY(t *testing.T) {
+       f := NewFile()
+       assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
+       assert.NoError(t, f.SetCellValue("Sheet1", "A2", 2))
+       formulaType, ref := STCellFormulaTypeArray, "B1:B2"
+       assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "A1:A2",
+               FormulaOpts{Ref: &ref, Type: &formulaType}))
+       assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY($B$1))"))
+       result, err := f.CalcCellValue("Sheet1", "C1")
+       assert.NoError(t, err)
+       assert.Equal(t, "3", result)
+
+       assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "SUM(_xlfn.ANCHORARRAY(\"\",\"\"))"))
+       result, err = f.CalcCellValue("Sheet1", "C1")
+       assert.EqualError(t, err, "ANCHORARRAY requires 1 numeric argument")
+       assert.Equal(t, "#VALUE!", result)
+
+       fn := &formulaFuncs{f: f, sheet: "SheetN"}
+       argsList := list.New()
+       argsList.PushBack(newStringFormulaArg("$B$1"))
+       formulaArg := fn.ANCHORARRAY(argsList)
+       assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value())
+
+       fn.sheet = "Sheet1"
+       argsList = argsList.Init()
+       arg := newStringFormulaArg("$A$1")
+       arg.cellRefs = list.New()
+       arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1})
+       argsList.PushBack(arg)
+       formulaArg = fn.ANCHORARRAY(argsList)
+       assert.Equal(t, ArgEmpty, formulaArg.Type)
+
+       ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
+       assert.True(t, ok)
+       ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{}
+       formulaArg = fn.ANCHORARRAY(argsList)
+       assert.Equal(t, ArgError, formulaArg.Type)
+       assert.Equal(t, ErrParameterInvalid.Error(), formulaArg.Value())
+
+       argsList = argsList.Init()
+       arg = newStringFormulaArg("$B$1")
+       arg.cellRefs = list.New()
+       arg.cellRefs.PushBack(cellRef{Row: 1, Col: 1, Sheet: "SheetN"})
+       argsList.PushBack(arg)
+       ws.(*xlsxWorksheet).SheetData.Row[0].C[0].F = &xlsxF{Ref: "A1:A1"}
+       formulaArg = fn.ANCHORARRAY(argsList)
+       assert.Equal(t, ArgError, formulaArg.Type)
+       assert.Equal(t, "sheet SheetN does not exist", formulaArg.Value())
+}
+
+func TestCalcArrayFormula(t *testing.T) {
+       t.Run("matrix_multiplication", func(t *testing.T) {
+               f := NewFile()
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
+               formulaType, ref := STCellFormulaTypeArray, "C1:C2"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A1:A2*B1:B2",
+                       FormulaOpts{Ref: &ref, Type: &formulaType}))
+               result, err := f.CalcCellValue("Sheet1", "C1")
+               assert.NoError(t, err)
+               assert.Equal(t, "2", result)
+               result, err = f.CalcCellValue("Sheet1", "C2")
+               assert.NoError(t, err)
+               assert.Equal(t, "12", result)
+       })
+       t.Run("matrix_multiplication_with_defined_name", func(t *testing.T) {
+               f := NewFile()
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
+               assert.NoError(t, f.SetDefinedName(&DefinedName{
+                       Name:     "matrix",
+                       RefersTo: "Sheet1!$A$1:$A$2",
+               }))
+               formulaType, ref := STCellFormulaTypeArray, "C1:C2"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "matrix*B1:B2+\"1\"",
+                       FormulaOpts{Ref: &ref, Type: &formulaType}))
+               result, err := f.CalcCellValue("Sheet1", "C1")
+               assert.NoError(t, err)
+               assert.Equal(t, "3", result)
+               result, err = f.CalcCellValue("Sheet1", "C2")
+               assert.NoError(t, err)
+               assert.Equal(t, "13", result)
+       })
+       t.Run("columm_multiplication", func(t *testing.T) {
+               f := NewFile()
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
+               formulaType, ref := STCellFormulaTypeArray, "C1:C1048576"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "A:A*B:B",
+                       FormulaOpts{Ref: &ref, Type: &formulaType}))
+               result, err := f.CalcCellValue("Sheet1", "C1")
+               assert.NoError(t, err)
+               assert.Equal(t, "2", result)
+               result, err = f.CalcCellValue("Sheet1", "C2")
+               assert.NoError(t, err)
+               assert.Equal(t, "12", result)
+       })
+       t.Run("row_multiplication", func(t *testing.T) {
+               f := NewFile()
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]int{1, 2}))
+               assert.NoError(t, f.SetSheetRow("Sheet1", "A2", &[]int{3, 4}))
+               formulaType, ref := STCellFormulaTypeArray, "A3:XFD3"
+               assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "1:1*2:2",
+                       FormulaOpts{Ref: &ref, Type: &formulaType}))
+               result, err := f.CalcCellValue("Sheet1", "A3")
+               assert.NoError(t, err)
+               assert.Equal(t, "3", result)
+               result, err = f.CalcCellValue("Sheet1", "B3")
+               assert.NoError(t, err)
+               assert.Equal(t, "8", result)
+       })
+}
+
 func TestCalcTRANSPOSE(t *testing.T) {
        cellData := [][]interface{}{
                {"a", "d"},
index f85169a..32c2ef1 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
diff --git a/cell.go b/cell.go
index ad09208..721ba7b 100644 (file)
--- a/cell.go
+++ b/cell.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
@@ -660,7 +660,22 @@ func (f *File) SetCellDefault(sheet, cell, value string) error {
 // GetCellFormula provides a function to get formula from cell by given
 // worksheet name and cell reference in spreadsheet.
 func (f *File) GetCellFormula(sheet, cell string) (string, error) {
+       return f.getCellFormula(sheet, cell, false)
+}
+
+// getCellFormula provides a function to get transformed formula from cell by
+// given worksheet name and cell reference in spreadsheet.
+func (f *File) getCellFormula(sheet, cell string, transformed bool) (string, error) {
        return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
+               if transformed && !f.formulaChecked {
+                       if err := f.setArrayFormulaCells(); err != nil {
+                               return "", false, err
+                       }
+                       f.formulaChecked = true
+               }
+               if transformed && c.f != "" {
+                       return c.f, true, nil
+               }
                if c.F == nil {
                        return "", false, nil
                }
@@ -785,6 +800,11 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts)
                                return err
                        }
                        c.F.T = *opt.Type
+                       if c.F.T == STCellFormulaTypeArray && opt.Ref != nil {
+                               if err = ws.setArrayFormula(sheet, &xlsxF{Ref: *opt.Ref, Content: formula}, f.GetDefinedName()); err != nil {
+                                       return err
+                               }
+                       }
                        if c.F.T == STCellFormulaTypeShared {
                                if err = ws.setSharedFormula(*opt.Ref); err != nil {
                                        return err
@@ -799,6 +819,67 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts)
        return err
 }
 
+// setArrayFormula transform the array formula in an array formula range to the
+// normal formula and set cells in this range to the formula as the normal
+// formula.
+func (ws *xlsxWorksheet) setArrayFormula(sheet string, formula *xlsxF, definedNames []DefinedName) error {
+       if len(strings.Split(formula.Ref, ":")) < 2 {
+               return nil
+       }
+       coordinates, err := rangeRefToCoordinates(formula.Ref)
+       if err != nil {
+               return err
+       }
+       _ = sortCoordinates(coordinates)
+       tokens, arrayFormulaOperandTokens, err := getArrayFormulaTokens(sheet, formula.Content, definedNames)
+       if err != nil {
+               return err
+       }
+       topLeftCol, topLeftRow := coordinates[0], coordinates[1]
+       for c := coordinates[0]; c <= coordinates[2]; c++ {
+               for r := coordinates[1]; r <= coordinates[3]; r++ {
+                       colOffset, rowOffset := c-topLeftCol, r-topLeftRow
+                       for i, af := range arrayFormulaOperandTokens {
+                               colNum, rowNum := af.topLeftCol+colOffset, af.topLeftRow+rowOffset
+                               if colNum <= af.bottomRightCol && rowNum <= af.bottomRightRow {
+                                       arrayFormulaOperandTokens[i].targetCellRef, _ = CoordinatesToCellName(colNum, rowNum)
+                               }
+                       }
+                       ws.prepareSheetXML(c, r)
+                       if cell := &ws.SheetData.Row[r-1].C[c-1]; cell.f == "" {
+                               cell.f = transformArrayFormula(tokens, arrayFormulaOperandTokens)
+                       }
+               }
+       }
+       return err
+}
+
+// setArrayFormulaCells transform the array formula in all worksheets to the
+// normal formula and set cells in the array formula reference range to the
+// formula as the normal formula.
+func (f *File) setArrayFormulaCells() error {
+       definedNames := f.GetDefinedName()
+       for _, sheetN := range f.GetSheetList() {
+               ws, err := f.workSheetReader(sheetN)
+               if err != nil {
+                       if err.Error() == newNotWorksheetError(sheetN).Error() {
+                               continue
+                       }
+                       return err
+               }
+               for _, row := range ws.SheetData.Row {
+                       for _, cell := range row.C {
+                               if cell.F != nil && cell.F.T == STCellFormulaTypeArray {
+                                       if err = ws.setArrayFormula(sheetN, cell.F, definedNames); err != nil {
+                                               return err
+                                       }
+                               }
+                       }
+               }
+       }
+       return nil
+}
+
 // setSharedFormula set shared formula for the cells.
 func (ws *xlsxWorksheet) setSharedFormula(ref string) error {
        coordinates, err := rangeRefToCoordinates(ref)
index c3a622e..0ed6e87 100644 (file)
@@ -134,11 +134,11 @@ func TestCheckCellInRangeRef(t *testing.T) {
        }
 
        ok, err := f.checkCellInRangeRef("A1", "A:B")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
        assert.False(t, ok)
 
        ok, err = f.checkCellInRangeRef("AA0", "Z0:AB1")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("AA0", newInvalidCellNameError("AA0")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("AA0", newInvalidCellNameError("AA0")), err)
        assert.False(t, ok)
 }
 
@@ -172,9 +172,9 @@ func TestSetCellFloat(t *testing.T) {
                assert.Equal(t, "123.42", val, "A1 should be 123.42")
        })
        f := NewFile()
-       assert.EqualError(t, f.SetCellFloat(sheet, "A", 123.42, -1, 64), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellFloat(sheet, "A", 123.42, -1, 64))
        // Test set cell float data type value with invalid sheet name
-       assert.EqualError(t, f.SetCellFloat("Sheet:1", "A1", 123.42, -1, 64), ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, f.SetCellFloat("Sheet:1", "A1", 123.42, -1, 64))
 }
 
 func TestSetCellUint(t *testing.T) {
@@ -232,8 +232,8 @@ func TestSetCellValuesMultiByte(t *testing.T) {
 
 func TestSetCellValue(t *testing.T) {
        f := NewFile()
-       assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
-       assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Now().UTC()))
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellValue("Sheet1", "A", time.Duration(1e13)))
        // Test set cell value with column and row style inherit
        style1, err := f.NewStyle(&Style{NumFmt: 2})
        assert.NoError(t, err)
@@ -251,7 +251,7 @@ func TestSetCellValue(t *testing.T) {
        assert.Equal(t, "0.50", B2)
 
        // Test set cell value with invalid sheet name
-       assert.EqualError(t, f.SetCellValue("Sheet:1", "A1", "A1"), ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, f.SetCellValue("Sheet:1", "A1", "A1"))
        // Test set cell value with unsupported charset shared strings table
        f.SharedStrings = nil
        f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
@@ -299,9 +299,9 @@ func TestSetCellValues(t *testing.T) {
 
 func TestSetCellBool(t *testing.T) {
        f := NewFile()
-       assert.EqualError(t, f.SetCellBool("Sheet1", "A", true), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellBool("Sheet1", "A", true))
        // Test set cell boolean data type value with invalid sheet name
-       assert.EqualError(t, f.SetCellBool("Sheet:1", "A1", true), ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, f.SetCellBool("Sheet:1", "A1", true))
 }
 
 func TestSetCellTime(t *testing.T) {
@@ -486,7 +486,7 @@ func TestGetCellValue(t *testing.T) {
        assert.EqualError(t, value, "XML syntax error on line 1: invalid UTF-8")
        // Test get cell value with invalid sheet name
        _, err = f.GetCellValue("Sheet:1", "A1")
-       assert.EqualError(t, err, ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, err)
 }
 
 func TestGetCellType(t *testing.T) {
@@ -499,10 +499,10 @@ func TestGetCellType(t *testing.T) {
        assert.NoError(t, err)
        assert.Equal(t, CellTypeSharedString, cellType)
        _, err = f.GetCellType("Sheet1", "A")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
        // Test get cell type with invalid sheet name
        _, err = f.GetCellType("Sheet:1", "A1")
-       assert.EqualError(t, err, ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, err)
 }
 
 func TestGetValueFrom(t *testing.T) {
@@ -528,7 +528,7 @@ func TestGetCellFormula(t *testing.T) {
 
        // Test get cell formula with invalid sheet name
        _, err = f.GetCellFormula("Sheet:1", "A1")
-       assert.EqualError(t, err, ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, err)
 
        // Test get cell formula on no formula cell
        assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
@@ -556,6 +556,25 @@ func TestGetCellFormula(t *testing.T) {
        formula, err := f.GetCellFormula("Sheet1", "B2")
        assert.NoError(t, err)
        assert.Equal(t, "", formula)
+
+       // Test get array formula with invalid cell range reference
+       f = NewFile()
+       assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Line}))
+       _, err = f.NewSheet("Sheet2")
+       assert.NoError(t, err)
+       formulaType, ref := STCellFormulaTypeArray, "B1:B2"
+       assert.NoError(t, f.SetCellFormula("Sheet2", "B1", "A1:B2", FormulaOpts{Ref: &ref, Type: &formulaType}))
+       ws, ok := f.Sheet.Load("xl/worksheets/sheet3.xml")
+       assert.True(t, ok)
+       ws.(*xlsxWorksheet).SheetData.Row[0].C[1].F.Ref = ":"
+       _, err = f.getCellFormula("Sheet2", "A1", true)
+       assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err)
+
+       // Test set formula for the cells in array formula range with unsupported charset
+       f = NewFile()
+       f.Sheet.Delete("xl/worksheets/sheet1.xml")
+       f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
+       assert.EqualError(t, f.setArrayFormulaCells(), "XML syntax error on line 1: invalid UTF-8")
 }
 
 func ExampleFile_SetCellFloat() {
@@ -614,10 +633,10 @@ func TestSetCellFormula(t *testing.T) {
        assert.NoError(t, f.SetCellFormula("Sheet1", "C19", "SUM(Sheet2!D2,Sheet2!D9)"))
 
        // Test set cell formula with invalid sheet name
-       assert.EqualError(t, f.SetCellFormula("Sheet:1", "A1", "SUM(1,2)"), ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, f.SetCellFormula("Sheet:1", "A1", "SUM(1,2)"))
 
        // Test set cell formula with illegal rows number
-       assert.EqualError(t, f.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)"), newCellNameToCoordinatesError("C", newInvalidCellNameError("C")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("C", newInvalidCellNameError("C")), f.SetCellFormula("Sheet1", "C", "SUM(Sheet2!D2,Sheet2!D9)"))
 
        assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula1.xlsx")))
        assert.NoError(t, f.Close())
@@ -649,7 +668,7 @@ func TestSetCellFormula(t *testing.T) {
        ref = "D1:D5"
        assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}))
        ref = ""
-       assert.EqualError(t, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}), ErrParameterInvalid.Error())
+       assert.Equal(t, ErrParameterInvalid, f.SetCellFormula("Sheet1", "D1", "=A1+C1", FormulaOpts{Ref: &ref, Type: &formulaType}))
        assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula5.xlsx")))
 
        // Test set table formula for the cells
@@ -661,6 +680,14 @@ func TestSetCellFormula(t *testing.T) {
        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")))
+
+       // Test set array formula with invalid cell range reference
+       formulaType, ref = STCellFormulaTypeArray, ":"
+       assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), f.SetCellFormula("Sheet1", "B1", "A1:A2", FormulaOpts{Ref: &ref, Type: &formulaType}))
+
+       // Test set array formula with invalid cell reference
+       formulaType, ref = STCellFormulaTypeArray, "A1:A2"
+       assert.Equal(t, ErrColumnNumber, f.SetCellFormula("Sheet1", "A1", "SUM(XFE1:XFE2)", FormulaOpts{Ref: &ref, Type: &formulaType}))
 }
 
 func TestGetCellRichText(t *testing.T) {
@@ -742,7 +769,7 @@ func TestGetCellRichText(t *testing.T) {
        assert.EqualError(t, err, "sheet SheetN does not exist")
        // Test set cell rich text with illegal cell reference
        _, err = f.GetCellRichText("Sheet1", "A")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
        // Test set rich text color theme without tint
        assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTheme: &theme}}}))
        // Test set rich text color tint without theme
@@ -759,7 +786,7 @@ func TestGetCellRichText(t *testing.T) {
        assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
        // Test get cell rich text with invalid sheet name
        _, err = f.GetCellRichText("Sheet:1", "A1")
-       assert.EqualError(t, err, ErrSheetNameInvalid.Error())
+       assert.Equal(t, ErrSheetNameInvalid, err)
 }
 
 func TestSetCellRichText(t *testing.T) {
@@ -858,7 +885,7 @@ func TestSetCellRichText(t *testing.T) {
        // Test set cell rich text with invalid sheet name
        assert.EqualError(t, f.SetCellRichText("Sheet:1", "A1", richTextRun), ErrSheetNameInvalid.Error())
        // Test set cell rich text with illegal cell reference
-       assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.SetCellRichText("Sheet1", "A", richTextRun))
        richTextRun = []RichTextRun{{Text: strings.Repeat("s", TotalCellChars+1)}}
        // Test set cell rich text with characters over the maximum limit
        assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", richTextRun), ErrCellCharsLength.Error())
index 5c7f8d7..a1078f7 100644 (file)
--- a/chart.go
+++ b/chart.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
diff --git a/col.go b/col.go
index d5700ba..b51a283 100644 (file)
--- a/col.go
+++ b/col.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index d2d6f46..8bbb58b 100644 (file)
--- a/crypt.go
+++ b/crypt.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index c8735fc..d7fd055 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
@@ -39,7 +39,7 @@ func TestEncrypt(t *testing.T) {
        assert.NoError(t, err)
        raw[2050] = 3
        _, err = Decrypt(raw, &Options{Password: "password"})
-       assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
+       assert.Equal(t, ErrUnsupportedEncryptMechanism, err)
 
        // Test encrypt spreadsheet with invalid password
        assert.EqualError(t, f.SaveAs(filepath.Join("test", "Encryption.xlsx"), Options{Password: strings.Repeat("*", MaxFieldLength+1)}), ErrPasswordLengthInvalid.Error())
index 4a1f634..8e6e594 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 18816fb..8816df0 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
diff --git a/date.go b/date.go
index c7ab5aa..de39b9c 100644 (file)
--- a/date.go
+++ b/date.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 45957b7..f6d1489 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 9ec66cf..4456bfc 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 0dbc06e..92481bf 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index c8c95fd..3c25057 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 0d4aba0..b460dfd 100644 (file)
--- a/errors.go
+++ b/errors.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 05049ad..b869572 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 //
 // See https://xuri.me/excelize for more information about this package.
 package excelize
@@ -30,6 +30,7 @@ import (
 type File struct {
        mu               sync.Mutex
        checked          sync.Map
+       formulaChecked   bool
        options          *Options
        sharedStringItem [][]uint
        sharedStringsMap map[string]int
diff --git a/file.go b/file.go
index dc42e1e..894dd0a 100644 (file)
--- a/file.go
+++ b/file.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
diff --git a/lib.go b/lib.go
index 44a6ee5..a1e340c 100644 (file)
--- a/lib.go
+++ b/lib.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index af34a55..9acf54d 100644 (file)
--- a/merge.go
+++ b/merge.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 2034318..c4022ba 100644 (file)
--- a/numfmt.go
+++ b/numfmt.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index a7d6a2e..8b006f8 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index d9414d3..376f399 100644 (file)
@@ -60,7 +60,7 @@ func TestAddPicture(t *testing.T) {
        // Test add picture to worksheet from bytes
        assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}))
        // Test add picture to worksheet from bytes with illegal cell reference
-       assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}))
 
        for _, preset := range [][]string{{"Q8", "gif"}, {"Q15", "jpg"}, {"Q22", "tif"}, {"Q28", "bmp"}} {
                assert.NoError(t, f.AddPicture("Sheet1", preset[0], filepath.Join("test", "images", fmt.Sprintf("excel.%s", preset[1])), nil))
@@ -165,7 +165,7 @@ func TestGetPicture(t *testing.T) {
 
        // Try to get picture from a worksheet with illegal cell reference
        _, err = f.GetPictures("Sheet1", "A")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
 
        // Try to get picture from a worksheet that doesn't contain any images
        pics, err = f.GetPictures("Sheet3", "I9")
@@ -366,11 +366,11 @@ func TestDrawingResize(t *testing.T) {
        assert.EqualError(t, err, "sheet SheetN does not exist")
        // Test calculate drawing resize with invalid coordinates
        _, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil)
-       assert.EqualError(t, err, newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("", newInvalidCellNameError("")), err)
        ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
        assert.True(t, ok)
        ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
-       assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}))
 }
 
 func TestSetContentTypePartRelsExtensions(t *testing.T) {
index 405378c..0b6ad3b 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
diff --git a/rows.go b/rows.go
index 3869260..b87d45d 100644 (file)
--- a/rows.go
+++ b/rows.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index cd72e74..ad15715 100644 (file)
@@ -239,7 +239,7 @@ func TestColumns(t *testing.T) {
        rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
        assert.True(t, rows.Next())
        _, err = rows.Columns()
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
 
        // Test token is nil
        rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
index 5aa6ba2..cc05bf3 100644 (file)
--- a/shape.go
+++ b/shape.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 1f3a0e0..ff07231 100644 (file)
--- a/sheet.go
+++ b/sheet.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 044af91..eb9e571 100644 (file)
@@ -181,7 +181,7 @@ func TestSearchSheet(t *testing.T) {
 
        f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`))
        result, err = f.SearchSheet("Sheet1", "A")
-       assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
+       assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), err)
        assert.Equal(t, []string(nil), result)
 
        f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="0"><c r="A1" t="inlineStr"><is><t>A</t></is></c></row></sheetData></worksheet>`))
index 665541f..f9d5172 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 6edb87f..fbc2d1d 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index c1562ff..a7f26ed 100644 (file)
--- a/slicer.go
+++ b/slicer.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 1ecdd69..5d872c3 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index adf9b8b..8fbbcfd 100644 (file)
--- a/stream.go
+++ b/stream.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index d90502a..489ce1d 100644 (file)
--- a/styles.go
+++ b/styles.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index d5d1fc4..ea49d3c 100644 (file)
--- a/table.go
+++ b/table.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 7028a5b..d9fb18c 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 //
 // This file contains default templates for XML files we don't yet populated
 // based on content.
diff --git a/vml.go b/vml.go
index 3c221b5..9250e17 100644 (file)
--- a/vml.go
+++ b/vml.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index c84c3c9..2c3a69a 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index ad58402..be18d09 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 5dd26f0..44db6c9 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 9432886..10a93d4 100644 (file)
--- a/xmlApp.go
+++ b/xmlApp.go
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 81ee47a..785bcba 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 60406b7..ee97a69 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 47a26e5..cc80b2d 100644 (file)
@@ -9,7 +9,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 8b997c2..5eb328a 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index beec2ba..0c00a54 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 7b9be61..ac8f745 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 8077af8..8cd7625 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 41c25ee..5cca3cf 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 14b1db0..c04ebbd 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 8275bb6..41405c3 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index f17e291..6133ad4 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 036f7a5..6e68897 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 9255733..02f4bec 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 79dd1b5..a2bb2bc 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 5f46da9..ec0c2bd 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index 423c9eb..6d489e9 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
index a897718..83e0805 100644 (file)
@@ -7,7 +7,7 @@
 // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
 // Supports complex components by high compatibility, and provided streaming
 // API for generating or reading data from a worksheet with huge amounts of
-// data. This library needs Go version 1.16 or later.
+// data. This library needs Go version 1.18 or later.
 
 package excelize
 
@@ -477,6 +477,7 @@ type xlsxC struct {
        F        *xlsxF   `xml:"f"`           // Formula
        V        string   `xml:"v,omitempty"` // Value
        IS       *xlsxSI  `xml:"is"`
+       f        string
 }
 
 // xlsxF represents a formula for the cell. The formula expression is