Improve security and simplify code
- Make variable name more semantic - Reduce cyclomatic complexities for the formula calculate function - Support specified unzip size limit on open file options, avoid zip bombs vulnerability attack - Typo fix for documentation and error message
This commit is contained in:
parent
f6f14f507e
commit
48c16de8bf
59
calc.go
59
calc.go
|
@ -6026,6 +6026,39 @@ func (fn *formulaFuncs) DATE(argsList *list.List) formulaArg {
|
||||||
return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
|
return newStringFormulaArg(timeFromExcelTime(daysBetween(excelMinTime1900.Unix(), d)+1, false).String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calcDateDif is an implementation of the formula function DATEDIF,
|
||||||
|
// calculation difference between two dates.
|
||||||
|
func calcDateDif(unit string, diff float64, seq []int, startArg, endArg formulaArg) float64 {
|
||||||
|
ey, sy, em, sm, ed, sd := seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]
|
||||||
|
switch unit {
|
||||||
|
case "d":
|
||||||
|
diff = endArg.Number - startArg.Number
|
||||||
|
case "md":
|
||||||
|
smMD := em
|
||||||
|
if ed < sd {
|
||||||
|
smMD--
|
||||||
|
}
|
||||||
|
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
|
||||||
|
case "ym":
|
||||||
|
diff = float64(em - sm)
|
||||||
|
if ed < sd {
|
||||||
|
diff--
|
||||||
|
}
|
||||||
|
if diff < 0 {
|
||||||
|
diff += 12
|
||||||
|
}
|
||||||
|
case "yd":
|
||||||
|
syYD := sy
|
||||||
|
if em < sm || (em == sm && ed < sd) {
|
||||||
|
syYD++
|
||||||
|
}
|
||||||
|
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
|
||||||
|
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
|
||||||
|
diff = s - e
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
// DATEDIF function calculates the number of days, months, or years between
|
// DATEDIF function calculates the number of days, months, or years between
|
||||||
// two dates. The syntax of the function is:
|
// two dates. The syntax of the function is:
|
||||||
//
|
//
|
||||||
|
@ -6051,8 +6084,6 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
|
||||||
ey, emm, ed := endDate.Date()
|
ey, emm, ed := endDate.Date()
|
||||||
sm, em, diff := int(smm), int(emm), 0.0
|
sm, em, diff := int(smm), int(emm), 0.0
|
||||||
switch unit {
|
switch unit {
|
||||||
case "d":
|
|
||||||
return newNumberFormulaArg(endArg.Number - startArg.Number)
|
|
||||||
case "y":
|
case "y":
|
||||||
diff = float64(ey - sy)
|
diff = float64(ey - sy)
|
||||||
if em < sm || (em == sm && ed < sd) {
|
if em < sm || (em == sm && ed < sd) {
|
||||||
|
@ -6069,28 +6100,8 @@ func (fn *formulaFuncs) DATEDIF(argsList *list.List) formulaArg {
|
||||||
mdiff += 12
|
mdiff += 12
|
||||||
}
|
}
|
||||||
diff = float64(ydiff*12 + mdiff)
|
diff = float64(ydiff*12 + mdiff)
|
||||||
case "md":
|
case "d", "md", "ym", "yd":
|
||||||
smMD := em
|
diff = calcDateDif(unit, diff, []int{ey, sy, em, sm, ed, sd}, startArg, endArg)
|
||||||
if ed < sd {
|
|
||||||
smMD--
|
|
||||||
}
|
|
||||||
diff = endArg.Number - daysBetween(excelMinTime1900.Unix(), makeDate(ey, time.Month(smMD), sd)) - 1
|
|
||||||
case "ym":
|
|
||||||
diff = float64(em - sm)
|
|
||||||
if ed < sd {
|
|
||||||
diff--
|
|
||||||
}
|
|
||||||
if diff < 0 {
|
|
||||||
diff += 12
|
|
||||||
}
|
|
||||||
case "yd":
|
|
||||||
syYD := sy
|
|
||||||
if em < sm || (em == sm && ed < sd) {
|
|
||||||
syYD++
|
|
||||||
}
|
|
||||||
s := daysBetween(excelMinTime1900.Unix(), makeDate(syYD, time.Month(em), ed))
|
|
||||||
e := daysBetween(excelMinTime1900.Unix(), makeDate(sy, time.Month(sm), sd))
|
|
||||||
diff = s - e
|
|
||||||
default:
|
default:
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
|
return newErrorFormulaArg(formulaErrorVALUE, "DATEDIF has invalid unit")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
//
|
//
|
||||||
// Package excelize providing a set of functions that allow you to write to
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
// and read from XLSX files. Support reads and writes XLSX file generated by
|
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
|
||||||
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
|
||||||
// charts of XLSX. This library needs Go version 1.15 or later.
|
// 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.
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
|
|
10
crypt.go
10
crypt.go
|
@ -3,9 +3,11 @@
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
//
|
//
|
||||||
// Package excelize providing a set of functions that allow you to write to
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
// and read from XLSX files. Support reads and writes XLSX file generated by
|
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
|
||||||
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
|
||||||
// charts of XLSX. This library needs Go version 1.15 or later.
|
// 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.
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
|
@ -15,6 +17,7 @@ import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
@ -22,7 +25,6 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"hash"
|
"hash"
|
||||||
"math/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
//
|
//
|
||||||
// Package excelize providing a set of functions that allow you to write to
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
// and read from XLSX files. Support reads and writes XLSX file generated by
|
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
|
||||||
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
|
||||||
// charts of XLSX. This library needs Go version 1.15 or later.
|
// 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.
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
//
|
//
|
||||||
// Package excelize providing a set of functions that allow you to write to
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
// and read from XLSX files. Support reads and writes XLSX file generated by
|
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
|
||||||
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
|
||||||
// charts of XLSX. This library needs Go version 1.15 or later.
|
// 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.
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
|
|
|
@ -85,5 +85,5 @@ func TestExcelDateToTime(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Check error case
|
// Check error case
|
||||||
_, err := ExcelDateToTime(-1, false)
|
_, err := ExcelDateToTime(-1, false)
|
||||||
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported supported")
|
assert.EqualError(t, err, "invalid date value -1.000000, negative values are not supported")
|
||||||
}
|
}
|
||||||
|
|
12
errors.go
12
errors.go
|
@ -16,26 +16,36 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// newInvalidColumnNameError defined the error message on receiving the invalid column name.
|
||||||
func newInvalidColumnNameError(col string) error {
|
func newInvalidColumnNameError(col string) error {
|
||||||
return fmt.Errorf("invalid column name %q", col)
|
return fmt.Errorf("invalid column name %q", col)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newInvalidRowNumberError defined the error message on receiving the invalid row number.
|
||||||
func newInvalidRowNumberError(row int) error {
|
func newInvalidRowNumberError(row int) error {
|
||||||
return fmt.Errorf("invalid row number %d", row)
|
return fmt.Errorf("invalid row number %d", row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newInvalidCellNameError defined the error message on receiving the invalid cell name.
|
||||||
func newInvalidCellNameError(cell string) error {
|
func newInvalidCellNameError(cell string) error {
|
||||||
return fmt.Errorf("invalid cell name %q", cell)
|
return fmt.Errorf("invalid cell name %q", cell)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newInvalidExcelDateError defined the error message on receiving the data with negative values.
|
||||||
func newInvalidExcelDateError(dateValue float64) error {
|
func newInvalidExcelDateError(dateValue float64) error {
|
||||||
return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue)
|
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newUnsupportChartType defined the error message on receiving the chart type are unsupported.
|
||||||
func newUnsupportChartType(chartType string) error {
|
func newUnsupportChartType(chartType string) error {
|
||||||
return fmt.Errorf("unsupported chart type %s", chartType)
|
return fmt.Errorf("unsupported chart type %s", chartType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newUnzipSizeLimitError defined the error message on unzip size exceeds the limit.
|
||||||
|
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
|
||||||
|
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrStreamSetColWidth defined the error message on set column width in
|
// ErrStreamSetColWidth defined the error message on set column width in
|
||||||
// stream writing mode.
|
// stream writing mode.
|
||||||
|
|
|
@ -21,5 +21,5 @@ func TestNewInvalidCellNameError(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewInvalidExcelDateError(t *testing.T) {
|
func TestNewInvalidExcelDateError(t *testing.T) {
|
||||||
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported supported")
|
assert.EqualError(t, newInvalidExcelDateError(-1), "invalid date value -1.000000, negative values are not supported")
|
||||||
}
|
}
|
||||||
|
|
42
excelize.go
42
excelize.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -60,20 +61,26 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
|
||||||
// Options define the options for open spreadsheet.
|
// Options define the options for open spreadsheet.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Password string
|
Password string
|
||||||
|
UnzipSizeLimit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFile take the name of an spreadsheet file and returns a populated spreadsheet file struct
|
// OpenFile take the name of an spreadsheet file and returns a populated
|
||||||
// for it. For example, open spreadsheet with password protection:
|
// spreadsheet file struct for it. For example, open spreadsheet with
|
||||||
|
// password protection:
|
||||||
//
|
//
|
||||||
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
|
// f, err := excelize.OpenFile("Book1.xlsx", excelize.Options{Password: "password"})
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Note that the excelize just support decrypt and not support encrypt currently, the spreadsheet
|
// Note that the excelize just support decrypt and not support encrypt
|
||||||
// saved by Save and SaveAs will be without password unprotected.
|
// currently, the spreadsheet saved by Save and SaveAs will be without
|
||||||
|
// password unprotected.
|
||||||
|
//
|
||||||
|
// UnzipSizeLimit specified the unzip size limit in bytes on open the
|
||||||
|
// spreadsheet, the default size limit is 16GB.
|
||||||
func OpenFile(filename string, opt ...Options) (*File, error) {
|
func OpenFile(filename string, opt ...Options) (*File, error) {
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filepath.Clean(filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -89,6 +96,7 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
|
||||||
// newFile is object builder
|
// newFile is object builder
|
||||||
func newFile() *File {
|
func newFile() *File {
|
||||||
return &File{
|
return &File{
|
||||||
|
options: &Options{UnzipSizeLimit: UnzipSizeLimit},
|
||||||
xmlAttr: make(map[string][]xml.Attr),
|
xmlAttr: make(map[string][]xml.Attr),
|
||||||
checked: make(map[string]bool),
|
checked: make(map[string]bool),
|
||||||
sheetMap: make(map[string]string),
|
sheetMap: make(map[string]string),
|
||||||
|
@ -111,10 +119,13 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f := newFile()
|
f := newFile()
|
||||||
if bytes.Contains(b, oleIdentifier) && len(opt) > 0 {
|
for i := range opt {
|
||||||
for _, o := range opt {
|
f.options = &opt[i]
|
||||||
f.options = &o
|
if f.options.UnzipSizeLimit == 0 {
|
||||||
|
f.options.UnzipSizeLimit = UnzipSizeLimit
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if bytes.Contains(b, oleIdentifier) {
|
||||||
b, err = Decrypt(b, f.options)
|
b, err = Decrypt(b, f.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decrypted file failed")
|
return nil, fmt.Errorf("decrypted file failed")
|
||||||
|
@ -124,8 +135,7 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
file, sheetCount, err := ReadZipReader(zr, f.options)
|
||||||
file, sheetCount, err := ReadZipReader(zr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -316,18 +326,18 @@ func (f *File) UpdateLinkedValue() error {
|
||||||
// recalculate formulas
|
// recalculate formulas
|
||||||
wb.CalcPr = nil
|
wb.CalcPr = nil
|
||||||
for _, name := range f.GetSheetList() {
|
for _, name := range f.GetSheetList() {
|
||||||
xlsx, err := f.workSheetReader(name)
|
ws, err := f.workSheetReader(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {
|
if err.Error() == fmt.Sprintf("sheet %s is chart sheet", trimSheetName(name)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for indexR := range xlsx.SheetData.Row {
|
for indexR := range ws.SheetData.Row {
|
||||||
for indexC, col := range xlsx.SheetData.Row[indexR].C {
|
for indexC, col := range ws.SheetData.Row[indexR].C {
|
||||||
if col.F != nil && col.V != "" {
|
if col.F != nil && col.V != "" {
|
||||||
xlsx.SheetData.Row[indexR].C[indexC].V = ""
|
ws.SheetData.Row[indexR].C[indexC].V = ""
|
||||||
xlsx.SheetData.Row[indexR].C[indexC].T = ""
|
ws.SheetData.Row[indexR].C[indexC].T = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +391,7 @@ func (f *File) AddVBAProject(bin string) error {
|
||||||
Type: SourceRelationshipVBAProject,
|
Type: SourceRelationshipVBAProject,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
file, _ := ioutil.ReadFile(bin)
|
file, _ := ioutil.ReadFile(filepath.Clean(bin))
|
||||||
f.Pkg.Store("xl/vbaProject.bin", file)
|
f.Pkg.Store("xl/vbaProject.bin", file)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,13 +184,9 @@ func TestSaveFile(t *testing.T) {
|
||||||
|
|
||||||
func TestSaveAsWrongPath(t *testing.T) {
|
func TestSaveAsWrongPath(t *testing.T) {
|
||||||
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
|
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
|
||||||
if assert.NoError(t, err) {
|
assert.NoError(t, err)
|
||||||
// Test write file to not exist directory.
|
// Test write file to not exist directory.
|
||||||
err = f.SaveAs("")
|
assert.EqualError(t, f.SaveAs(""), "open .: is a directory")
|
||||||
if assert.Error(t, err) {
|
|
||||||
assert.True(t, os.IsNotExist(err), "Error: %v: Expected os.IsNotExists(err) == true", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCharsetTranscoder(t *testing.T) {
|
func TestCharsetTranscoder(t *testing.T) {
|
||||||
|
@ -204,6 +200,10 @@ func TestOpenReader(t *testing.T) {
|
||||||
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"})
|
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password"})
|
||||||
assert.EqualError(t, err, "decrypted file failed")
|
assert.EqualError(t, err, "decrypted file failed")
|
||||||
|
|
||||||
|
// Test open spreadsheet with unzip size limit.
|
||||||
|
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
|
||||||
|
assert.EqualError(t, err, newUnzipSizeLimitError(100).Error())
|
||||||
|
|
||||||
// Test open password protected spreadsheet created by Microsoft Office Excel 2010.
|
// Test open password protected spreadsheet created by Microsoft Office Excel 2010.
|
||||||
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
|
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -1226,6 +1226,7 @@ func TestWorkSheetReader(t *testing.T) {
|
||||||
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
|
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
|
||||||
_, err := f.workSheetReader("Sheet1")
|
_, err := f.workSheetReader("Sheet1")
|
||||||
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
|
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
|
||||||
|
assert.EqualError(t, f.UpdateLinkedValue(), "xml decode error: XML syntax error on line 1: invalid UTF-8")
|
||||||
|
|
||||||
// Test on no checked worksheet.
|
// Test on no checked worksheet.
|
||||||
f = NewFile()
|
f = NewFile()
|
||||||
|
|
16
file.go
16
file.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,14 +70,14 @@ func (f *File) SaveAs(name string, opt ...Options) error {
|
||||||
return ErrMaxFileNameLength
|
return ErrMaxFileNameLength
|
||||||
}
|
}
|
||||||
f.Path = name
|
f.Path = name
|
||||||
file, err := os.OpenFile(name, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
|
file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
f.options = nil
|
f.options = nil
|
||||||
for _, o := range opt {
|
for i := range opt {
|
||||||
f.options = &o
|
f.options = &opt[i]
|
||||||
}
|
}
|
||||||
return f.Write(file)
|
return f.Write(file)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,8 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteToBuffer provides a function to get bytes.Buffer from the saved file. And it allocate space in memory. Be careful when the file size is large.
|
// WriteToBuffer provides a function to get bytes.Buffer from the saved file,
|
||||||
|
// and it allocates space in memory. Be careful when the file size is large.
|
||||||
func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
|
func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
zw := zip.NewWriter(buf)
|
zw := zip.NewWriter(buf)
|
||||||
|
@ -130,7 +132,7 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
|
||||||
func (f *File) writeDirectToWriter(w io.Writer) error {
|
func (f *File) writeDirectToWriter(w io.Writer) error {
|
||||||
zw := zip.NewWriter(w)
|
zw := zip.NewWriter(w)
|
||||||
if err := f.writeToZip(zw); err != nil {
|
if err := f.writeToZip(zw); err != nil {
|
||||||
zw.Close()
|
_ = zw.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return zw.Close()
|
return zw.Close()
|
||||||
|
@ -157,14 +159,14 @@ func (f *File) writeToZip(zw *zip.Writer) error {
|
||||||
var from io.Reader
|
var from io.Reader
|
||||||
from, err = stream.rawData.Reader()
|
from, err = stream.rawData.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stream.rawData.Close()
|
_ = stream.rawData.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(fi, from)
|
_, err = io.Copy(fi, from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stream.rawData.Close()
|
_ = stream.rawData.Close()
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
f.Pkg.Range(func(path, content interface{}) bool {
|
f.Pkg.Range(func(path, content interface{}) bool {
|
||||||
|
|
12
file_test.go
12
file_test.go
|
@ -37,9 +37,7 @@ func BenchmarkWrite(b *testing.B) {
|
||||||
func TestWriteTo(t *testing.T) {
|
func TestWriteTo(t *testing.T) {
|
||||||
// Test WriteToBuffer err
|
// Test WriteToBuffer err
|
||||||
{
|
{
|
||||||
f := File{}
|
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f.Pkg = sync.Map{}
|
|
||||||
f.Pkg.Store("/d/", []byte("s"))
|
f.Pkg.Store("/d/", []byte("s"))
|
||||||
_, err := f.WriteTo(bufio.NewWriter(&buf))
|
_, err := f.WriteTo(bufio.NewWriter(&buf))
|
||||||
assert.EqualError(t, err, "zip: write to directory")
|
assert.EqualError(t, err, "zip: write to directory")
|
||||||
|
@ -47,9 +45,7 @@ func TestWriteTo(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Test file path overflow
|
// Test file path overflow
|
||||||
{
|
{
|
||||||
f := File{}
|
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f.Pkg = sync.Map{}
|
|
||||||
const maxUint16 = 1<<16 - 1
|
const maxUint16 = 1<<16 - 1
|
||||||
f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
|
f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
|
||||||
_, err := f.WriteTo(bufio.NewWriter(&buf))
|
_, err := f.WriteTo(bufio.NewWriter(&buf))
|
||||||
|
@ -57,9 +53,7 @@ func TestWriteTo(t *testing.T) {
|
||||||
}
|
}
|
||||||
// Test StreamsWriter err
|
// Test StreamsWriter err
|
||||||
{
|
{
|
||||||
f := File{}
|
f, buf := File{Pkg: sync.Map{}}, bytes.Buffer{}
|
||||||
buf := bytes.Buffer{}
|
|
||||||
f.Pkg = sync.Map{}
|
|
||||||
f.Pkg.Store("s", nil)
|
f.Pkg.Store("s", nil)
|
||||||
f.streams = make(map[string]*StreamWriter)
|
f.streams = make(map[string]*StreamWriter)
|
||||||
file, _ := os.Open("123")
|
file, _ := os.Open("123")
|
||||||
|
|
22
lib.go
22
lib.go
|
@ -26,15 +26,22 @@ import (
|
||||||
|
|
||||||
// ReadZipReader can be used to read the spreadsheet in memory without touching the
|
// ReadZipReader can be used to read the spreadsheet in memory without touching the
|
||||||
// filesystem.
|
// filesystem.
|
||||||
func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
|
func ReadZipReader(r *zip.Reader, o *Options) (map[string][]byte, int, error) {
|
||||||
var err error
|
var (
|
||||||
var docPart = map[string]string{
|
err error
|
||||||
|
docPart = map[string]string{
|
||||||
"[content_types].xml": "[Content_Types].xml",
|
"[content_types].xml": "[Content_Types].xml",
|
||||||
"xl/sharedstrings.xml": "xl/sharedStrings.xml",
|
"xl/sharedstrings.xml": "xl/sharedStrings.xml",
|
||||||
}
|
}
|
||||||
fileList := make(map[string][]byte, len(r.File))
|
fileList = make(map[string][]byte, len(r.File))
|
||||||
worksheets := 0
|
worksheets int
|
||||||
|
unzipSize int64
|
||||||
|
)
|
||||||
for _, v := range r.File {
|
for _, v := range r.File {
|
||||||
|
unzipSize += v.FileInfo().Size()
|
||||||
|
if unzipSize > o.UnzipSizeLimit {
|
||||||
|
return fileList, worksheets, newUnzipSizeLimitError(o.UnzipSizeLimit)
|
||||||
|
}
|
||||||
fileName := strings.Replace(v.Name, "\\", "/", -1)
|
fileName := strings.Replace(v.Name, "\\", "/", -1)
|
||||||
if partName, ok := docPart[strings.ToLower(fileName)]; ok {
|
if partName, ok := docPart[strings.ToLower(fileName)]; ok {
|
||||||
fileName = partName
|
fileName = partName
|
||||||
|
@ -61,7 +68,7 @@ func (f *File) readXML(name string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveFileList provides a function to update given file content in file list
|
// saveFileList provides a function to update given file content in file list
|
||||||
// of XLSX.
|
// of spreadsheet.
|
||||||
func (f *File) saveFileList(name string, content []byte) {
|
func (f *File) saveFileList(name string, content []byte) {
|
||||||
f.Pkg.Store(name, append([]byte(XMLHeader), content...))
|
f.Pkg.Store(name, append([]byte(XMLHeader), content...))
|
||||||
}
|
}
|
||||||
|
@ -75,8 +82,7 @@ func readFile(file *zip.File) ([]byte, error) {
|
||||||
dat := make([]byte, 0, file.FileInfo().Size())
|
dat := make([]byte, 0, file.FileInfo().Size())
|
||||||
buff := bytes.NewBuffer(dat)
|
buff := bytes.NewBuffer(dat)
|
||||||
_, _ = io.Copy(buff, rc)
|
_, _ = io.Copy(buff, rc)
|
||||||
rc.Close()
|
return buff.Bytes(), rc.Close()
|
||||||
return buff.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitCellName splits cell name to column name and row number.
|
// SplitCellName splits cell name to column name and row number.
|
||||||
|
|
|
@ -148,16 +148,16 @@ func TestUnmergeCell(t *testing.T) {
|
||||||
}
|
}
|
||||||
sheet1 := f.GetSheetName(0)
|
sheet1 := f.GetSheetName(0)
|
||||||
|
|
||||||
xlsx, err := f.workSheetReader(sheet1)
|
sheet, err := f.workSheetReader(sheet1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
mergeCellNum := len(xlsx.MergeCells.Cells)
|
mergeCellNum := len(sheet.MergeCells.Cells)
|
||||||
|
|
||||||
assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
assert.EqualError(t, f.UnmergeCell("Sheet1", "A", "A"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
|
||||||
|
|
||||||
// unmerge the mergecell that contains A1
|
// unmerge the mergecell that contains A1
|
||||||
assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
|
assert.NoError(t, f.UnmergeCell(sheet1, "A1", "A1"))
|
||||||
if len(xlsx.MergeCells.Cells) != mergeCellNum-1 {
|
if len(sheet.MergeCells.Cells) != mergeCellNum-1 {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
picture.go
14
picture.go
|
@ -94,7 +94,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrImgExt
|
return ErrImgExt
|
||||||
}
|
}
|
||||||
file, _ := ioutil.ReadFile(picture)
|
file, _ := ioutil.ReadFile(filepath.Clean(picture))
|
||||||
_, name := filepath.Split(picture)
|
_, name := filepath.Split(picture)
|
||||||
return f.AddPictureFromBytes(sheet, cell, format, name, ext, file)
|
return f.AddPictureFromBytes(sheet, cell, format, name, ext, file)
|
||||||
}
|
}
|
||||||
|
@ -199,8 +199,8 @@ func (f *File) deleteSheetRelationships(sheet, rID string) {
|
||||||
// addSheetLegacyDrawing provides a function to add legacy drawing element to
|
// addSheetLegacyDrawing provides a function to add legacy drawing element to
|
||||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||||
func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
|
func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
|
||||||
xlsx, _ := f.workSheetReader(sheet)
|
ws, _ := f.workSheetReader(sheet)
|
||||||
xlsx.LegacyDrawing = &xlsxLegacyDrawing{
|
ws.LegacyDrawing = &xlsxLegacyDrawing{
|
||||||
RID: "rId" + strconv.Itoa(rID),
|
RID: "rId" + strconv.Itoa(rID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,8 +208,8 @@ func (f *File) addSheetLegacyDrawing(sheet string, rID int) {
|
||||||
// addSheetDrawing provides a function to add drawing element to
|
// addSheetDrawing provides a function to add drawing element to
|
||||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||||
func (f *File) addSheetDrawing(sheet string, rID int) {
|
func (f *File) addSheetDrawing(sheet string, rID int) {
|
||||||
xlsx, _ := f.workSheetReader(sheet)
|
ws, _ := f.workSheetReader(sheet)
|
||||||
xlsx.Drawing = &xlsxDrawing{
|
ws.Drawing = &xlsxDrawing{
|
||||||
RID: "rId" + strconv.Itoa(rID),
|
RID: "rId" + strconv.Itoa(rID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,8 +217,8 @@ func (f *File) addSheetDrawing(sheet string, rID int) {
|
||||||
// addSheetPicture provides a function to add picture element to
|
// addSheetPicture provides a function to add picture element to
|
||||||
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
|
||||||
func (f *File) addSheetPicture(sheet string, rID int) {
|
func (f *File) addSheetPicture(sheet string, rID int) {
|
||||||
xlsx, _ := f.workSheetReader(sheet)
|
ws, _ := f.workSheetReader(sheet)
|
||||||
xlsx.Picture = &xlsxPicture{
|
ws.Picture = &xlsxPicture{
|
||||||
RID: "rId" + strconv.Itoa(rID),
|
RID: "rId" + strconv.Itoa(rID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,24 +71,24 @@ func TestAddPicture(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddPictureErrors(t *testing.T) {
|
func TestAddPictureErrors(t *testing.T) {
|
||||||
xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
|
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Test add picture to worksheet with invalid file path.
|
// Test add picture to worksheet with invalid file path.
|
||||||
err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "")
|
err = f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "")
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.True(t, os.IsNotExist(err), "Expected os.IsNotExist(err) == true")
|
assert.True(t, os.IsNotExist(err), "Expected os.IsNotExist(err) == true")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test add picture to worksheet with unsupported file type.
|
// Test add picture to worksheet with unsupported file type.
|
||||||
err = xlsx.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), "")
|
err = f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), "")
|
||||||
assert.EqualError(t, err, ErrImgExt.Error())
|
assert.EqualError(t, err, ErrImgExt.Error())
|
||||||
|
|
||||||
err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1))
|
err = f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1))
|
||||||
assert.EqualError(t, err, ErrImgExt.Error())
|
assert.EqualError(t, err, ErrImgExt.Error())
|
||||||
|
|
||||||
// Test add picture to worksheet with invalid file data.
|
// Test add picture to worksheet with invalid file data.
|
||||||
err = xlsx.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1))
|
err = f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1))
|
||||||
assert.EqualError(t, err, "image: unknown format")
|
assert.EqualError(t, err, "image: unknown format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
sheet.go
8
sheet.go
|
@ -480,7 +480,7 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrImgExt
|
return ErrImgExt
|
||||||
}
|
}
|
||||||
file, _ := ioutil.ReadFile(picture)
|
file, _ := ioutil.ReadFile(filepath.Clean(picture))
|
||||||
name := f.addMedia(file, ext)
|
name := f.addMedia(file, ext)
|
||||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
|
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
|
||||||
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
|
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
|
||||||
|
@ -655,13 +655,13 @@ func (f *File) SetSheetVisible(name string, visible bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range content.Sheets.Sheet {
|
for k, v := range content.Sheets.Sheet {
|
||||||
xlsx, err := f.workSheetReader(v.Name)
|
ws, err := f.workSheetReader(v.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tabSelected := false
|
tabSelected := false
|
||||||
if len(xlsx.SheetViews.SheetView) > 0 {
|
if len(ws.SheetViews.SheetView) > 0 {
|
||||||
tabSelected = xlsx.SheetViews.SheetView[0].TabSelected
|
tabSelected = ws.SheetViews.SheetView[0].TabSelected
|
||||||
}
|
}
|
||||||
if v.Name == name && count > 1 && !tabSelected {
|
if v.Name == name && count > 1 && !tabSelected {
|
||||||
content.Sheets.Sheet[k].State = "hidden"
|
content.Sheets.Sheet[k].State = "hidden"
|
||||||
|
|
10
sheetpr.go
10
sheetpr.go
|
@ -182,14 +182,14 @@ func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) {
|
||||||
// AutoPageBreaks(bool)
|
// AutoPageBreaks(bool)
|
||||||
// OutlineSummaryBelow(bool)
|
// OutlineSummaryBelow(bool)
|
||||||
func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
|
func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
|
||||||
sheet, err := f.workSheetReader(name)
|
ws, err := f.workSheetReader(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pr := sheet.SheetPr
|
pr := ws.SheetPr
|
||||||
if pr == nil {
|
if pr == nil {
|
||||||
pr = new(xlsxSheetPr)
|
pr = new(xlsxSheetPr)
|
||||||
sheet.SheetPr = pr
|
ws.SheetPr = pr
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
@ -208,11 +208,11 @@ func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
|
||||||
// AutoPageBreaks(bool)
|
// AutoPageBreaks(bool)
|
||||||
// OutlineSummaryBelow(bool)
|
// OutlineSummaryBelow(bool)
|
||||||
func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
|
func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
|
||||||
sheet, err := f.workSheetReader(name)
|
ws, err := f.workSheetReader(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pr := sheet.SheetPr
|
pr := ws.SheetPr
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt.getSheetPrOption(pr)
|
opt.getSheetPrOption(pr)
|
||||||
|
|
|
@ -346,8 +346,8 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpt
|
||||||
// marshalRowAttrs prepare attributes of the row by given options.
|
// marshalRowAttrs prepare attributes of the row by given options.
|
||||||
func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) {
|
func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) {
|
||||||
var opt *RowOpts
|
var opt *RowOpts
|
||||||
for _, o := range opts {
|
for i := range opts {
|
||||||
opt = &o
|
opt = &opts[i]
|
||||||
}
|
}
|
||||||
if opt == nil {
|
if opt == nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
// struct code generated by github.com/xuri/xgen
|
// struct code generated by github.com/xuri/xgen
|
||||||
//
|
//
|
||||||
// Package excelize providing a set of functions that allow you to write to
|
// Package excelize providing a set of functions that allow you to write to
|
||||||
// and read from XLSX files. Support reads and writes XLSX file generated by
|
// and read from XLSX / XLSM / XLTM files. Supports reading and writing
|
||||||
// Microsoft Excel™ 2007 and later. Support save file without losing original
|
// spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
|
||||||
// charts of XLSX. This library needs Go version 1.15 or later.
|
// 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.
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ const (
|
||||||
|
|
||||||
// Excel specifications and limits
|
// Excel specifications and limits
|
||||||
const (
|
const (
|
||||||
|
UnzipSizeLimit = 1000 << 24
|
||||||
StreamChunkSize = 1 << 24
|
StreamChunkSize = 1 << 24
|
||||||
MaxFontFamilyLength = 31
|
MaxFontFamilyLength = 31
|
||||||
MaxFontSize = 409
|
MaxFontSize = 409
|
||||||
|
|
Loading…
Reference in New Issue