From 3ee3c38f9c63de3782fad21aae9c05ee0530fc32 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 23 Jan 2022 00:32:34 +0800 Subject: [PATCH] Fix file corrupted in some cases, check file extension and format code 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` --- .gitignore | 5 +- README.md | 2 +- README_zh.md | 2 +- adjust_test.go | 6 ++- calc.go | 134 +++++++++++++++++++++++++++++++++++----------- calc_test.go | 11 ++-- cell_test.go | 9 ++-- comment.go | 2 +- crypt.go | 29 +++++----- datavalidation.go | 2 - date.go | 5 +- docProps.go | 7 ++- drawing.go | 9 ++-- errors.go | 3 ++ excelize.go | 11 ++-- excelize_test.go | 10 ++-- file.go | 11 ++++ lib.go | 6 +-- lib_test.go | 1 - picture.go | 9 ++-- picture_test.go | 7 ++- rows.go | 2 +- rows_test.go | 3 +- shape.go | 5 +- sheet.go | 4 +- sheet_test.go | 2 +- sheetpr_test.go | 3 -- sheetview_test.go | 1 - styles.go | 8 +-- templates.go | 8 ++- xmlDrawing.go | 4 ++ xmlPivotCache.go | 45 ++++++---------- xmlPivotTable.go | 9 ++-- 33 files changed, 225 insertions(+), 150 deletions(-) diff --git a/.gitignore b/.gitignore index 68532a7f91..4dce768053 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index a0d2d3ddd3..8e16a88b03 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_zh.md b/README_zh.md index 919e954be4..dafdd93f1e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -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/)。 ## 快速上手 diff --git a/adjust_test.go b/adjust_test.go index b2ec3c46aa..ab6bedcc06 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -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()) diff --git a/calc.go b/calc.go index 89eb0879fc..b3c6df8228 100644 --- a/calc.go +++ b/calc.go @@ -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 { @@ -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 } @@ -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) @@ -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) @@ -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"}, }, } @@ -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 @@ -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() { @@ -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: @@ -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) } } @@ -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 @@ -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") } diff --git a/calc_test.go b/calc_test.go index a9899d1650..c3be3080bb 100644 --- a/calc_test.go +++ b/calc_test.go @@ -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) { @@ -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) { @@ -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", @@ -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}, @@ -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", diff --git a/cell_test.go b/cell_test.go index 8d00e2def2..f6f1098ddc 100644 --- a/cell_test.go +++ b/cell_test.go @@ -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) @@ -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) } @@ -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)) diff --git a/comment.go b/comment.go index aa066ec0cc..c0dc33b97f 100644 --- a/comment.go +++ b/comment.go @@ -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() diff --git a/crypt.go b/crypt.go index 91beab290f..9912a6753d 100644 --- a/crypt.go +++ b/crypt.go @@ -168,16 +168,20 @@ func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { HashAlgorithm: "SHA512", SaltValue: base64.StdEncoding.EncodeToString(keyDataSaltValue), }, - KeyEncryptors: KeyEncryptors{KeyEncryptor: []KeyEncryptor{{ - EncryptedKey: EncryptedKey{SpinCount: 100000, KeyData: KeyData{ - CipherAlgorithm: "AES", - CipherChaining: "ChainingModeCBC", - HashAlgorithm: "SHA512", - HashSize: 64, - BlockSize: 16, - KeyBits: 256, - SaltValue: base64.StdEncoding.EncodeToString(keyEncryptors)}, - }}}, + KeyEncryptors: KeyEncryptors{ + KeyEncryptor: []KeyEncryptor{{ + EncryptedKey: EncryptedKey{ + SpinCount: 100000, KeyData: KeyData{ + CipherAlgorithm: "AES", + CipherChaining: "ChainingModeCBC", + HashAlgorithm: "SHA512", + HashSize: 64, + BlockSize: 16, + KeyBits: 256, + SaltValue: base64.StdEncoding.EncodeToString(keyEncryptors), + }, + }, + }}, }, } @@ -481,7 +485,7 @@ func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) ( // hashing data by specified hash algorithm. func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) { - var hashMap = map[string]hash.Hash{ + hashMap := map[string]hash.Hash{ "md4": md4.New(), "md5": md5.New(), "ripemd-160": ripemd160.New(), @@ -535,8 +539,7 @@ func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input // cryptPackage encrypt / decrypt package by given packageKey and encryption // info. func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) { - encryptedKey := encryption.KeyData - var offset = packageOffset + encryptedKey, offset := encryption.KeyData, packageOffset if encrypt { offset = 0 } diff --git a/datavalidation.go b/datavalidation.go index c8c9141a12..b8f939b64e 100644 --- a/datavalidation.go +++ b/datavalidation.go @@ -199,7 +199,6 @@ func convDataValidationType(t DataValidationType) string { } return typeMap[t] - } // convDataValidationOperator get excel data validation operator. @@ -216,7 +215,6 @@ func convDataValidationOperator(o DataValidationOperator) string { } return typeMap[o] - } // AddDataValidation provides set data validation on a range of the worksheet diff --git a/date.go b/date.go index 9923f9f1a6..04c9110e46 100644 --- a/date.go +++ b/date.go @@ -82,7 +82,6 @@ func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) { // minutes, seconds and nanoseconds that comprised a given fraction of a day. // values would round to 1 us. func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) { - const ( c1us = 1e3 c1s = 1e9 @@ -137,7 +136,7 @@ func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) { // representation (stored as a floating point number) to a time.Time. func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { var date time.Time - var wholeDaysPart = int(excelTime) + wholeDaysPart := int(excelTime) // Excel uses Julian dates prior to March 1st 1900, and Gregorian // thereafter. if wholeDaysPart <= 61 { @@ -152,7 +151,7 @@ func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { } return date } - var floatPart = excelTime - float64(wholeDaysPart) + roundEpsilon + floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon if date1904 { date = excel1904Epoc } else { diff --git a/docProps.go b/docProps.go index 770ed1a7f8..44c30c889d 100644 --- a/docProps.go +++ b/docProps.go @@ -101,7 +101,7 @@ func (f *File) SetAppProps(appProperties *AppProperties) (err error) { // GetAppProps provides a function to get document application properties. func (f *File) GetAppProps() (ret *AppProperties, err error) { - var app = new(xlsxProperties) + app := new(xlsxProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))). Decode(app); err != nil && err != io.EOF { err = fmt.Errorf("xml decode error: %s", err) @@ -204,8 +204,7 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) { Category: core.Category, Version: core.Version, }, nil - newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type = - core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type + newProps.Created.Text, newProps.Created.Type, newProps.Modified.Text, newProps.Modified.Type = core.Created.Text, core.Created.Type, core.Modified.Text, core.Modified.Type fields = []string{ "Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords", "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version", @@ -230,7 +229,7 @@ func (f *File) SetDocProps(docProperties *DocProperties) (err error) { // GetDocProps provides a function to get document core properties. func (f *File) GetDocProps() (ret *DocProperties, err error) { - var core = new(decodeCoreProperties) + core := new(decodeCoreProperties) if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))). Decode(core); err != nil && err != io.EOF { diff --git a/drawing.go b/drawing.go index be4583ccaf..ac88032e22 100644 --- a/drawing.go +++ b/drawing.go @@ -157,7 +157,8 @@ func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) { Cmpd: "sng", Algn: "ctr", SolidFill: &aSolidFill{ - SchemeClr: &aSchemeClr{Val: "tx1", + SchemeClr: &aSchemeClr{ + Val: "tx1", LumMod: &attrValInt{ Val: intPtr(15000), }, @@ -941,7 +942,8 @@ func (f *File) drawChartDLbls(formatSet *formatChart) *cDLbls { func (f *File) drawChartSeriesDLbls(formatSet *formatChart) *cDLbls { dLbls := f.drawChartDLbls(formatSet) chartSeriesDLbls := map[string]*cDLbls{ - Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil} + Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil, + } if _, ok := chartSeriesDLbls[formatSet.Type]; ok { return nil } @@ -1194,8 +1196,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) - colStart, rowStart, colEnd, rowEnd, x2, y2 := - f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height) + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.OffsetX, formatSet.OffsetY, width, height) content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning diff --git a/errors.go b/errors.go index 8368fee9a3..ebbcef6c27 100644 --- a/errors.go +++ b/errors.go @@ -106,6 +106,9 @@ var ( // ErrImgExt defined the error message on receive an unsupported image // extension. ErrImgExt = errors.New("unsupported image extension") + // ErrWorkbookExt defined the error message on receive an unsupported + // workbook extension. + ErrWorkbookExt = errors.New("unsupported workbook extension") // ErrMaxFileNameLength defined the error message on receive the file name // length overflow. ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit") diff --git a/excelize.go b/excelize.go index 6100ac457a..2155f0affb 100644 --- a/excelize.go +++ b/excelize.go @@ -327,7 +327,7 @@ func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) { // addRels provides a function to add relationships by given XML path, // relationship type, target and target mode. func (f *File) addRels(relPath, relType, target, targetMode string) int { - var uniqPart = map[string]string{ + uniqPart := map[string]string{ SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", } rels := f.relsReader(relPath) @@ -434,7 +434,6 @@ func (f *File) AddVBAProject(bin string) error { if path.Ext(bin) != ".bin" { return ErrAddVBAProject } - f.setContentTypePartVBAProjectExtensions() wb := f.relsReader(f.getWorkbookRelsPath()) wb.Lock() defer wb.Unlock() @@ -463,9 +462,9 @@ func (f *File) AddVBAProject(bin string) error { return err } -// setContentTypePartVBAProjectExtensions provides a function to set the -// content type for relationship parts and the main document part. -func (f *File) setContentTypePartVBAProjectExtensions() { +// setContentTypePartProjectExtensions provides a function to set the content +// type for relationship parts and the main document part. +func (f *File) setContentTypePartProjectExtensions(contentType string) { var ok bool content := f.contentTypesReader() content.Lock() @@ -477,7 +476,7 @@ func (f *File) setContentTypePartVBAProjectExtensions() { } for idx, o := range content.Overrides { if o.PartName == "/xl/workbook.xml" { - content.Overrides[idx].ContentType = ContentTypeMacro + content.Overrides[idx].ContentType = contentType } } if !ok { diff --git a/excelize_test.go b/excelize_test.go index c78797d974..1548cc6f5a 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -175,7 +175,10 @@ func TestSaveFile(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsx"))) + assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestSaveFile.xlsb")), ErrWorkbookExt.Error()) + for _, ext := range []string{".xlam", ".xlsm", ".xlsx", ".xltm", ".xltx"} { + assert.NoError(t, f.SaveAs(filepath.Join("test", fmt.Sprintf("TestSaveFile%s", ext)))) + } assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestSaveFile.xlsx")) if !assert.NoError(t, err) { @@ -189,7 +192,7 @@ func TestSaveAsWrongPath(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) // Test write file to not exist directory. - assert.EqualError(t, f.SaveAs(""), "open .: is a directory") + assert.Error(t, f.SaveAs(filepath.Join("x", "Book1.xlsx"))) assert.NoError(t, f.Close()) } @@ -1305,7 +1308,8 @@ func TestDeleteSheetFromWorkbookRels(t *testing.T) { func TestAttrValToInt(t *testing.T) { _, err := attrValToInt("r", []xml.Attr{ - {Name: xml.Name{Local: "r"}, Value: "s"}}) + {Name: xml.Name{Local: "r"}, Value: "s"}, + }) assert.EqualError(t, err, `strconv.Atoi: parsing "s": invalid syntax`) } diff --git a/file.go b/file.go index 1f2b772ff0..43d94b2fb1 100644 --- a/file.go +++ b/file.go @@ -70,6 +70,17 @@ func (f *File) SaveAs(name string, opt ...Options) error { return ErrMaxFileNameLength } f.Path = name + contentType, ok := map[string]string{ + ".xlam": ContentTypeAddinMacro, + ".xlsm": ContentTypeMacro, + ".xlsx": ContentTypeSheetML, + ".xltm": ContentTypeTemplateMacro, + ".xltx": ContentTypeTemplate, + }[filepath.Ext(f.Path)] + if !ok { + return ErrWorkbookExt + } + f.setContentTypePartProjectExtensions(contentType) file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) if err != nil { return err diff --git a/lib.go b/lib.go index caaeab2f90..a435452aff 100644 --- a/lib.go +++ b/lib.go @@ -490,7 +490,7 @@ func parseFormatSet(formatSet string) []byte { // namespaceStrictToTransitional provides a method to convert Strict and // Transitional namespaces. func namespaceStrictToTransitional(content []byte) []byte { - var namespaceTranslationDic = map[string]string{ + namespaceTranslationDic := map[string]string{ StrictSourceRelationship: SourceRelationship.Value, StrictSourceRelationshipOfficeDocument: SourceRelationshipOfficeDocument, StrictSourceRelationshipChart: SourceRelationshipChart, @@ -611,8 +611,8 @@ func getXMLNamespace(space string, attr []xml.Attr) string { // replaceNameSpaceBytes provides a function to replace the XML root element // attribute by the given component part path and XML content. func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte { - var oldXmlns = []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) - var newXmlns = []byte(templateNamespaceIDMap) + oldXmlns := []byte(`xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`) + newXmlns := []byte(templateNamespaceIDMap) if attr, ok := f.xmlAttr[path]; ok { newXmlns = []byte(genXMLNamespace(attr)) } diff --git a/lib_test.go b/lib_test.go index 3346acc6d6..da75dee5b5 100644 --- a/lib_test.go +++ b/lib_test.go @@ -159,7 +159,6 @@ func TestJoinCellName_Error(t *testing.T) { test(col.Name, row) } } - } func TestCellNameToCoordinates_OK(t *testing.T) { diff --git a/picture.go b/picture.go index 9429f4a97d..f018bd9ae3 100644 --- a/picture.go +++ b/picture.go @@ -209,7 +209,7 @@ func (f *File) deleteSheetRelationships(sheet, rID string) { if !ok { name = strings.ToLower(sheet) + ".xml" } - var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" sheetRels := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} @@ -288,8 +288,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he } col-- row-- - colStart, rowStart, colEnd, rowEnd, x2, y2 := - f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning @@ -372,7 +371,7 @@ func (f *File) addMedia(file []byte, ext string) string { // setContentTypePartImageExtensions provides a function to set the content // type for relationship parts and the Main Document part. func (f *File) setContentTypePartImageExtensions() { - var imageTypes = map[string]bool{"jpeg": false, "png": false, "gif": false, "tiff": false} + imageTypes := map[string]bool{"jpeg": false, "png": false, "gif": false, "tiff": false} content := f.contentTypesReader() content.Lock() defer content.Unlock() @@ -465,7 +464,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { if !ok { name = strings.ToLower(sheet) + ".xml" } - var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" sheetRels := f.relsReader(rels) if sheetRels == nil { sheetRels = &xlsxRelationships{} diff --git a/picture_test.go b/picture_test.go index 5bf139d579..8da7c3d870 100644 --- a/picture_test.go +++ b/picture_test.go @@ -1,19 +1,18 @@ package excelize import ( + "fmt" _ "image/gif" _ "image/jpeg" _ "image/png" - - _ "golang.org/x/image/tiff" - - "fmt" "io/ioutil" "os" "path/filepath" "strings" "testing" + _ "golang.org/x/image/tiff" + "github.com/stretchr/testify/assert" ) diff --git a/rows.go b/rows.go index 8702f93340..88df52c7af 100644 --- a/rows.go +++ b/rows.go @@ -362,7 +362,7 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) { if row < 1 { return defaultRowHeightPixels, newInvalidRowNumberError(row) } - var ht = defaultRowHeight + ht := defaultRowHeight ws, err := f.workSheetReader(sheet) if err != nil { return ht, err diff --git a/rows_test.go b/rows_test.go index 0d2ca23026..4d81e6698b 100644 --- a/rows_test.go +++ b/rows_test.go @@ -847,7 +847,8 @@ func TestDuplicateRowTo(t *testing.T) { assert.Equal(t, nil, f.DuplicateRowTo(sheetName, 1, 2)) // Test duplicate row on the worksheet with illegal cell coordinates f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ - MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}}) + MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, + }) assert.EqualError(t, f.DuplicateRowTo(sheetName, 1, 2), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test duplicate row on not exists worksheet assert.EqualError(t, f.DuplicateRowTo("SheetN", 1, 2), "sheet SheetN is not exist") diff --git a/shape.go b/shape.go index 974aa5fce8..be3735baeb 100644 --- a/shape.go +++ b/shape.go @@ -351,9 +351,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format width := int(float64(formatSet.Width) * formatSet.Format.XScale) height := int(float64(formatSet.Height) * formatSet.Format.YScale) - colStart, rowStart, colEnd, rowEnd, x2, y2 := - f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY, - width, height) + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, formatSet.Format.OffsetX, formatSet.Format.OffsetY, + width, height) content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Format.Positioning diff --git a/sheet.go b/sheet.go index 2426bf8a51..f10ca07d04 100644 --- a/sheet.go +++ b/sheet.go @@ -368,7 +368,7 @@ func (f *File) SetActiveSheet(index int) { // GetActiveSheetIndex provides a function to get active sheet index of the // spreadsheet. If not found the active sheet will be return integer 0. func (f *File) GetActiveSheetIndex() (index int) { - var sheetID = f.getActiveSheetID() + sheetID := f.getActiveSheetID() wb := f.workbookReader() if wb != nil { for idx, sheet := range wb.Sheets.Sheet { @@ -1723,7 +1723,7 @@ func (f *File) UngroupSheets() error { func (f *File) InsertPageBreak(sheet, cell string) (err error) { var ws *xlsxWorksheet var row, col int - var rowBrk, colBrk = -1, -1 + rowBrk, colBrk := -1, -1 if ws, err = f.workSheetReader(sheet); err != nil { return } diff --git a/sheet_test.go b/sheet_test.go index a5c99a6f16..7df9018b1c 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -123,7 +123,6 @@ func TestPageLayoutOption(t *testing.T) { for i, test := range testData { t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) { - opt := test.nonDefault t.Logf("option %T", opt) @@ -452,6 +451,7 @@ func BenchmarkNewSheet(b *testing.B) { } }) } + func newSheetWithSet() { file := NewFile() file.NewSheet("sheet1") diff --git a/sheetpr_test.go b/sheetpr_test.go index 7c669e863a..91685d8837 100644 --- a/sheetpr_test.go +++ b/sheetpr_test.go @@ -108,7 +108,6 @@ func TestSheetPrOptions(t *testing.T) { for i, test := range testData { t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) { - opt := test.nonDefault t.Logf("option %T", opt) @@ -258,7 +257,6 @@ func TestPageMarginsOption(t *testing.T) { for i, test := range testData { t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) { - opt := test.nonDefault t.Logf("option %T", opt) @@ -395,7 +393,6 @@ func TestSheetFormatPrOptions(t *testing.T) { for i, test := range testData { t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) { - opt := test.nonDefault t.Logf("option %T", opt) diff --git a/sheetview_test.go b/sheetview_test.go index e323e2380c..9207decca3 100644 --- a/sheetview_test.go +++ b/sheetview_test.go @@ -81,7 +81,6 @@ func ExampleFile_SetSheetViewOptions() { // - zoomScale: 80 // Used correct value: // - zoomScale: 123 - } func ExampleFile_GetSheetViewOptions() { diff --git a/styles.go b/styles.go index 261e3df4dd..5c887dbd75 100644 --- a/styles.go +++ b/styles.go @@ -963,7 +963,7 @@ func parseTime(v string, format string) string { goFmt = format if strings.Contains(goFmt, "[") { - var re = regexp.MustCompile(`\[.+\]`) + re := regexp.MustCompile(`\[.+\]`) goFmt = re.ReplaceAllLiteralString(goFmt, "") } @@ -2388,7 +2388,7 @@ func getFillID(styleSheet *xlsxStyleSheet, style *Style) (fillID int) { // newFills provides a function to add fill elements in the styles.xml by // given cell format settings. func newFills(style *Style, fg bool) *xlsxFill { - var patterns = []string{ + patterns := []string{ "none", "solid", "mediumGray", @@ -2410,7 +2410,7 @@ func newFills(style *Style, fg bool) *xlsxFill { "gray0625", } - var variants = []float64{ + variants := []float64{ 90, 0, 45, @@ -2522,7 +2522,7 @@ func getBorderID(styleSheet *xlsxStyleSheet, style *Style) (borderID int) { // newBorders provides a function to add border elements in the styles.xml by // given borders format settings. func newBorders(style *Style) *xlsxBorder { - var styles = []string{ + styles := []string{ "none", "thin", "medium", diff --git a/templates.go b/templates.go index 81b2c208ca..d08f45ffd7 100644 --- a/templates.go +++ b/templates.go @@ -16,11 +16,9 @@ package excelize import "encoding/xml" -var ( - // XMLHeaderByte define an XML declaration can also contain a standalone - // declaration. - XMLHeaderByte = []byte(xml.Header) -) +// XMLHeaderByte define an XML declaration can also contain a standalone +// declaration. +var XMLHeaderByte = []byte(xml.Header) const ( defaultXMLPathContentTypes = "[Content_Types].xml" diff --git a/xmlDrawing.go b/xmlDrawing.go index f51451bfe2..61c25ca810 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -65,7 +65,11 @@ const ( NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml" ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" + ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" + ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml" ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" + ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" diff --git a/xmlPivotCache.go b/xmlPivotCache.go index 7f3dac0a3a..9591858384 100644 --- a/xmlPivotCache.go +++ b/xmlPivotCache.go @@ -79,8 +79,7 @@ type xlsxWorksheetSource struct { // PivotTable is a collection of ranges in the workbook. The ranges are // specified in the rangeSets collection. The logic for how the application // consolidates the data in the ranges is application- defined. -type xlsxConsolidation struct { -} +type xlsxConsolidation struct{} // xlsxCacheFields represents the collection of field definitions in the // source data. @@ -144,8 +143,7 @@ type xlsxSharedItems struct { } // xlsxMissing represents a value that was not specified. -type xlsxMissing struct { -} +type xlsxMissing struct{} // xlsxNumber represents a numeric value in the PivotTable. type xlsxNumber struct { @@ -167,18 +165,15 @@ type xlsxNumber struct { // xlsxTuples represents members for the OLAP sheet data entry, also known as // a tuple. -type xlsxTuples struct { -} +type xlsxTuples struct{} // xlsxBoolean represents a boolean value for an item in the PivotTable. -type xlsxBoolean struct { -} +type xlsxBoolean struct{} // xlsxError represents an error value. The use of this item indicates that an // error value is present in the PivotTable source. The error is recorded in // the value attribute. -type xlsxError struct { -} +type xlsxError struct{} // xlsxString represents a character value in a PivotTable. type xlsxString struct { @@ -199,45 +194,35 @@ type xlsxString struct { } // xlsxDateTime represents a date-time value in the PivotTable. -type xlsxDateTime struct { -} +type xlsxDateTime struct{} // xlsxFieldGroup represents the collection of properties for a field group. -type xlsxFieldGroup struct { -} +type xlsxFieldGroup struct{} // xlsxCacheHierarchies represents the collection of OLAP hierarchies in the // PivotCache. -type xlsxCacheHierarchies struct { -} +type xlsxCacheHierarchies struct{} // xlsxKpis represents the collection of Key Performance Indicators (KPIs) // defined on the OLAP server and stored in the PivotCache. -type xlsxKpis struct { -} +type xlsxKpis struct{} // xlsxTupleCache represents the cache of OLAP sheet data members, or tuples. -type xlsxTupleCache struct { -} +type xlsxTupleCache struct{} // xlsxCalculatedItems represents the collection of calculated items. -type xlsxCalculatedItems struct { -} +type xlsxCalculatedItems struct{} // xlsxCalculatedMembers represents the collection of calculated members in an // OLAP PivotTable. -type xlsxCalculatedMembers struct { -} +type xlsxCalculatedMembers struct{} // xlsxDimensions represents the collection of PivotTable OLAP dimensions. -type xlsxDimensions struct { -} +type xlsxDimensions struct{} // xlsxMeasureGroups represents the collection of PivotTable OLAP measure // groups. -type xlsxMeasureGroups struct { -} +type xlsxMeasureGroups struct{} // xlsxMaps represents the PivotTable OLAP measure group - Dimension maps. -type xlsxMaps struct { -} +type xlsxMaps struct{} diff --git a/xmlPivotTable.go b/xmlPivotTable.go index 38dfb1e487..a7543a747e 100644 --- a/xmlPivotTable.go +++ b/xmlPivotTable.go @@ -195,8 +195,7 @@ type xlsxItem struct { } // xlsxAutoSortScope represents the sorting scope for the PivotTable. -type xlsxAutoSortScope struct { -} +type xlsxAutoSortScope struct{} // xlsxRowFields represents the collection of row fields for the PivotTable. type xlsxRowFields struct { @@ -225,8 +224,7 @@ type xlsxI struct { } // xlsxX represents an array of indexes to cached shared item values. -type xlsxX struct { -} +type xlsxX struct{} // xlsxColFields represents the collection of fields that are on the column // axis of the PivotTable. @@ -281,8 +279,7 @@ type xlsxDataField struct { // xlsxConditionalFormats represents the collection of conditional formats // applied to a PivotTable. -type xlsxConditionalFormats struct { -} +type xlsxConditionalFormats struct{} // xlsxPivotTableStyleInfo represent information on style applied to the // PivotTable.