已验证 提交 4249dad8 编写于 作者: xurime's avatar xurime

Resolve #539 Merge branch 'v2'

# Conflicts:
#	rows.go
#	sheet.go
~$*.xlsx
test/Test*.xlsx
*.out
*.test
\ No newline at end of file
*.test
.idea
......@@ -182,6 +182,4 @@ This program is under the terms of the BSD 3-Clause License. See [https://openso
The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
Some struct of XML originally by [tealeg/xlsx](https://github.com/tealeg/xlsx). Licensed under the [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE).
gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/).
......@@ -182,6 +182,4 @@ func main() {
Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
本类库中部分 XML 结构体的定义参考了开源项目:[tealeg/xlsx](https://github.com/tealeg/xlsx),遵循 [BSD 3-Clause License](https://github.com/tealeg/xlsx/blob/master/LICENSE) 开源许可协议。
gopher.{ai,svg,png} 由 [Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。
......@@ -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
}
......
......@@ -10,9 +10,12 @@
package excelize
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"io"
"log"
"strconv"
"strings"
)
......@@ -1750,14 +1753,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{
......
......@@ -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]
......
......@@ -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
}
......@@ -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)
}
......@@ -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
......
......@@ -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()
......
......@@ -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
)
......@@ -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
drawRel *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,
drawRel = 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(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), []byte(f.XLSX[strings.Replace(drawRel.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 {
drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), []byte(f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)])
return
}
}
}
}
return "", nil, nil
return
}
// getDrawingRelationships provides a function to get drawing relationships
......
......@@ -14,6 +14,8 @@ import (
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"math"
"strconv"
)
......@@ -161,7 +163,7 @@ func (f *File) Rows(sheet string) (*Rows, error) {
row int
rows Rows
)
decoder := xml.NewDecoder(bytes.NewReader(f.readXML(name)))
decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
for {
token, _ := decoder.Token()
if token == nil {
......@@ -186,7 +188,7 @@ func (f *File) Rows(sheet string) (*Rows, error) {
}
rows.f = f
rows.sheet = name
rows.decoder = xml.NewDecoder(bytes.NewReader(f.readXML(name)))
rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
return &rows, nil
}
......@@ -255,15 +257,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
}
......
......@@ -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
}
......@@ -716,18 +728,21 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) {
// 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 (
err error
cellName, inElement string
result []string
cellCol, row int
d *xlsxSST
)
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 {
token, err := decoder.Token()
if err != nil || token == nil {
if err == io.EOF {
err = nil
}
break
}
switch startElement := token.(type) {
......@@ -770,7 +785,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
......@@ -1406,14 +1422,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]
}
......
......@@ -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
......
......@@ -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
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册