From b1b3c0d15158abc71267da5893de020f047c3872 Mon Sep 17 00:00:00 2001 From: Alex Geer Date: Thu, 19 Dec 2019 19:30:48 +0300 Subject: [PATCH] =?UTF-8?q?Fix=20#539=20Fixed=20error=20opening=20excel=20?= =?UTF-8?q?file=20created=20in=20encoding=20d=E2=80=A6=20(#540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed issue #539 Fixed error opening excel file created in encoding different from UTF-8, added logging of possible errors when decoding XML if the function does not provide exit with an error * Added test for CharsetReader * Fixed #discussion_r359397878 Discussion: https://github.com/360EntSecGroup-Skylar/excelize/pull/540#discussion_r359397878 * Fixed go fmt * go mod tidy and removed unused imports * The code has been refactored --- .gitignore | 3 +- calcchain.go | 18 +++++++--- chart.go | 16 +++++++-- comment.go | 23 +++++++++---- docProps.go | 66 ++++++++++++++++++++++-------------- docProps_test.go | 4 +-- excelize.go | 81 +++++++++++++++++++++++++++++++------------- file.go | 8 ++--- go.mod | 2 ++ go.sum | 8 +++++ picture.go | 62 ++++++++++++++++++++++------------ rows.go | 12 +++++-- sheet.go | 78 +++++++++++++++++++++++++++++-------------- sparkline.go | 87 +++++++++++++++++++++++++++++++++--------------- styles.go | 26 ++++++++++++--- 15 files changed, 342 insertions(+), 152 deletions(-) diff --git a/.gitignore b/.gitignore index bafda04266..a3fcff22aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ ~$*.xlsx test/Test*.xlsx *.out -*.test \ No newline at end of file +*.test +.idea diff --git a/calcchain.go b/calcchain.go index b4cadefe0b..413f470ec1 100644 --- a/calcchain.go +++ b/calcchain.go @@ -9,16 +9,26 @@ package excelize -import "encoding/xml" +import ( + "bytes" + "encoding/xml" + "io" + "log" +) // calcChainReader provides a function to get the pointer to the structure // after deserialization of xl/calcChain.xml. func (f *File) calcChainReader() *xlsxCalcChain { + var err error + if f.CalcChain == nil { - var c xlsxCalcChain - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")), &c) - f.CalcChain = &c + f.CalcChain = new(xlsxCalcChain) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")))). + Decode(f.CalcChain); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } + return f.CalcChain } diff --git a/chart.go b/chart.go index 7d40405a26..aaa7cd6844 100644 --- a/chart.go +++ b/chart.go @@ -10,9 +10,12 @@ package excelize import ( + "bytes" "encoding/json" "encoding/xml" "errors" + "io" + "log" "strconv" "strings" ) @@ -1735,14 +1738,21 @@ func (f *File) drawPlotAreaTxPr() *cTxPr { // deserialization, two different structures: decodeWsDr and encodeWsDr are // defined. func (f *File) drawingParser(path string) (*xlsxWsDr, int) { + var ( + err error + ok bool + ) + if f.Drawings[path] == nil { content := xlsxWsDr{} content.A = NameSpaceDrawingML content.Xdr = NameSpaceDrawingMLSpreadSheet - _, ok := f.XLSX[path] - if ok { // Append Model + if _, ok = f.XLSX[path]; ok { // Append Model decodeWsDr := decodeWsDr{} - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(path)), &decodeWsDr) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). + Decode(&decodeWsDr); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } content.R = decodeWsDr.R for _, v := range decodeWsDr.OneCellAnchor { content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ diff --git a/comment.go b/comment.go index 7f3b10dbac..99630c93f7 100644 --- a/comment.go +++ b/comment.go @@ -10,9 +10,12 @@ package excelize import ( + "bytes" "encoding/json" "encoding/xml" "fmt" + "io" + "log" "strconv" "strings" ) @@ -303,12 +306,16 @@ func (f *File) countComments() int { // decodeVMLDrawingReader provides a function to get the pointer to the // structure after deserialization of xl/drawings/vmlDrawing%d.xml. func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { + var err error + if f.DecodeVMLDrawing[path] == nil { c, ok := f.XLSX[path] if ok { - d := decodeVmlDrawing{} - _ = xml.Unmarshal(namespaceStrictToTransitional(c), &d) - f.DecodeVMLDrawing[path] = &d + f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c))). + Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } } return f.DecodeVMLDrawing[path] @@ -328,12 +335,16 @@ func (f *File) vmlDrawingWriter() { // commentsReader provides a function to get the pointer to the structure // after deserialization of xl/comments%d.xml. func (f *File) commentsReader(path string) *xlsxComments { + var err error + if f.Comments[path] == nil { content, ok := f.XLSX[path] if ok { - c := xlsxComments{} - _ = xml.Unmarshal(namespaceStrictToTransitional(content), &c) - f.Comments[path] = &c + f.Comments[path] = new(xlsxComments) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content))). + Decode(f.Comments[path]); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } } return f.Comments[path] diff --git a/docProps.go b/docProps.go index 166512f73c..884eb6317f 100644 --- a/docProps.go +++ b/docProps.go @@ -10,7 +10,10 @@ package excelize import ( + "bytes" "encoding/xml" + "fmt" + "io" "reflect" ) @@ -65,13 +68,23 @@ import ( // Version: "1.0.0", // }) // -func (f *File) SetDocProps(docProperties *DocProperties) error { - core := decodeCoreProperties{} - err := xml.Unmarshal(namespaceStrictToTransitional(f.readXML("docProps/core.xml")), &core) - if err != nil { - return err +func (f *File) SetDocProps(docProperties *DocProperties) (err error) { + var ( + core *decodeCoreProperties + newProps *xlsxCoreProperties + fields []string + output []byte + immutable, mutable reflect.Value + field, val string + ) + + core = new(decodeCoreProperties) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("docProps/core.xml")))). + Decode(core); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return } - newProps := xlsxCoreProperties{ + newProps, err = &xlsxCoreProperties{ Dc: NameSpaceDublinCore, Dcterms: NameSpaceDublinCoreTerms, Dcmitype: NameSpaceDublinCoreMetadataIntiative, @@ -88,18 +101,16 @@ func (f *File) SetDocProps(docProperties *DocProperties) error { ContentStatus: core.ContentStatus, 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 + fields = []string{ + "Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords", + "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version", } - newProps.Created.Text = core.Created.Text - newProps.Created.Type = core.Created.Type - newProps.Modified.Text = core.Modified.Text - newProps.Modified.Type = core.Modified.Type - - fields := []string{"Category", "ContentStatus", "Creator", "Description", "Identifier", "Keywords", "LastModifiedBy", "Revision", "Subject", "Title", "Language", "Version"} - immutable := reflect.ValueOf(*docProperties) - mutable := reflect.ValueOf(&newProps).Elem() - for _, field := range fields { - val := immutable.FieldByName(field).String() - if val != "" { + immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem() + for _, field = range fields { + if val = immutable.FieldByName(field).String(); val != "" { mutable.FieldByName(field).SetString(val) } } @@ -109,19 +120,22 @@ func (f *File) SetDocProps(docProperties *DocProperties) error { if docProperties.Modified != "" { newProps.Modified.Text = docProperties.Modified } - output, err := xml.Marshal(&newProps) + output, err = xml.Marshal(newProps) f.saveFileList("docProps/core.xml", output) - return err + + return } // GetDocProps provides a function to get document core properties. -func (f *File) GetDocProps() (*DocProperties, error) { - core := decodeCoreProperties{} - err := xml.Unmarshal(namespaceStrictToTransitional(f.readXML("docProps/core.xml")), &core) - if err != nil { - return nil, err +func (f *File) GetDocProps() (ret *DocProperties, err error) { + var core = new(decodeCoreProperties) + + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("docProps/core.xml")))). + Decode(core); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return } - return &DocProperties{ + ret, err = &DocProperties{ Category: core.Category, ContentStatus: core.ContentStatus, Created: core.Created.Text, @@ -137,4 +151,6 @@ func (f *File) GetDocProps() (*DocProperties, error) { Language: core.Language, Version: core.Version, }, nil + + return } diff --git a/docProps_test.go b/docProps_test.go index 671d998cc9..df3122b19a 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -39,7 +39,7 @@ func TestSetDocProps(t *testing.T) { })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx"))) f.XLSX["docProps/core.xml"] = nil - assert.EqualError(t, f.SetDocProps(&DocProperties{}), "EOF") + assert.NoError(t, f.SetDocProps(&DocProperties{})) } func TestGetDocProps(t *testing.T) { @@ -52,5 +52,5 @@ func TestGetDocProps(t *testing.T) { assert.Equal(t, props.Creator, "Microsoft Office User") f.XLSX["docProps/core.xml"] = nil _, err = f.GetDocProps() - assert.EqualError(t, err, "EOF") + assert.NoError(t, err) } diff --git a/excelize.go b/excelize.go index 4d46b94e9b..fe227b9524 100644 --- a/excelize.go +++ b/excelize.go @@ -22,6 +22,8 @@ import ( "path" "strconv" "strings" + + "golang.org/x/net/html/charset" ) // File define a populated XLSX file struct. @@ -43,8 +45,11 @@ type File struct { WorkBook *xlsxWorkbook Relationships map[string]*xlsxRelationships XLSX map[string][]byte + CharsetReader charsetTranscoderFn } +type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error) + // OpenFile take the name of an XLSX file and returns a populated XLSX file // struct for it. func OpenFile(filename string) (*File, error) { @@ -61,6 +66,21 @@ func OpenFile(filename string) (*File, error) { return f, nil } +// object builder +func newFile() *File { + return &File{ + checked: make(map[string]bool), + sheetMap: make(map[string]string), + Comments: make(map[string]*xlsxComments), + Drawings: make(map[string]*xlsxWsDr), + Sheet: make(map[string]*xlsxWorksheet), + DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), + VMLDrawing: make(map[string]*vmlDrawing), + Relationships: make(map[string]*xlsxRelationships), + CharsetReader: charset.NewReaderLabel, + } +} + // OpenReader take an io.Reader and return a populated XLSX file. func OpenReader(r io.Reader) (*File, error) { b, err := ioutil.ReadAll(r) @@ -88,17 +108,8 @@ func OpenReader(r io.Reader) (*File, error) { if err != nil { return nil, err } - f := &File{ - checked: make(map[string]bool), - Comments: make(map[string]*xlsxComments), - Drawings: make(map[string]*xlsxWsDr), - Sheet: make(map[string]*xlsxWorksheet), - SheetCount: sheetCount, - DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), - VMLDrawing: make(map[string]*vmlDrawing), - Relationships: make(map[string]*xlsxRelationships), - XLSX: file, - } + f := newFile() + f.SheetCount, f.XLSX = sheetCount, file f.CalcChain = f.calcChainReader() f.sheetMap = f.getSheetMap() f.Styles = f.stylesReader() @@ -106,6 +117,16 @@ func OpenReader(r io.Reader) (*File, error) { return f, nil } +// CharsetTranscoder Set user defined codepage transcoder function for open XLSX from non UTF-8 encoding +func (f *File) CharsetTranscoder(fn charsetTranscoderFn) *File { f.CharsetReader = fn; return f } + +// Creates new XML decoder with charset reader +func (f *File) xmlNewDecoder(rdr io.Reader) (ret *xml.Decoder) { + ret = xml.NewDecoder(rdr) + ret.CharsetReader = f.CharsetReader + return +} + // setDefaultTimeStyle provides a function to set default numbers format for // time.Time type cell value by given worksheet name, cell coordinates and // number format code. @@ -123,26 +144,38 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error { // workSheetReader provides a function to get the pointer to the structure // after deserialization by given worksheet name. -func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) { - name, ok := f.sheetMap[trimSheetName(sheet)] - if !ok { - return nil, fmt.Errorf("sheet %s is not exist", sheet) +func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) { + var ( + name string + ok bool + ) + + if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok { + err = fmt.Errorf("sheet %s is not exist", sheet) + return } - if f.Sheet[name] == nil { - var xlsx xlsxWorksheet - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx) + if xlsx = f.Sheet[name]; f.Sheet[name] == nil { + xlsx = new(xlsxWorksheet) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))). + Decode(xlsx); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + err = nil if f.checked == nil { f.checked = make(map[string]bool) } - ok := f.checked[name] - if !ok { - checkSheet(&xlsx) - checkRow(&xlsx) + if ok = f.checked[name]; !ok { + checkSheet(xlsx) + if err = checkRow(xlsx); err != nil { + return + } f.checked[name] = true } - f.Sheet[name] = &xlsx + f.Sheet[name] = xlsx } - return f.Sheet[name], nil + + return } // checkSheet provides a function to fill each row element and make that is diff --git a/file.go b/file.go index 2e0d27bef4..d8f10facfa 100644 --- a/file.go +++ b/file.go @@ -33,12 +33,8 @@ func NewFile() *File { file["xl/styles.xml"] = []byte(XMLHeader + templateStyles) file["xl/workbook.xml"] = []byte(XMLHeader + templateWorkbook) file["[Content_Types].xml"] = []byte(XMLHeader + templateContentTypes) - f := &File{ - sheetMap: make(map[string]string), - Sheet: make(map[string]*xlsxWorksheet), - SheetCount: 1, - XLSX: file, - } + f := newFile() + f.SheetCount, f.XLSX = 1, file f.CalcChain = f.calcChainReader() f.Comments = make(map[string]*xlsxComments) f.ContentTypes = f.contentTypesReader() diff --git a/go.mod b/go.mod index 892f3066da..420c64eb64 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,6 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/stretchr/testify v1.3.0 golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 + golang.org/x/text v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index 2d29d33a51..54492ac2ed 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/picture.go b/picture.go index ff40863369..09c19559de 100644 --- a/picture.go +++ b/picture.go @@ -14,7 +14,9 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" "image" + "io" "io/ioutil" "os" "path" @@ -471,39 +473,55 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte, error) { // getPicture provides a function to get picture base name and raw content // embed in XLSX by given coordinates and drawing relationships. -func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (string, []byte, error) { - wsDr, _ := f.drawingParser(drawingXML) - for _, anchor := range wsDr.TwoCellAnchor { +func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (ret string, buf []byte, err error) { + var ( + wsDr *xlsxWsDr + ok bool + anchor *xdrCellAnchor + deWsDr *decodeWsDr + xxRelationship *xlsxRelationship + deTwoCellAnchor *decodeTwoCellAnchor + ) + + wsDr, _ = f.drawingParser(drawingXML) + for _, anchor = range wsDr.TwoCellAnchor { if anchor.From != nil && anchor.Pic != nil { if anchor.From.Col == col && anchor.From.Row == row { - xlsxRelationship := f.getDrawingRelationships(drawingRelationships, + xxRelationship = f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed) - _, ok := supportImageTypes[filepath.Ext(xlsxRelationship.Target)] - if ok { - return filepath.Base(xlsxRelationship.Target), - []byte(f.XLSX[strings.Replace(xlsxRelationship.Target, - "..", "xl", -1)]), nil + if _, ok = supportImageTypes[filepath.Ext(xxRelationship.Target)]; ok { + ret, buf = filepath.Base(xxRelationship.Target), []byte(f.XLSX[strings.Replace(xxRelationship.Target, "..", "xl", -1)]) + return } } } } - - decodeWsDr := decodeWsDr{} - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(drawingXML)), &decodeWsDr) - for _, anchor := range decodeWsDr.TwoCellAnchor { - decodeTwoCellAnchor := decodeTwoCellAnchor{} - _ = xml.Unmarshal([]byte(""+anchor.Content+""), &decodeTwoCellAnchor) - if decodeTwoCellAnchor.From != nil && decodeTwoCellAnchor.Pic != nil { - if decodeTwoCellAnchor.From.Col == col && decodeTwoCellAnchor.From.Row == row { - xlsxRelationship := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed) - _, ok := supportImageTypes[filepath.Ext(xlsxRelationship.Target)] - if ok { - return filepath.Base(xlsxRelationship.Target), []byte(f.XLSX[strings.Replace(xlsxRelationship.Target, "..", "xl", -1)]), nil + deWsDr = new(decodeWsDr) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))). + Decode(deWsDr); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + err = nil + for _, anchor := range deWsDr.TwoCellAnchor { + deTwoCellAnchor = new(decodeTwoCellAnchor) + if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + anchor.Content + ""))). + Decode(deTwoCellAnchor); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil { + if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { + xxRelationship = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) + if _, ok = supportImageTypes[filepath.Ext(xxRelationship.Target)]; ok { + ret, buf = filepath.Base(xxRelationship.Target), []byte(f.XLSX[strings.Replace(xxRelationship.Target, "..", "xl", -1)]) + return } } } } - return "", nil, nil + + return } // getDrawingRelationships provides a function to get drawing relationships diff --git a/rows.go b/rows.go index 379644110c..e12e349a02 100644 --- a/rows.go +++ b/rows.go @@ -10,9 +10,11 @@ package excelize import ( - "encoding/xml" + "bytes" "errors" "fmt" + "io" + "log" "math" "strconv" ) @@ -187,15 +189,21 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) { // sharedStringsReader provides a function to get the pointer to the structure // after deserialization of xl/sharedStrings.xml. func (f *File) sharedStringsReader() *xlsxSST { + var err error + if f.SharedStrings == nil { var sharedStrings xlsxSST ss := f.readXML("xl/sharedStrings.xml") if len(ss) == 0 { ss = f.readXML("xl/SharedStrings.xml") } - _ = xml.Unmarshal(namespaceStrictToTransitional(ss), &sharedStrings) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))). + Decode(&sharedStrings); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } f.SharedStrings = &sharedStrings } + return f.SharedStrings } diff --git a/sheet.go b/sheet.go index 951baf9239..42fd6b3394 100644 --- a/sheet.go +++ b/sheet.go @@ -15,7 +15,9 @@ import ( "encoding/xml" "errors" "fmt" + "io" "io/ioutil" + "log" "os" "path" "reflect" @@ -61,11 +63,16 @@ func (f *File) NewSheet(name string) int { // contentTypesReader provides a function to get the pointer to the // [Content_Types].xml structure after deserialization. func (f *File) contentTypesReader() *xlsxTypes { + var err error + if f.ContentTypes == nil { - var content xlsxTypes - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")), &content) - f.ContentTypes = &content + f.ContentTypes = new(xlsxTypes) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")))). + Decode(f.ContentTypes); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } + return f.ContentTypes } @@ -81,11 +88,16 @@ func (f *File) contentTypesWriter() { // workbookReader provides a function to get the pointer to the xl/workbook.xml // structure after deserialization. func (f *File) workbookReader() *xlsxWorkbook { + var err error + if f.WorkBook == nil { - var content xlsxWorkbook - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")), &content) - f.WorkBook = &content + f.WorkBook = new(xlsxWorkbook) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")))). + Decode(f.WorkBook); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } + return f.WorkBook } @@ -679,42 +691,51 @@ func (f *File) GetSheetVisible(name string) bool { // // result, err := f.SearchSheet("Sheet1", "[0-9]", true) // -func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { +func (f *File) SearchSheet(sheet, value string, reg ...bool) (result []string, err error) { var ( - regSearch bool - result []string + xlsx *xlsxWorksheet + regSearch, r, ok bool + name string + output []byte ) - for _, r := range reg { + + for _, r = range reg { regSearch = r } - xlsx, err := f.workSheetReader(sheet) - if err != nil { - return result, err + if xlsx, err = f.workSheetReader(sheet); err != nil { + return } - name, ok := f.sheetMap[trimSheetName(sheet)] - if !ok { - return result, nil + if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok { + return } if xlsx != nil { - output, _ := xml.Marshal(f.Sheet[name]) + if output, err = xml.Marshal(f.Sheet[name]); err != nil { + return + } f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output)) } + return f.searchSheet(name, value, regSearch) } // searchSheet provides a function to get coordinates by given worksheet name, // cell value, and regular expression. -func (f *File) searchSheet(name, value string, regSearch bool) ([]string, error) { +func (f *File) searchSheet(name, value string, regSearch bool) (result []string, err error) { var ( + d *xlsxSST + decoder *xml.Decoder inElement string - result []string r xlsxRow + token xml.Token ) - d := f.sharedStringsReader() - decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name))) + + d = f.sharedStringsReader() + decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name))) for { - token, _ := decoder.Token() - if token == nil { + if token, err = decoder.Token(); err != nil || token == nil { + if err == io.EOF { + err = nil + } break } switch startElement := token.(type) { @@ -750,7 +771,8 @@ func (f *File) searchSheet(name, value string, regSearch bool) ([]string, error) default: } } - return result, nil + + return } // SetHeaderFooter provides a function to set headers and footers by given @@ -1360,14 +1382,20 @@ func (f *File) UngroupSheets() error { // relsReader provides a function to get the pointer to the structure // after deserialization of xl/worksheets/_rels/sheet%d.xml.rels. func (f *File) relsReader(path string) *xlsxRelationships { + var err error + if f.Relationships[path] == nil { _, ok := f.XLSX[path] if ok { c := xlsxRelationships{} - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(path)), &c) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). + Decode(&c); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } f.Relationships[path] = &c } } + return f.Relationships[path] } diff --git a/sparkline.go b/sparkline.go index b09dbf4051..9ad5087775 100644 --- a/sparkline.go +++ b/sparkline.go @@ -10,8 +10,10 @@ package excelize import ( + "bytes" "encoding/xml" "errors" + "io" "strings" ) @@ -386,23 +388,40 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup { // ColorAxis | An RGB Color is specified as RRGGBB // Axis | Show sparkline axis // -func (f *File) AddSparkline(sheet string, opt *SparklineOption) error { +func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) { + var ( + ws *xlsxWorksheet + sparkType string + sparkTypes map[string]string + specifiedSparkTypes string + ok bool + group *xlsxX14SparklineGroup + groups *xlsxX14SparklineGroups + decodeExtLst *decodeWorksheetExt + idx int + ext *xlsxWorksheetExt + decodeSparklineGroups *decodeX14SparklineGroups + sparklineGroupBytes []byte + sparklineGroupsBytes []byte + extLst string + extLstBytes, extBytes []byte + ) + // parameter validation - ws, err := f.parseFormatAddSparklineSet(sheet, opt) - if err != nil { - return err + if ws, err = f.parseFormatAddSparklineSet(sheet, opt); err != nil { + return } // Handle the sparkline type - sparkType := "line" - sparkTypes := map[string]string{"line": "line", "column": "column", "win_loss": "stacked"} + sparkType = "line" + sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"} if opt.Type != "" { - specifiedSparkTypes, ok := sparkTypes[opt.Type] - if !ok { - return errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'") + if specifiedSparkTypes, ok = sparkTypes[opt.Type]; !ok { + err = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'") + return } sparkType = specifiedSparkTypes } - group := f.addSparklineGroupByStyle(opt.Style) + group = f.addSparklineGroupByStyle(opt.Style) group.Type = sparkType group.ColorAxis = &xlsxColor{RGB: "FF000000"} group.DisplayEmptyCellsAs = "gap" @@ -423,43 +442,57 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) error { } f.addSparkline(opt, group) if ws.ExtLst.Ext != "" { // append mode ext - decodeExtLst := decodeWorksheetExt{} - err = xml.Unmarshal([]byte(""+ws.ExtLst.Ext+""), &decodeExtLst) - if err != nil { - return err + decodeExtLst = new(decodeWorksheetExt) + if err = f.xmlNewDecoder(bytes.NewReader([]byte("" + ws.ExtLst.Ext + ""))). + Decode(decodeExtLst); err != nil && err != io.EOF { + return } - for idx, ext := range decodeExtLst.Ext { + for idx, ext = range decodeExtLst.Ext { if ext.URI == ExtURISparklineGroups { - decodeSparklineGroups := decodeX14SparklineGroups{} - _ = xml.Unmarshal([]byte(ext.Content), &decodeSparklineGroups) - sparklineGroupBytes, _ := xml.Marshal(group) - groups := xlsxX14SparklineGroups{ + decodeSparklineGroups = new(decodeX14SparklineGroups) + if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))). + Decode(decodeSparklineGroups); err != nil && err != io.EOF { + return + } + if sparklineGroupBytes, err = xml.Marshal(group); err != nil { + return + } + groups = &xlsxX14SparklineGroups{ XMLNSXM: NameSpaceSpreadSheetExcel2006Main, Content: decodeSparklineGroups.Content + string(sparklineGroupBytes), } - sparklineGroupsBytes, _ := xml.Marshal(groups) + if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { + return + } decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes) } } - extLstBytes, _ := xml.Marshal(decodeExtLst) - extLst := string(extLstBytes) + if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil { + return + } + extLst = string(extLstBytes) ws.ExtLst = &xlsxExtLst{ Ext: strings.TrimSuffix(strings.TrimPrefix(extLst, ""), ""), } } else { - groups := xlsxX14SparklineGroups{ + groups = &xlsxX14SparklineGroups{ XMLNSXM: NameSpaceSpreadSheetExcel2006Main, SparklineGroups: []*xlsxX14SparklineGroup{group}, } - sparklineGroupsBytes, _ := xml.Marshal(groups) - extLst := xlsxWorksheetExt{ + if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil { + return + } + ext = &xlsxWorksheetExt{ URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes), } - extBytes, _ := xml.Marshal(extLst) + if extBytes, err = xml.Marshal(ext); err != nil { + return + } ws.ExtLst.Ext = string(extBytes) } - return nil + + return } // parseFormatAddSparklineSet provides a function to validate sparkline diff --git a/styles.go b/styles.go index 3244be240a..fa0507ebc9 100644 --- a/styles.go +++ b/styles.go @@ -10,9 +10,12 @@ package excelize import ( + "bytes" "encoding/json" "encoding/xml" "fmt" + "io" + "log" "math" "strconv" "strings" @@ -997,11 +1000,16 @@ func is12HourTime(format string) bool { // stylesReader provides a function to get the pointer to the structure after // deserialization of xl/styles.xml. func (f *File) stylesReader() *xlsxStyleSheet { + var err error + if f.Styles == nil { - var styleSheet xlsxStyleSheet - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/styles.xml")), &styleSheet) - f.Styles = &styleSheet + f.Styles = new(xlsxStyleSheet) + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/styles.xml")))). + Decode(f.Styles); err != nil && err != io.EOF { + log.Printf("xml decode error: %s", err) + } } + return f.Styles } @@ -2803,8 +2811,16 @@ func getPaletteColor(color string) string { // themeReader provides a function to get the pointer to the xl/theme/theme1.xml // structure after deserialization. func (f *File) themeReader() *xlsxTheme { - var theme xlsxTheme - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")), &theme) + var ( + err error + theme xlsxTheme + ) + + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))). + Decode(&theme); err != nil && err != io.EOF { + log.Printf("xml decoder error: %s", err) + } + return &theme }