forked from p30928647/excelize
* 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
This commit is contained in:
parent
a00ba75f0f
commit
b1b3c0d151
|
@ -2,3 +2,4 @@
|
||||||
test/Test*.xlsx
|
test/Test*.xlsx
|
||||||
*.out
|
*.out
|
||||||
*.test
|
*.test
|
||||||
|
.idea
|
||||||
|
|
18
calcchain.go
18
calcchain.go
|
@ -9,16 +9,26 @@
|
||||||
|
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
// calcChainReader provides a function to get the pointer to the structure
|
// calcChainReader provides a function to get the pointer to the structure
|
||||||
// after deserialization of xl/calcChain.xml.
|
// after deserialization of xl/calcChain.xml.
|
||||||
func (f *File) calcChainReader() *xlsxCalcChain {
|
func (f *File) calcChainReader() *xlsxCalcChain {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.CalcChain == nil {
|
if f.CalcChain == nil {
|
||||||
var c xlsxCalcChain
|
f.CalcChain = new(xlsxCalcChain)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")), &c)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/calcChain.xml")))).
|
||||||
f.CalcChain = &c
|
Decode(f.CalcChain); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return f.CalcChain
|
return f.CalcChain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
chart.go
16
chart.go
|
@ -10,9 +10,12 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -1735,14 +1738,21 @@ func (f *File) drawPlotAreaTxPr() *cTxPr {
|
||||||
// deserialization, two different structures: decodeWsDr and encodeWsDr are
|
// deserialization, two different structures: decodeWsDr and encodeWsDr are
|
||||||
// defined.
|
// defined.
|
||||||
func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
|
func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
if f.Drawings[path] == nil {
|
if f.Drawings[path] == nil {
|
||||||
content := xlsxWsDr{}
|
content := xlsxWsDr{}
|
||||||
content.A = NameSpaceDrawingML
|
content.A = NameSpaceDrawingML
|
||||||
content.Xdr = NameSpaceDrawingMLSpreadSheet
|
content.Xdr = NameSpaceDrawingMLSpreadSheet
|
||||||
_, ok := f.XLSX[path]
|
if _, ok = f.XLSX[path]; ok { // Append Model
|
||||||
if ok { // Append Model
|
|
||||||
decodeWsDr := decodeWsDr{}
|
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
|
content.R = decodeWsDr.R
|
||||||
for _, v := range decodeWsDr.OneCellAnchor {
|
for _, v := range decodeWsDr.OneCellAnchor {
|
||||||
content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
|
content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{
|
||||||
|
|
23
comment.go
23
comment.go
|
@ -10,9 +10,12 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -303,12 +306,16 @@ func (f *File) countComments() int {
|
||||||
// decodeVMLDrawingReader provides a function to get the pointer to the
|
// decodeVMLDrawingReader provides a function to get the pointer to the
|
||||||
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
|
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
|
||||||
func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
|
func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.DecodeVMLDrawing[path] == nil {
|
if f.DecodeVMLDrawing[path] == nil {
|
||||||
c, ok := f.XLSX[path]
|
c, ok := f.XLSX[path]
|
||||||
if ok {
|
if ok {
|
||||||
d := decodeVmlDrawing{}
|
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c))).
|
||||||
f.DecodeVMLDrawing[path] = &d
|
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f.DecodeVMLDrawing[path]
|
return f.DecodeVMLDrawing[path]
|
||||||
|
@ -328,12 +335,16 @@ func (f *File) vmlDrawingWriter() {
|
||||||
// commentsReader provides a function to get the pointer to the structure
|
// commentsReader provides a function to get the pointer to the structure
|
||||||
// after deserialization of xl/comments%d.xml.
|
// after deserialization of xl/comments%d.xml.
|
||||||
func (f *File) commentsReader(path string) *xlsxComments {
|
func (f *File) commentsReader(path string) *xlsxComments {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.Comments[path] == nil {
|
if f.Comments[path] == nil {
|
||||||
content, ok := f.XLSX[path]
|
content, ok := f.XLSX[path]
|
||||||
if ok {
|
if ok {
|
||||||
c := xlsxComments{}
|
f.Comments[path] = new(xlsxComments)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(content), &c)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content))).
|
||||||
f.Comments[path] = &c
|
Decode(f.Comments[path]); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f.Comments[path]
|
return f.Comments[path]
|
||||||
|
|
66
docProps.go
66
docProps.go
|
@ -10,7 +10,10 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,13 +68,23 @@ import (
|
||||||
// Version: "1.0.0",
|
// Version: "1.0.0",
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
func (f *File) SetDocProps(docProperties *DocProperties) error {
|
func (f *File) SetDocProps(docProperties *DocProperties) (err error) {
|
||||||
core := decodeCoreProperties{}
|
var (
|
||||||
err := xml.Unmarshal(namespaceStrictToTransitional(f.readXML("docProps/core.xml")), &core)
|
core *decodeCoreProperties
|
||||||
if err != nil {
|
newProps *xlsxCoreProperties
|
||||||
return err
|
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,
|
Dc: NameSpaceDublinCore,
|
||||||
Dcterms: NameSpaceDublinCoreTerms,
|
Dcterms: NameSpaceDublinCoreTerms,
|
||||||
Dcmitype: NameSpaceDublinCoreMetadataIntiative,
|
Dcmitype: NameSpaceDublinCoreMetadataIntiative,
|
||||||
|
@ -88,18 +101,16 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
|
||||||
ContentStatus: core.ContentStatus,
|
ContentStatus: core.ContentStatus,
|
||||||
Category: core.Category,
|
Category: core.Category,
|
||||||
Version: core.Version,
|
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
|
immutable, mutable = reflect.ValueOf(*docProperties), reflect.ValueOf(newProps).Elem()
|
||||||
newProps.Created.Type = core.Created.Type
|
for _, field = range fields {
|
||||||
newProps.Modified.Text = core.Modified.Text
|
if val = immutable.FieldByName(field).String(); val != "" {
|
||||||
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 != "" {
|
|
||||||
mutable.FieldByName(field).SetString(val)
|
mutable.FieldByName(field).SetString(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,19 +120,22 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
|
||||||
if docProperties.Modified != "" {
|
if docProperties.Modified != "" {
|
||||||
newProps.Modified.Text = docProperties.Modified
|
newProps.Modified.Text = docProperties.Modified
|
||||||
}
|
}
|
||||||
output, err := xml.Marshal(&newProps)
|
output, err = xml.Marshal(newProps)
|
||||||
f.saveFileList("docProps/core.xml", output)
|
f.saveFileList("docProps/core.xml", output)
|
||||||
return err
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDocProps provides a function to get document core properties.
|
// GetDocProps provides a function to get document core properties.
|
||||||
func (f *File) GetDocProps() (*DocProperties, error) {
|
func (f *File) GetDocProps() (ret *DocProperties, err error) {
|
||||||
core := decodeCoreProperties{}
|
var core = new(decodeCoreProperties)
|
||||||
err := xml.Unmarshal(namespaceStrictToTransitional(f.readXML("docProps/core.xml")), &core)
|
|
||||||
if err != nil {
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("docProps/core.xml")))).
|
||||||
return nil, err
|
Decode(core); err != nil && err != io.EOF {
|
||||||
|
err = fmt.Errorf("xml decode error: %s", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return &DocProperties{
|
ret, err = &DocProperties{
|
||||||
Category: core.Category,
|
Category: core.Category,
|
||||||
ContentStatus: core.ContentStatus,
|
ContentStatus: core.ContentStatus,
|
||||||
Created: core.Created.Text,
|
Created: core.Created.Text,
|
||||||
|
@ -137,4 +151,6 @@ func (f *File) GetDocProps() (*DocProperties, error) {
|
||||||
Language: core.Language,
|
Language: core.Language,
|
||||||
Version: core.Version,
|
Version: core.Version,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func TestSetDocProps(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
|
||||||
f.XLSX["docProps/core.xml"] = nil
|
f.XLSX["docProps/core.xml"] = nil
|
||||||
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "EOF")
|
assert.NoError(t, f.SetDocProps(&DocProperties{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDocProps(t *testing.T) {
|
func TestGetDocProps(t *testing.T) {
|
||||||
|
@ -52,5 +52,5 @@ func TestGetDocProps(t *testing.T) {
|
||||||
assert.Equal(t, props.Creator, "Microsoft Office User")
|
assert.Equal(t, props.Creator, "Microsoft Office User")
|
||||||
f.XLSX["docProps/core.xml"] = nil
|
f.XLSX["docProps/core.xml"] = nil
|
||||||
_, err = f.GetDocProps()
|
_, err = f.GetDocProps()
|
||||||
assert.EqualError(t, err, "EOF")
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
81
excelize.go
81
excelize.go
|
@ -22,6 +22,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html/charset"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File define a populated XLSX file struct.
|
// File define a populated XLSX file struct.
|
||||||
|
@ -43,8 +45,11 @@ type File struct {
|
||||||
WorkBook *xlsxWorkbook
|
WorkBook *xlsxWorkbook
|
||||||
Relationships map[string]*xlsxRelationships
|
Relationships map[string]*xlsxRelationships
|
||||||
XLSX map[string][]byte
|
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
|
// OpenFile take the name of an XLSX file and returns a populated XLSX file
|
||||||
// struct for it.
|
// struct for it.
|
||||||
func OpenFile(filename string) (*File, error) {
|
func OpenFile(filename string) (*File, error) {
|
||||||
|
@ -61,6 +66,21 @@ func OpenFile(filename string) (*File, error) {
|
||||||
return f, nil
|
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.
|
// OpenReader take an io.Reader and return a populated XLSX file.
|
||||||
func OpenReader(r io.Reader) (*File, error) {
|
func OpenReader(r io.Reader) (*File, error) {
|
||||||
b, err := ioutil.ReadAll(r)
|
b, err := ioutil.ReadAll(r)
|
||||||
|
@ -88,17 +108,8 @@ func OpenReader(r io.Reader) (*File, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f := &File{
|
f := newFile()
|
||||||
checked: make(map[string]bool),
|
f.SheetCount, f.XLSX = sheetCount, file
|
||||||
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.CalcChain = f.calcChainReader()
|
f.CalcChain = f.calcChainReader()
|
||||||
f.sheetMap = f.getSheetMap()
|
f.sheetMap = f.getSheetMap()
|
||||||
f.Styles = f.stylesReader()
|
f.Styles = f.stylesReader()
|
||||||
|
@ -106,6 +117,16 @@ func OpenReader(r io.Reader) (*File, error) {
|
||||||
return f, nil
|
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
|
// setDefaultTimeStyle provides a function to set default numbers format for
|
||||||
// time.Time type cell value by given worksheet name, cell coordinates and
|
// time.Time type cell value by given worksheet name, cell coordinates and
|
||||||
// number format code.
|
// 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
|
// workSheetReader provides a function to get the pointer to the structure
|
||||||
// after deserialization by given worksheet name.
|
// after deserialization by given worksheet name.
|
||||||
func (f *File) workSheetReader(sheet string) (*xlsxWorksheet, error) {
|
func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
|
||||||
name, ok := f.sheetMap[trimSheetName(sheet)]
|
var (
|
||||||
if !ok {
|
name string
|
||||||
return nil, fmt.Errorf("sheet %s is not exist", sheet)
|
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 {
|
if xlsx = f.Sheet[name]; f.Sheet[name] == nil {
|
||||||
var xlsx xlsxWorksheet
|
xlsx = new(xlsxWorksheet)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
|
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 {
|
if f.checked == nil {
|
||||||
f.checked = make(map[string]bool)
|
f.checked = make(map[string]bool)
|
||||||
}
|
}
|
||||||
ok := f.checked[name]
|
if ok = f.checked[name]; !ok {
|
||||||
if !ok {
|
checkSheet(xlsx)
|
||||||
checkSheet(&xlsx)
|
if err = checkRow(xlsx); err != nil {
|
||||||
checkRow(&xlsx)
|
return
|
||||||
|
}
|
||||||
f.checked[name] = true
|
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
|
// checkSheet provides a function to fill each row element and make that is
|
||||||
|
|
8
file.go
8
file.go
|
@ -33,12 +33,8 @@ func NewFile() *File {
|
||||||
file["xl/styles.xml"] = []byte(XMLHeader + templateStyles)
|
file["xl/styles.xml"] = []byte(XMLHeader + templateStyles)
|
||||||
file["xl/workbook.xml"] = []byte(XMLHeader + templateWorkbook)
|
file["xl/workbook.xml"] = []byte(XMLHeader + templateWorkbook)
|
||||||
file["[Content_Types].xml"] = []byte(XMLHeader + templateContentTypes)
|
file["[Content_Types].xml"] = []byte(XMLHeader + templateContentTypes)
|
||||||
f := &File{
|
f := newFile()
|
||||||
sheetMap: make(map[string]string),
|
f.SheetCount, f.XLSX = 1, file
|
||||||
Sheet: make(map[string]*xlsxWorksheet),
|
|
||||||
SheetCount: 1,
|
|
||||||
XLSX: file,
|
|
||||||
}
|
|
||||||
f.CalcChain = f.calcChainReader()
|
f.CalcChain = f.calcChainReader()
|
||||||
f.Comments = make(map[string]*xlsxComments)
|
f.Comments = make(map[string]*xlsxComments)
|
||||||
f.ContentTypes = f.contentTypesReader()
|
f.ContentTypes = f.contentTypesReader()
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -7,4 +7,6 @@ require (
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a
|
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
go.sum
8
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/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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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 h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0=
|
||||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
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.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=
|
||||||
|
|
68
picture.go
68
picture.go
|
@ -14,7 +14,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"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
|
// getPicture provides a function to get picture base name and raw content
|
||||||
// embed in XLSX by given coordinates and drawing relationships.
|
// embed in XLSX by given coordinates and drawing relationships.
|
||||||
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (string, []byte, error) {
|
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (ret string, buf []byte, err error) {
|
||||||
wsDr, _ := f.drawingParser(drawingXML)
|
var (
|
||||||
for _, anchor := range wsDr.TwoCellAnchor {
|
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 != nil && anchor.Pic != nil {
|
||||||
if anchor.From.Col == col && anchor.From.Row == row {
|
if anchor.From.Col == col && anchor.From.Row == row {
|
||||||
xlsxRelationship := f.getDrawingRelationships(drawingRelationships,
|
xxRelationship = f.getDrawingRelationships(drawingRelationships,
|
||||||
anchor.Pic.BlipFill.Blip.Embed)
|
anchor.Pic.BlipFill.Blip.Embed)
|
||||||
_, ok := supportImageTypes[filepath.Ext(xlsxRelationship.Target)]
|
if _, ok = supportImageTypes[filepath.Ext(xxRelationship.Target)]; ok {
|
||||||
if ok {
|
ret, buf = filepath.Base(xxRelationship.Target), []byte(f.XLSX[strings.Replace(xxRelationship.Target, "..", "xl", -1)])
|
||||||
return filepath.Base(xlsxRelationship.Target),
|
return
|
||||||
[]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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeWsDr := decodeWsDr{}
|
return
|
||||||
_ = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDrawingRelationships provides a function to get drawing relationships
|
// getDrawingRelationships provides a function to get drawing relationships
|
||||||
|
|
12
rows.go
12
rows.go
|
@ -10,9 +10,11 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"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
|
// sharedStringsReader provides a function to get the pointer to the structure
|
||||||
// after deserialization of xl/sharedStrings.xml.
|
// after deserialization of xl/sharedStrings.xml.
|
||||||
func (f *File) sharedStringsReader() *xlsxSST {
|
func (f *File) sharedStringsReader() *xlsxSST {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.SharedStrings == nil {
|
if f.SharedStrings == nil {
|
||||||
var sharedStrings xlsxSST
|
var sharedStrings xlsxSST
|
||||||
ss := f.readXML("xl/sharedStrings.xml")
|
ss := f.readXML("xl/sharedStrings.xml")
|
||||||
if len(ss) == 0 {
|
if len(ss) == 0 {
|
||||||
ss = f.readXML("xl/SharedStrings.xml")
|
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
|
f.SharedStrings = &sharedStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.SharedStrings
|
return f.SharedStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
78
sheet.go
78
sheet.go
|
@ -15,7 +15,9 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -61,11 +63,16 @@ func (f *File) NewSheet(name string) int {
|
||||||
// contentTypesReader provides a function to get the pointer to the
|
// contentTypesReader provides a function to get the pointer to the
|
||||||
// [Content_Types].xml structure after deserialization.
|
// [Content_Types].xml structure after deserialization.
|
||||||
func (f *File) contentTypesReader() *xlsxTypes {
|
func (f *File) contentTypesReader() *xlsxTypes {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.ContentTypes == nil {
|
if f.ContentTypes == nil {
|
||||||
var content xlsxTypes
|
f.ContentTypes = new(xlsxTypes)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")), &content)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("[Content_Types].xml")))).
|
||||||
f.ContentTypes = &content
|
Decode(f.ContentTypes); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return f.ContentTypes
|
return f.ContentTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,11 +88,16 @@ func (f *File) contentTypesWriter() {
|
||||||
// workbookReader provides a function to get the pointer to the xl/workbook.xml
|
// workbookReader provides a function to get the pointer to the xl/workbook.xml
|
||||||
// structure after deserialization.
|
// structure after deserialization.
|
||||||
func (f *File) workbookReader() *xlsxWorkbook {
|
func (f *File) workbookReader() *xlsxWorkbook {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.WorkBook == nil {
|
if f.WorkBook == nil {
|
||||||
var content xlsxWorkbook
|
f.WorkBook = new(xlsxWorkbook)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")), &content)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/workbook.xml")))).
|
||||||
f.WorkBook = &content
|
Decode(f.WorkBook); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return f.WorkBook
|
return f.WorkBook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,42 +691,51 @@ func (f *File) GetSheetVisible(name string) bool {
|
||||||
//
|
//
|
||||||
// result, err := f.SearchSheet("Sheet1", "[0-9]", true)
|
// 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 (
|
var (
|
||||||
regSearch bool
|
xlsx *xlsxWorksheet
|
||||||
result []string
|
regSearch, r, ok bool
|
||||||
|
name string
|
||||||
|
output []byte
|
||||||
)
|
)
|
||||||
for _, r := range reg {
|
|
||||||
|
for _, r = range reg {
|
||||||
regSearch = r
|
regSearch = r
|
||||||
}
|
}
|
||||||
xlsx, err := f.workSheetReader(sheet)
|
if xlsx, err = f.workSheetReader(sheet); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return result, err
|
|
||||||
}
|
}
|
||||||
name, ok := f.sheetMap[trimSheetName(sheet)]
|
if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
|
||||||
if !ok {
|
return
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
if xlsx != nil {
|
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))
|
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.searchSheet(name, value, regSearch)
|
return f.searchSheet(name, value, regSearch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchSheet provides a function to get coordinates by given worksheet name,
|
// searchSheet provides a function to get coordinates by given worksheet name,
|
||||||
// cell value, and regular expression.
|
// 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 (
|
var (
|
||||||
|
d *xlsxSST
|
||||||
|
decoder *xml.Decoder
|
||||||
inElement string
|
inElement string
|
||||||
result []string
|
|
||||||
r xlsxRow
|
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 {
|
for {
|
||||||
token, _ := decoder.Token()
|
if token, err = decoder.Token(); err != nil || token == nil {
|
||||||
if token == nil {
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch startElement := token.(type) {
|
switch startElement := token.(type) {
|
||||||
|
@ -750,7 +771,8 @@ func (f *File) searchSheet(name, value string, regSearch bool) ([]string, error)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHeaderFooter provides a function to set headers and footers by given
|
// 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
|
// relsReader provides a function to get the pointer to the structure
|
||||||
// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels.
|
// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels.
|
||||||
func (f *File) relsReader(path string) *xlsxRelationships {
|
func (f *File) relsReader(path string) *xlsxRelationships {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.Relationships[path] == nil {
|
if f.Relationships[path] == nil {
|
||||||
_, ok := f.XLSX[path]
|
_, ok := f.XLSX[path]
|
||||||
if ok {
|
if ok {
|
||||||
c := xlsxRelationships{}
|
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
|
f.Relationships[path] = &c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Relationships[path]
|
return f.Relationships[path]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
87
sparkline.go
87
sparkline.go
|
@ -10,8 +10,10 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -386,23 +388,40 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup {
|
||||||
// ColorAxis | An RGB Color is specified as RRGGBB
|
// ColorAxis | An RGB Color is specified as RRGGBB
|
||||||
// Axis | Show sparkline axis
|
// 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
|
// parameter validation
|
||||||
ws, err := f.parseFormatAddSparklineSet(sheet, opt)
|
if ws, err = f.parseFormatAddSparklineSet(sheet, opt); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
// Handle the sparkline type
|
// Handle the sparkline type
|
||||||
sparkType := "line"
|
sparkType = "line"
|
||||||
sparkTypes := map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
|
sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
|
||||||
if opt.Type != "" {
|
if opt.Type != "" {
|
||||||
specifiedSparkTypes, ok := sparkTypes[opt.Type]
|
if specifiedSparkTypes, ok = sparkTypes[opt.Type]; !ok {
|
||||||
if !ok {
|
err = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
|
||||||
return errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
|
return
|
||||||
}
|
}
|
||||||
sparkType = specifiedSparkTypes
|
sparkType = specifiedSparkTypes
|
||||||
}
|
}
|
||||||
group := f.addSparklineGroupByStyle(opt.Style)
|
group = f.addSparklineGroupByStyle(opt.Style)
|
||||||
group.Type = sparkType
|
group.Type = sparkType
|
||||||
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
|
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
|
||||||
group.DisplayEmptyCellsAs = "gap"
|
group.DisplayEmptyCellsAs = "gap"
|
||||||
|
@ -423,43 +442,57 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) error {
|
||||||
}
|
}
|
||||||
f.addSparkline(opt, group)
|
f.addSparkline(opt, group)
|
||||||
if ws.ExtLst.Ext != "" { // append mode ext
|
if ws.ExtLst.Ext != "" { // append mode ext
|
||||||
decodeExtLst := decodeWorksheetExt{}
|
decodeExtLst = new(decodeWorksheetExt)
|
||||||
err = xml.Unmarshal([]byte("<extLst>"+ws.ExtLst.Ext+"</extLst>"), &decodeExtLst)
|
if err = f.xmlNewDecoder(bytes.NewReader([]byte("<extLst>" + ws.ExtLst.Ext + "</extLst>"))).
|
||||||
if err != nil {
|
Decode(decodeExtLst); err != nil && err != io.EOF {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
for idx, ext := range decodeExtLst.Ext {
|
for idx, ext = range decodeExtLst.Ext {
|
||||||
if ext.URI == ExtURISparklineGroups {
|
if ext.URI == ExtURISparklineGroups {
|
||||||
decodeSparklineGroups := decodeX14SparklineGroups{}
|
decodeSparklineGroups = new(decodeX14SparklineGroups)
|
||||||
_ = xml.Unmarshal([]byte(ext.Content), &decodeSparklineGroups)
|
if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))).
|
||||||
sparklineGroupBytes, _ := xml.Marshal(group)
|
Decode(decodeSparklineGroups); err != nil && err != io.EOF {
|
||||||
groups := xlsxX14SparklineGroups{
|
return
|
||||||
|
}
|
||||||
|
if sparklineGroupBytes, err = xml.Marshal(group); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groups = &xlsxX14SparklineGroups{
|
||||||
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
|
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
|
||||||
Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
|
Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
|
||||||
}
|
}
|
||||||
sparklineGroupsBytes, _ := xml.Marshal(groups)
|
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
|
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extLstBytes, _ := xml.Marshal(decodeExtLst)
|
if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil {
|
||||||
extLst := string(extLstBytes)
|
return
|
||||||
|
}
|
||||||
|
extLst = string(extLstBytes)
|
||||||
ws.ExtLst = &xlsxExtLst{
|
ws.ExtLst = &xlsxExtLst{
|
||||||
Ext: strings.TrimSuffix(strings.TrimPrefix(extLst, "<extLst>"), "</extLst>"),
|
Ext: strings.TrimSuffix(strings.TrimPrefix(extLst, "<extLst>"), "</extLst>"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
groups := xlsxX14SparklineGroups{
|
groups = &xlsxX14SparklineGroups{
|
||||||
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
|
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
|
||||||
SparklineGroups: []*xlsxX14SparklineGroup{group},
|
SparklineGroups: []*xlsxX14SparklineGroup{group},
|
||||||
}
|
}
|
||||||
sparklineGroupsBytes, _ := xml.Marshal(groups)
|
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
|
||||||
extLst := xlsxWorksheetExt{
|
return
|
||||||
|
}
|
||||||
|
ext = &xlsxWorksheetExt{
|
||||||
URI: ExtURISparklineGroups,
|
URI: ExtURISparklineGroups,
|
||||||
Content: string(sparklineGroupsBytes),
|
Content: string(sparklineGroupsBytes),
|
||||||
}
|
}
|
||||||
extBytes, _ := xml.Marshal(extLst)
|
if extBytes, err = xml.Marshal(ext); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
ws.ExtLst.Ext = string(extBytes)
|
ws.ExtLst.Ext = string(extBytes)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFormatAddSparklineSet provides a function to validate sparkline
|
// parseFormatAddSparklineSet provides a function to validate sparkline
|
||||||
|
|
26
styles.go
26
styles.go
|
@ -10,9 +10,12 @@
|
||||||
package excelize
|
package excelize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -997,11 +1000,16 @@ func is12HourTime(format string) bool {
|
||||||
// stylesReader provides a function to get the pointer to the structure after
|
// stylesReader provides a function to get the pointer to the structure after
|
||||||
// deserialization of xl/styles.xml.
|
// deserialization of xl/styles.xml.
|
||||||
func (f *File) stylesReader() *xlsxStyleSheet {
|
func (f *File) stylesReader() *xlsxStyleSheet {
|
||||||
|
var err error
|
||||||
|
|
||||||
if f.Styles == nil {
|
if f.Styles == nil {
|
||||||
var styleSheet xlsxStyleSheet
|
f.Styles = new(xlsxStyleSheet)
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/styles.xml")), &styleSheet)
|
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/styles.xml")))).
|
||||||
f.Styles = &styleSheet
|
Decode(f.Styles); err != nil && err != io.EOF {
|
||||||
|
log.Printf("xml decode error: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return f.Styles
|
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
|
// themeReader provides a function to get the pointer to the xl/theme/theme1.xml
|
||||||
// structure after deserialization.
|
// structure after deserialization.
|
||||||
func (f *File) themeReader() *xlsxTheme {
|
func (f *File) themeReader() *xlsxTheme {
|
||||||
var theme xlsxTheme
|
var (
|
||||||
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")), &theme)
|
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
|
return &theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue