浏览代码

Resolve #274, performance optimization for add images, charts and shapes

xuri 6 年之前
父节点
当前提交
1aed1d744b
共有 7 个文件被更改,包括 126 次插入72 次删除
  1. 27 25
      chart.go
  2. 2 5
      comment.go
  3. 4 0
      excelize.go
  4. 18 0
      excelize_test.go
  5. 4 0
      file.go
  6. 69 35
      picture.go
  7. 2 7
      shape.go

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

+ 2 - 5
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 {

+ 4 - 0
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),

+ 18 - 0
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) {

+ 4 - 0
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()

+ 69 - 35
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)
-
-	_, 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)
-
+	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)
 	for _, anchor := range decodeWsDr.TwoCellAnchor {
 		decodeTwoCellAnchor := decodeTwoCellAnchor{}
 		_ = xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+anchor.Content+"</decodeTwoCellAnchor>"), &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)
+		}
+	}
+}

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