Skip to content

Commit 02b81b7

Browse files
committed
- Get an images in a cell supported, new function GetPicture added;
- go test updated
1 parent 8dcdf90 commit 02b81b7

File tree

5 files changed

+271
-4
lines changed

5 files changed

+271
-4
lines changed

excelize_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
_ "image/gif"
55
_ "image/jpeg"
66
_ "image/png"
7+
"io/ioutil"
78
"strconv"
89
"testing"
910
)
@@ -376,3 +377,28 @@ func TestSetDeleteSheet(t *testing.T) {
376377
t.Log(err)
377378
}
378379
}
380+
381+
func TestGetPicture(t *testing.T) {
382+
xlsx, err := OpenFile("./test/Workbook_2.xlsx")
383+
if err != nil {
384+
t.Log(err)
385+
}
386+
file, raw := xlsx.GetPicture("Sheet1", "F21")
387+
if file == "" {
388+
err = ioutil.WriteFile(file, raw, 0644)
389+
if err != nil {
390+
t.Log(err)
391+
}
392+
}
393+
// Try to get picture from a worksheet that doesn't contain any images.
394+
file, raw = xlsx.GetPicture("Sheet3", "I9")
395+
if file != "" {
396+
err = ioutil.WriteFile(file, raw, 0644)
397+
if err != nil {
398+
t.Log(err)
399+
}
400+
}
401+
// Try to get picture from a cell that doesn't contain an image.
402+
file, raw = xlsx.GetPicture("Sheet2", "A2")
403+
t.Log(file, len(raw))
404+
}

picture.go

+80-2
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,12 @@ func parseFormatPictureSet(formatSet string) *formatPicture {
7272
// }
7373
//
7474
func (f *File) AddPicture(sheet, cell, picture, format string) error {
75-
var supportTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
7675
var err error
7776
// Check picture exists first.
7877
if _, err = os.Stat(picture); os.IsNotExist(err) {
7978
return err
8079
}
81-
ext, ok := supportTypes[path.Ext(picture)]
80+
ext, ok := supportImageTypes[path.Ext(picture)]
8281
if !ok {
8382
return errors.New("Unsupported image extension")
8483
}
@@ -360,3 +359,82 @@ func (f *File) getSheetRelationshipsTargetByID(sheet string, rID string) string
360359
}
361360
return ""
362361
}
362+
363+
// GetPicture provides function to get picture base name and raw content embed
364+
// in XLSX by given worksheet and cell name. This function returns the file name
365+
// in XLSX and file contents as []byte data types. For example:
366+
//
367+
// xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx")
368+
// if err != nil {
369+
// fmt.Println(err)
370+
// os.Exit(1)
371+
// }
372+
// file, raw := xlsx.GetPicture("Sheet1", "A2")
373+
// if file == "" {
374+
// os.Exit(1)
375+
// }
376+
// err := ioutil.WriteFile(file, raw, 0644)
377+
// if err != nil {
378+
// fmt.Println(err)
379+
// os.Exit(1)
380+
// }
381+
//
382+
func (f *File) GetPicture(sheet, cell string) (string, []byte) {
383+
xlsx := f.workSheetReader(sheet)
384+
if xlsx.Drawing == nil {
385+
return "", []byte{}
386+
}
387+
target := f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID)
388+
drawingXML := strings.Replace(target, "..", "xl", -1)
389+
390+
_, ok := f.XLSX[drawingXML]
391+
if !ok {
392+
return "", []byte{}
393+
}
394+
decodeWsDr := decodeWsDr{}
395+
xml.Unmarshal([]byte(f.readXML(drawingXML)), &decodeWsDr)
396+
397+
cell = strings.ToUpper(cell)
398+
fromCol := string(strings.Map(letterOnlyMapF, cell))
399+
fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
400+
row := fromRow - 1
401+
col := titleToNumber(fromCol)
402+
403+
drawingRelationships := strings.Replace(strings.Replace(target, "../drawings", "xl/drawings/_rels", -1), ".xml", ".xml.rels", -1)
404+
405+
for _, anchor := range decodeWsDr.TwoCellAnchor {
406+
decodeTwoCellAnchor := decodeTwoCellAnchor{}
407+
xml.Unmarshal([]byte("<decodeTwoCellAnchor>"+anchor.Content+"</decodeTwoCellAnchor>"), &decodeTwoCellAnchor)
408+
if decodeTwoCellAnchor.From == nil || decodeTwoCellAnchor.Pic == nil {
409+
continue
410+
}
411+
if decodeTwoCellAnchor.From.Col == col && decodeTwoCellAnchor.From.Row == row {
412+
xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed)
413+
_, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)]
414+
if !ok {
415+
continue
416+
}
417+
418+
return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)])
419+
}
420+
}
421+
return "", []byte{}
422+
}
423+
424+
// getDrawingRelationships provides function to get drawing relationships from
425+
// xl/drawings/_rels/drawing%s.xml.rels by given file name and relationship ID.
426+
func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation {
427+
_, ok := f.XLSX[rels]
428+
if !ok {
429+
return nil
430+
}
431+
var drawingRels xlsxWorkbookRels
432+
xml.Unmarshal([]byte(f.readXML(rels)), &drawingRels)
433+
for _, v := range drawingRels.Relationships {
434+
if v.ID != rID {
435+
continue
436+
}
437+
return &v
438+
}
439+
return nil
440+
}

sheet.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,12 @@ func (f *File) GetSheetMap() map[int]string {
257257
// SetSheetBackground provides function to set background picture by given sheet
258258
// index.
259259
func (f *File) SetSheetBackground(sheet, picture string) error {
260-
var supportTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
261260
var err error
262261
// Check picture exists first.
263262
if _, err = os.Stat(picture); os.IsNotExist(err) {
264263
return err
265264
}
266-
ext, ok := supportTypes[path.Ext(picture)]
265+
ext, ok := supportImageTypes[path.Ext(picture)]
267266
if !ok {
268267
return errors.New("Unsupported image extension")
269268
}

xmlDecodeDrawing.go

+162
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,165 @@ type decodeWsDr struct {
2222
TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"`
2323
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing wsDr,omitempty"`
2424
}
25+
26+
// decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
27+
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies
28+
// a two cell anchor placeholder for a group, a shape, or a drawing element. It
29+
// moves with cells and its extents are in EMU units.
30+
type decodeTwoCellAnchor struct {
31+
From *decodeFrom `xml:"from"`
32+
To *decodeTo `xml:"to"`
33+
Pic *decodePic `xml:"pic,omitempty"`
34+
ClientData *decodeClientData `xml:"clientData"`
35+
}
36+
37+
// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
38+
// element specifies non-visual canvas properties. This allows for additional
39+
// information that does not affect the appearance of the picture to be stored.
40+
type decodeCNvPr struct {
41+
ID int `xml:"id,attr"`
42+
Name string `xml:"name,attr"`
43+
Descr string `xml:"descr,attr"`
44+
Title string `xml:"title,attr,omitempty"`
45+
}
46+
47+
// decodePicLocks directly maps the picLocks (Picture Locks). This element
48+
// specifies all locking properties for a graphic frame. These properties inform
49+
// the generating application about specific properties that have been
50+
// previously locked and thus should not be changed.
51+
type decodePicLocks struct {
52+
NoAdjustHandles bool `xml:"noAdjustHandles,attr,omitempty"`
53+
NoChangeArrowheads bool `xml:"noChangeArrowheads,attr,omitempty"`
54+
NoChangeAspect bool `xml:"noChangeAspect,attr"`
55+
NoChangeShapeType bool `xml:"noChangeShapeType,attr,omitempty"`
56+
NoCrop bool `xml:"noCrop,attr,omitempty"`
57+
NoEditPoints bool `xml:"noEditPoints,attr,omitempty"`
58+
NoGrp bool `xml:"noGrp,attr,omitempty"`
59+
NoMove bool `xml:"noMove,attr,omitempty"`
60+
NoResize bool `xml:"noResize,attr,omitempty"`
61+
NoRot bool `xml:"noRot,attr,omitempty"`
62+
NoSelect bool `xml:"noSelect,attr,omitempty"`
63+
}
64+
65+
// decodeBlip directly maps the blip element in the namespace
66+
// http://purl.oclc.org/ooxml/officeDoc ument/relationships - This element
67+
// specifies the existence of an image (binary large image or picture) and
68+
// contains a reference to the image data.
69+
type decodeBlip struct {
70+
Embed string `xml:"embed,attr"`
71+
Cstate string `xml:"cstate,attr,omitempty"`
72+
R string `xml:"r,attr"`
73+
}
74+
75+
// decodeStretch directly maps the stretch element. This element specifies that
76+
// a BLIP should be stretched to fill the target rectangle. The other option is
77+
// a tile where a BLIP is tiled to fill the available area.
78+
type decodeStretch struct {
79+
FillRect string `xml:"fillRect"`
80+
}
81+
82+
// decodeOff directly maps the colOff and rowOff element. This element is used
83+
// to specify the column offset within a cell.
84+
type decodeOff struct {
85+
X int `xml:"x,attr"`
86+
Y int `xml:"y,attr"`
87+
}
88+
89+
// decodeExt directly maps the ext element.
90+
type decodeExt struct {
91+
Cx int `xml:"cx,attr"`
92+
Cy int `xml:"cy,attr"`
93+
}
94+
95+
// decodePrstGeom directly maps the prstGeom (Preset geometry). This element
96+
// specifies when a preset geometric shape should be used instead of a custom
97+
// geometric shape. The generating application should be able to render all
98+
// preset geometries enumerated in the ST_ShapeType list.
99+
type decodePrstGeom struct {
100+
Prst string `xml:"prst,attr"`
101+
}
102+
103+
// decodeXfrm directly maps the xfrm (2D Transform for Graphic Frame). This
104+
// element specifies the transform to be applied to the corresponding graphic
105+
// frame. This transformation is applied to the graphic frame just as it would
106+
// be for a shape or group shape.
107+
type decodeXfrm struct {
108+
Off decodeOff `xml:"off"`
109+
Ext decodeExt `xml:"ext"`
110+
}
111+
112+
// decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing
113+
// Properties). This element specifies the non-visual properties for the picture
114+
// canvas. These properties are to be used by the generating application to
115+
// determine how certain properties are to be changed for the picture object in
116+
// question.
117+
type decodeCNvPicPr struct {
118+
PicLocks decodePicLocks `xml:"picLocks"`
119+
}
120+
121+
// directly maps the nvPicPr (Non-Visual Properties for a Picture). This element
122+
// specifies all non-visual properties for a picture. This element is a
123+
// container for the non-visual identification properties, shape properties and
124+
// application properties that are to be associated with a picture. This allows
125+
// for additional information that does not affect the appearance of the picture
126+
// to be stored.
127+
type decodeNvPicPr struct {
128+
CNvPr decodeCNvPr `xml:"cNvPr"`
129+
CNvPicPr decodeCNvPicPr `xml:"cNvPicPr"`
130+
}
131+
132+
// decodeBlipFill directly maps the blipFill (Picture Fill). This element
133+
// specifies the kind of picture fill that the picture object has. Because a
134+
// picture has a picture fill already by default, it is possible to have two
135+
// fills specified for a picture object.
136+
type decodeBlipFill struct {
137+
Blip decodeBlip `xml:"blip"`
138+
Stretch decodeStretch `xml:"stretch"`
139+
}
140+
141+
// decodeSpPr directly maps the spPr (Shape Properties). This element specifies
142+
// the visual shape properties that can be applied to a picture. These are the
143+
// same properties that are allowed to describe the visual properties of a shape
144+
// but are used here to describe the visual appearance of a picture within a
145+
// document.
146+
type decodeSpPr struct {
147+
Xfrm decodeXfrm `xml:"a:xfrm"`
148+
PrstGeom decodePrstGeom `xml:"a:prstGeom"`
149+
}
150+
151+
// decodePic elements encompass the definition of pictures within the DrawingML
152+
// framework. While pictures are in many ways very similar to shapes they have
153+
// specific properties that are unique in order to optimize for picture-
154+
// specific scenarios.
155+
type decodePic struct {
156+
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
157+
BlipFill decodeBlipFill `xml:"blipFill"`
158+
SpPr decodeSpPr `xml:"spPr"`
159+
}
160+
161+
// decodeFrom specifies the starting anchor.
162+
type decodeFrom struct {
163+
Col int `xml:"col"`
164+
ColOff int `xml:"colOff"`
165+
Row int `xml:"row"`
166+
RowOff int `xml:"rowOff"`
167+
}
168+
169+
// decodeTo directly specifies the ending anchor.
170+
type decodeTo struct {
171+
Col int `xml:"col"`
172+
ColOff int `xml:"colOff"`
173+
Row int `xml:"row"`
174+
RowOff int `xml:"rowOff"`
175+
}
176+
177+
// decodeClientData directly maps the clientData element. An empty element which
178+
// specifies (via attributes) certain properties related to printing and
179+
// selection of the drawing object. The fLocksWithSheet attribute (either true
180+
// or false) determines whether to disable selection when the sheet is
181+
// protected, and fPrintsWithSheet attribute (either true or false) determines
182+
// whether the object is printed when the sheet is printed.
183+
type decodeClientData struct {
184+
FLocksWithSheet bool `xml:"fLocksWithSheet,attr"`
185+
FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"`
186+
}

xmlDrawing.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const (
1212
NameSpaceXML = "http://www.w3.org/XML/1998/namespace"
1313
)
1414

15+
var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png"}
16+
1517
// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
1618
// element specifies non-visual canvas properties. This allows for additional
1719
// information that does not affect the appearance of the picture to be stored.

0 commit comments

Comments
 (0)