From 1aed1d744b12885c4a88c090494175c59208e038 Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 25 Feb 2019 22:14:34 +0800 Subject: [PATCH] Resolve #274, performance optimization for add images, charts and shapes --- chart.go | 52 ++++++++++++------------ comment.go | 7 +--- excelize.go | 4 ++ excelize_test.go | 18 ++++++++ file.go | 4 ++ picture.go | 104 +++++++++++++++++++++++++++++++---------------- shape.go | 9 +--- 7 files changed, 126 insertions(+), 72 deletions(-) diff --git a/chart.go b/chart.go index 77a01254..f11fd55a 100644 --- a/chart.go +++ b/chart.go @@ -1207,28 +1207,34 @@ func (f *File) drawPlotAreaTxPr() *cTxPr { // the problem that the label structure is changed after serialization and // deserialization, two different structures: decodeWsDr and encodeWsDr are // defined. -func (f *File) drawingParser(drawingXML string, content *xlsxWsDr) int { +func (f *File) drawingParser(path string) (*xlsxWsDr, int) { cNvPrID := 1 - _, ok := f.XLSX[drawingXML] - if ok { // Append Model - decodeWsDr := decodeWsDr{} - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(drawingXML)), &decodeWsDr) - content.R = decodeWsDr.R - cNvPrID = len(decodeWsDr.OneCellAnchor) + len(decodeWsDr.TwoCellAnchor) + 1 - for _, v := range decodeWsDr.OneCellAnchor { - content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ - EditAs: v.EditAs, - GraphicFrame: v.Content, - }) - } - for _, v := range decodeWsDr.TwoCellAnchor { - content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ - EditAs: v.EditAs, - GraphicFrame: v.Content, - }) + if f.Drawings[path] == nil { + content := xlsxWsDr{} + content.A = NameSpaceDrawingML + content.Xdr = NameSpaceDrawingMLSpreadSheet + _, ok := f.XLSX[path] + if ok { // Append Model + decodeWsDr := decodeWsDr{} + _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(path)), &decodeWsDr) + content.R = decodeWsDr.R + cNvPrID = len(decodeWsDr.OneCellAnchor) + len(decodeWsDr.TwoCellAnchor) + 1 + for _, v := range decodeWsDr.OneCellAnchor { + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } + for _, v := range decodeWsDr.TwoCellAnchor { + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } } + f.Drawings[path] = &content } - return cNvPrID + return f.Drawings[path], cNvPrID } // addDrawingChart provides a function to add chart graphic frame by given @@ -1242,10 +1248,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) - content := xlsxWsDr{} - content.A = NameSpaceDrawingML - content.Xdr = NameSpaceDrawingMLSpreadSheet - cNvPrID := f.drawingParser(drawingXML, &content) + content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning from := xlsxFrom{} @@ -1286,6 +1289,5 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI FPrintsWithSheet: formatSet.FPrintsWithSheet, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) - output, _ := xml.Marshal(content) - f.saveFileList(drawingXML, output) + f.Drawings[drawingXML] = content } diff --git a/comment.go b/comment.go index 5db6312f..c86e9325 100644 --- a/comment.go +++ b/comment.go @@ -33,10 +33,7 @@ func parseFormatCommentsSet(formatSet string) (*formatComment, error) { func (f *File) GetComments() (comments map[string][]Comment) { comments = map[string][]Comment{} for n := range f.sheetMap { - c, ok := f.XLSX["xl"+strings.TrimPrefix(f.getSheetComments(f.GetSheetIndex(n)), "..")] - if ok { - d := xlsxComments{} - xml.Unmarshal([]byte(c), &d) + if d := f.commentsReader("xl" + strings.TrimPrefix(f.getSheetComments(f.GetSheetIndex(n)), "..")); d != nil { sheetComments := []Comment{} for _, comment := range d.CommentList.Comment { sheetComment := Comment{} @@ -294,7 +291,7 @@ func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { return f.DecodeVMLDrawing[path] } -// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml. +// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml // after serialize structure. func (f *File) vmlDrawingWriter() { for path, vml := range f.VMLDrawing { diff --git a/excelize.go b/excelize.go index df3cd05b..a2bec077 100644 --- a/excelize.go +++ b/excelize.go @@ -28,6 +28,8 @@ type File struct { CalcChain *xlsxCalcChain Comments map[string]*xlsxComments ContentTypes *xlsxTypes + DrawingRels map[string]*xlsxWorkbookRels + Drawings map[string]*xlsxWsDr Path string SharedStrings *xlsxSST Sheet map[string]*xlsxWorksheet @@ -76,6 +78,8 @@ func OpenReader(r io.Reader) (*File, error) { f := &File{ checked: make(map[string]bool), Comments: make(map[string]*xlsxComments), + DrawingRels: make(map[string]*xlsxWorkbookRels), + Drawings: make(map[string]*xlsxWsDr), Sheet: make(map[string]*xlsxWorksheet), SheetCount: sheetCount, DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), diff --git a/excelize_test.go b/excelize_test.go index d621b87b..ed6f073c 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -817,6 +817,24 @@ func TestGetPicture(t *testing.T) { xlsx.getDrawingRelationships("", "") xlsx.getSheetRelationshipsTargetByID("", "") xlsx.deleteSheetRelationships("", "") + + // Try to get picture from a local storage file. + assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestGetPicture.xlsx"))) + xlsx, err = OpenFile(filepath.Join("test", "TestGetPicture.xlsx")) + if !assert.NoError(t, err) { + t.FailNow() + } + file, raw = xlsx.GetPicture("Sheet1", "F21") + if file == "" { + err = ioutil.WriteFile(file, raw, 0644) + if !assert.NoError(t, err) { + t.FailNow() + } + } + + // Try to get picture from a local storage file that doesn't contain an image. + file, raw = xlsx.GetPicture("Sheet1", "F22") + t.Log(file, len(raw)) } func TestSheetVisibility(t *testing.T) { diff --git a/file.go b/file.go index b6bf57d0..8d68851e 100644 --- a/file.go +++ b/file.go @@ -42,6 +42,8 @@ func NewFile() *File { f.CalcChain = f.calcChainReader() f.Comments = make(map[string]*xlsxComments) f.ContentTypes = f.contentTypesReader() + f.DrawingRels = make(map[string]*xlsxWorkbookRels) + f.Drawings = make(map[string]*xlsxWsDr) f.Styles = f.stylesReader() f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing) @@ -94,6 +96,8 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) { f.calcChainWriter() f.commentsWriter() f.contentTypesWriter() + f.drawingRelsWriter() + f.drawingsWriter() f.vmlDrawingWriter() f.workbookWriter() f.workbookRelsWriter() diff --git a/picture.go b/picture.go index 763d89a6..2ad9db2c 100644 --- a/picture.go +++ b/picture.go @@ -272,10 +272,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) - content := xlsxWsDr{} - content.A = NameSpaceDrawingML - content.Xdr = NameSpaceDrawingMLSpreadSheet - cNvPrID := f.drawingParser(drawingXML, &content) + content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Positioning from := xlsxFrom{} @@ -311,8 +308,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he FPrintsWithSheet: formatSet.FPrintsWithSheet, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) - output, _ := xml.Marshal(content) - f.saveFileList(drawingXML, output) + f.Drawings[drawingXML] = content } // addDrawingRelationships provides a function to add image part relationships @@ -320,27 +316,25 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he // relationship type and target. func (f *File) addDrawingRelationships(index int, relType, target, targetMode string) int { var rels = "xl/drawings/_rels/drawing" + strconv.Itoa(index) + ".xml.rels" - var drawingRels xlsxWorkbookRels var rID = 1 var ID bytes.Buffer ID.WriteString("rId") ID.WriteString(strconv.Itoa(rID)) - _, ok := f.XLSX[rels] - if ok { - ID.Reset() - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rels)), &drawingRels) - rID = len(drawingRels.Relationships) + 1 - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) + drawingRels := f.drawingRelsReader(rels) + if drawingRels == nil { + drawingRels = &xlsxWorkbookRels{} } + ID.Reset() + rID = len(drawingRels.Relationships) + 1 + ID.WriteString("rId") + ID.WriteString(strconv.Itoa(rID)) drawingRels.Relationships = append(drawingRels.Relationships, xlsxWorkbookRelation{ ID: ID.String(), Type: relType, Target: target, TargetMode: targetMode, }) - output, _ := xml.Marshal(drawingRels) - f.saveFileList(rels, output) + f.DrawingRels[rels] = drawingRels return rID } @@ -482,22 +476,30 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte) { } target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID) drawingXML := strings.Replace(target, "..", "xl", -1) - + cell = strings.ToUpper(cell) + fromCol := string(strings.Map(letterOnlyMapF, cell)) + fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell)) + row := fromRow - 1 + col := TitleToNumber(fromCol) + drawingRelationships := strings.Replace(strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1) + 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 { + xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed) + _, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)] + if ok { + return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)]) + } + } + } + } _, ok := f.XLSX[drawingXML] if !ok { return "", nil } decodeWsDr := decodeWsDr{} _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(drawingXML)), &decodeWsDr) - - cell = strings.ToUpper(cell) - fromCol := string(strings.Map(letterOnlyMapF, cell)) - fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell)) - row := fromRow - 1 - col := TitleToNumber(fromCol) - - drawingRelationships := strings.Replace(strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1) - for _, anchor := range decodeWsDr.TwoCellAnchor { decodeTwoCellAnchor := decodeTwoCellAnchor{} _ = xml.Unmarshal([]byte(""+anchor.Content+""), &decodeTwoCellAnchor) @@ -518,16 +520,48 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte) { // from xl/drawings/_rels/drawing%s.xml.rels by given file name and // relationship ID. func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation { - _, ok := f.XLSX[rels] - if !ok { - return nil - } - var drawingRels xlsxWorkbookRels - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rels)), &drawingRels) - for _, v := range drawingRels.Relationships { - if v.ID == rID { - return &v + if drawingRels := f.drawingRelsReader(rels); drawingRels != nil { + for _, v := range drawingRels.Relationships { + if v.ID == rID { + return &v + } } } return nil } + +// drawingRelsReader provides a function to get the pointer to the structure +// after deserialization of xl/drawings/_rels/drawing%d.xml.rels. +func (f *File) drawingRelsReader(rel string) *xlsxWorkbookRels { + if f.DrawingRels[rel] == nil { + _, ok := f.XLSX[rel] + if ok { + d := xlsxWorkbookRels{} + _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rel)), &d) + f.DrawingRels[rel] = &d + } + } + return f.DrawingRels[rel] +} + +// drawingRelsWriter provides a function to save +// xl/drawings/_rels/drawing%d.xml.rels after serialize structure. +func (f *File) drawingRelsWriter() { + for path, d := range f.DrawingRels { + if d != nil { + v, _ := xml.Marshal(d) + f.saveFileList(path, v) + } + } +} + +// drawingsWriter provides a function to save xl/drawings/drawing%d.xml after +// serialize structure. +func (f *File) drawingsWriter() { + for path, d := range f.Drawings { + if d != nil { + v, _ := xml.Marshal(d) + f.saveFileList(path, v) + } + } +} diff --git a/shape.go b/shape.go index e2281a23..3cf09d8a 100644 --- a/shape.go +++ b/shape.go @@ -11,7 +11,6 @@ package excelize import ( "encoding/json" - "encoding/xml" "strconv" "strings" ) @@ -293,10 +292,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format width := int(float64(formatSet.Width) * formatSet.Format.XScale) height := int(float64(formatSet.Height) * formatSet.Format.YScale) colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.Format.OffsetX, formatSet.Format.OffsetY, width, height) - content := xlsxWsDr{} - content.A = NameSpaceDrawingML - content.Xdr = NameSpaceDrawingMLSpreadSheet - cNvPrID := f.drawingParser(drawingXML, &content) + content, cNvPrID := f.drawingParser(drawingXML) twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = formatSet.Format.Positioning from := xlsxFrom{} @@ -402,8 +398,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format FPrintsWithSheet: formatSet.Format.FPrintsWithSheet, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) - output, _ := xml.Marshal(content) - f.saveFileList(drawingXML, output) + f.Drawings[drawingXML] = content } // setShapeRef provides a function to set color with hex model by given actual