Skip to content

Commit

Permalink
Fix file corrupted in some cases, check file extension and format code
Browse files Browse the repository at this point in the history
Fix file corrupted when save as in XLAM / XLSM / XLTM / XLTX extension in some case
New exported error ErrWorkbookExt has been added, and check file extension on save the workbook
Format source code with `gofumpt`
  • Loading branch information
xuri committed Jan 22, 2022
1 parent f1dc176 commit 35d564d
Show file tree
Hide file tree
Showing 33 changed files with 225 additions and 150 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
~$*.xlsx
test/Test*.xlsx
test/Test*.xlam
test/Test*.xlsm
test/Test*.xlsx
test/Test*.xltm
test/Test*.xltx
# generated files
test/BadEncrypt.xlsx
test/BadWorkbook.SaveAsEmptyStruct.xlsx
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## Introduction

Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLSX / XLSM / XLTM / XLTX files. Supports reading and 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.15 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/).
Excelize is a library written in pure Go providing a set of functions that allow you to write to and read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and 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.15 or later. The full API docs can be seen using go's built-in documentation tool, or online at [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) and [docs reference](https://xuri.me/excelize/).

## Basic Usage

Expand Down
2 changes: 1 addition & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

## 简介

Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLSX / XLSM / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.15 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式,高度兼容带有样式、图片(表)、透视表、切片器等复杂组件的文档,并提供流式读写 API,用于处理包含大规模数据的工作簿。可应用于各类报表平台、云计算、边缘计算等系统。使用本类库要求使用的 Go 语言为 1.15 或更高版本,完整的 API 使用文档请访问 [go.dev](https://pkg.go.dev/github.com/xuri/excelize/v2) 或查看 [参考文档](https://xuri.me/excelize/)

## 快速上手

Expand Down
6 changes: 4 additions & 2 deletions adjust_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,11 @@ func TestAdjustHelper(t *testing.T) {
f := NewFile()
f.NewSheet("Sheet2")
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}})
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
})
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}})
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
})
// testing adjustHelper with illegal cell coordinates.
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
Expand Down
134 changes: 103 additions & 31 deletions calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1377,7 +1377,7 @@ func (f *File) rangeResolver(cellRefs, cellRanges *list.List) (arg formulaArg, e
if cellRanges.Len() > 0 {
arg.Type = ArgMatrix
for row := valueRange[0]; row <= valueRange[1]; row++ {
var matrixRow = []formulaArg{}
matrixRow := []formulaArg{}
for col := valueRange[2]; col <= valueRange[3]; col++ {
var cell, value string
if cell, err = CoordinatesToCellName(col, row); err != nil {
Expand Down Expand Up @@ -1473,7 +1473,7 @@ func formulaCriteriaParser(exp string) (fc *formulaCriteria) {
func formulaCriteriaEval(val string, criteria *formulaCriteria) (result bool, err error) {
var value, expected float64
var e error
var prepareValue = func(val, cond string) (value float64, expected float64, err error) {
prepareValue := func(val, cond string) (value float64, expected float64, err error) {
if value, err = strconv.ParseFloat(val, 64); err != nil {
return
}
Expand Down Expand Up @@ -3385,7 +3385,7 @@ func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments")
}
var text = argsList.Front().Value.(formulaArg).String
text := argsList.Front().Value.(formulaArg).String
var radix int
var err error
radix, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String)
Expand Down Expand Up @@ -3934,7 +3934,7 @@ func (fn *formulaFuncs) MDETERM(argsList *list.List) (result formulaArg) {
return newErrorFormulaArg(formulaErrorVALUE, "MDETERM requires at least 1 argument")
}
strMtx = argsList.Front().Value.(formulaArg).Matrix
var rows = len(strMtx)
rows := len(strMtx)
for _, row := range argsList.Front().Value.(formulaArg).Matrix {
if len(row) != rows {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
Expand Down Expand Up @@ -4257,30 +4257,107 @@ type romanNumerals struct {

var romanTable = [][]romanNumerals{
{
{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"},
{50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {450, "LD"}, {400, "CD"},
{100, "C"}, {95, "VC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
{1000, "M"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{95, "VC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"}, {490, "XD"},
{450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"},
{45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
{1000, "M"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"}, {500, "D"},
{495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"}, {100, "C"}, {99, "IC"},
{90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"}, {10, "X"}, {9, "IX"},
{5, "V"}, {4, "IV"}, {1, "I"},
{1000, "M"},
{995, "VM"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{495, "VD"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
{
{1000, "M"}, {999, "IM"}, {995, "VM"}, {990, "XM"}, {950, "LM"}, {900, "CM"},
{500, "D"}, {499, "ID"}, {495, "VD"}, {490, "XD"}, {450, "LD"}, {400, "CD"},
{100, "C"}, {99, "IC"}, {90, "XC"}, {50, "L"}, {45, "VL"}, {40, "XL"},
{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
{1000, "M"},
{999, "IM"},
{995, "VM"},
{990, "XM"},
{950, "LM"},
{900, "CM"},
{500, "D"},
{499, "ID"},
{495, "VD"},
{490, "XD"},
{450, "LD"},
{400, "CD"},
{100, "C"},
{99, "IC"},
{90, "XC"},
{50, "L"},
{45, "VL"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
},
}

Expand Down Expand Up @@ -4751,8 +4828,8 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "SUMIF requires at least 2 arguments")
}
var criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
var rangeMtx = argsList.Front().Value.(formulaArg).Matrix
criteria := formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
rangeMtx := argsList.Front().Value.(formulaArg).Matrix
var sumRange [][]formulaArg
if argsList.Len() == 3 {
sumRange = argsList.Back().Value.(formulaArg).Matrix
Expand Down Expand Up @@ -5886,7 +5963,7 @@ func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument")
}
var values = []float64{}
values := []float64{}
var median, digits float64
var err error
for token := argsList.Front(); token != nil; token = token.Next() {
Expand Down Expand Up @@ -9047,7 +9124,6 @@ func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte
if matchPattern(rs, ls) {
return criteriaEq
}

}
return map[int]byte{1: criteriaG, -1: criteriaL, 0: criteriaEq}[strings.Compare(ls, rs)]
case ArgEmpty:
Expand Down Expand Up @@ -9931,9 +10007,8 @@ func (fn *formulaFuncs) ACCRINT(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
}
cm := newBoolFormulaArg(true)
if argsList.Len() == 8 {
if cm = argsList.Back().Value.(formulaArg).ToBool(); cm.Type != ArgNumber {
if cm := argsList.Back().Value.(formulaArg).ToBool(); cm.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
}
Expand Down Expand Up @@ -10206,10 +10281,8 @@ func coupdays(from, to time.Time, basis int) float64 {
date = date.AddDate(0, 13-int(date.Month()), 0)
}
days += getDaysInMonthRange(toY, int(date.Month()), int(toM)-1, basis)
date = date.AddDate(0, int(toM)-int(date.Month()), 0)
}
days += toDay - fromDay
if days > 0 {
if days += toDay - fromDay; days > 0 {
return float64(days)
}
return 0
Expand Down Expand Up @@ -11375,8 +11448,7 @@ func (fn *formulaFuncs) ODDFPRICE(argsList *list.List) formulaArg {
if args.Type != ArgList {
return args
}
settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basisArg :=
args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8]
settlement, maturity, issue, firstCoupon, rate, yld, redemption, frequency, basisArg := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5], args.List[6], args.List[7], args.List[8]
if basisArg.Number < 0 || basisArg.Number > 4 {
return newErrorFormulaArg(formulaErrorNUM, "invalid basis")
}
Expand Down
11 changes: 6 additions & 5 deletions calc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3386,7 +3386,6 @@ func TestCalcCellValue(t *testing.T) {
_, err = f.CalcCellValue("Sheet1", "A1")
assert.EqualError(t, err, "not support UNSUPPORT function")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx")))

}

func TestCalculate(t *testing.T) {
Expand Down Expand Up @@ -3438,7 +3437,6 @@ func TestCalcWithDefinedName(t *testing.T) {
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "YES", result, `=IF("B1_as_string"=defined_name1,"YES","NO")`)

}

func TestCalcISBLANK(t *testing.T) {
Expand Down Expand Up @@ -3748,7 +3746,8 @@ func TestCalcXIRR(t *testing.T) {
{25.00, "02/01/2017"},
{8.00, "03/01/2017"},
{15.00, "06/01/2017"},
{-1e-10, "09/01/2017"}}
{-1e-10, "09/01/2017"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"=XIRR(A1:A4,B1:B4)": "-0.196743861298328",
Expand Down Expand Up @@ -3886,7 +3885,8 @@ func TestCalcXLOOKUP(t *testing.T) {
}

func TestCalcXNPV(t *testing.T) {
cellData := [][]interface{}{{nil, 0.05},
cellData := [][]interface{}{
{nil, 0.05},
{"01/01/2016", -10000, nil},
{"02/01/2016", 2000},
{"05/01/2016", 2400},
Expand All @@ -3895,7 +3895,8 @@ func TestCalcXNPV(t *testing.T) {
{"01/01/2017", 4100},
{},
{"02/01/2016"},
{"01/01/2016"}}
{"01/01/2016"},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"=XNPV(B1,B2:B7,A2:A7)": "4447.938009440515",
Expand Down
9 changes: 6 additions & 3 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ func TestConcurrency(t *testing.T) {
_, err := f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
assert.NoError(t, err)
// Concurrency set rows
assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{" Hello",
assert.NoError(t, f.SetSheetRow("Sheet1", "B6", &[]interface{}{
" Hello",
[]byte("World"), 42, int8(1<<8/2 - 1), int16(1<<16/2 - 1), int32(1<<32/2 - 1),
int64(1<<32/2 - 1), float32(42.65418), float64(-42.65418), float32(42), float64(42),
uint(1<<32 - 1), uint8(1<<8 - 1), uint16(1<<16 - 1), uint32(1<<32 - 1),
uint64(1<<32 - 1), true, complex64(5 + 10i)}))
uint64(1<<32 - 1), true, complex64(5 + 10i),
}))
// Concurrency create style
style, err := f.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
assert.NoError(t, err)
Expand Down Expand Up @@ -384,7 +386,7 @@ func TestGetCellFormula(t *testing.T) {

func ExampleFile_SetCellFloat() {
f := NewFile()
var x = 3.14159265
x := 3.14159265
if err := f.SetCellFloat("Sheet1", "A1", x, 2, 64); err != nil {
fmt.Println(err)
}
Expand Down Expand Up @@ -534,6 +536,7 @@ func TestGetCellRichText(t *testing.T) {
_, err = f.GetCellRichText("Sheet1", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}

func TestSetCellRichText(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetRowHeight("Sheet1", 1, 35))
Expand Down
2 changes: 1 addition & 1 deletion comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (f *File) GetComments() (comments map[string][]Comment) {
// getSheetComments provides the method to get the target comment reference by
// given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string {
var rels = "xl/worksheets/_rels/" + sheetFile + ".rels"
rels := "xl/worksheets/_rels/" + sheetFile + ".rels"
if sheetRels := f.relsReader(rels); sheetRels != nil {
sheetRels.Lock()
defer sheetRels.Unlock()
Expand Down
Loading

0 comments on commit 35d564d

Please sign in to comment.