浏览代码

Fix #539 Fixed error opening excel file created in encoding d… (#540)

* 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
Alex Geer 6 年之前
父节点
当前提交
b1b3c0d151
共有 15 个文件被更改,包括 342 次插入152 次删除
  1. 2 1
      .gitignore
  2. 14 4
      calcchain.go
  3. 13 3
      chart.go
  4. 17 6
      comment.go
  5. 41 25
      docProps.go
  6. 2 2
      docProps_test.go
  7. 57 24
      excelize.go
  8. 2 6
      file.go
  9. 2 0
      go.mod
  10. 8 0
      go.sum
  11. 40 22
      picture.go
  12. 10 2
      rows.go
  13. 53 25
      sheet.go
  14. 60 27
      sparkline.go
  15. 21 5
      styles.go

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 ~$*.xlsx
 test/Test*.xlsx
 *.out
-*.test
+*.test
+.idea

+ 14 - 4
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
 }
 

+ 13 - 3
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{

+ 17 - 6
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]

+ 41 - 25
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
 }

+ 2 - 2
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)
 }

+ 57 - 24
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

+ 2 - 6
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()

+ 2 - 0
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
 )

+ 8 - 0
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=

+ 40 - 22
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("<decodeTwoCellAnchor>"+anchor.Content+"</decodeTwoCellAnchor>"), &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("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>"))).
+			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

+ 10 - 2
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
 }
 

+ 53 - 25
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]
 }
 

+ 60 - 27
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("<extLst>"+ws.ExtLst.Ext+"</extLst>"), &decodeExtLst)
-		if err != nil {
-			return err
+		decodeExtLst = new(decodeWorksheetExt)
+		if err = f.xmlNewDecoder(bytes.NewReader([]byte("<extLst>" + ws.ExtLst.Ext + "</extLst>"))).
+			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, "<extLst>"), "</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

+ 21 - 5
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
 }