Skip to content

Commit

Permalink
This closes #1218 and closes #1689
Browse files Browse the repository at this point in the history
- Introduce new exported function GetPictureCells
- Upgrade dependencies module golang.org/x/net from 0.16.0 to 0.17.0
- Update unit tests
  • Loading branch information
xuri committed Oct 12, 2023
1 parent 7d3b576 commit 33d91d8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 141 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ require (
github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05
golang.org/x/crypto v0.14.0
golang.org/x/image v0.11.0
golang.org/x/net v0.16.0
golang.org/x/net v0.17.0
golang.org/x/text v0.13.0
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
221 changes: 85 additions & 136 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,34 +478,9 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
return f.getPicture(row, col, drawingXML, drawingRelationships)
}

// GetAllPictures provides a function to get all pictures meta info and raw content
// embed in spreadsheet by given worksheet. This function
// returns the image contents as []byte data types. This function is
// concurrency safe. For example:
//
// f, err := excelize.OpenFile("Book1.xlsx")
// if err != nil {
// fmt.Println(err)
// return
// }
// defer func() {
// if err := f.Close(); err != nil {
// fmt.Println(err)
// }
// }()
// picsMap, err := f.GetAllPictures("Sheet1")
// if err != nil {
// fmt.Println(err)
// }
// for cellName, pics := range picsMap {
// for _, pic := range pics {
// name := fmt.Sprintf("image%d%s", idx+1, pic.Extension)
// if err := os.WriteFile(name, pic.File, 0644); err != nil {
// fmt.Println(err)
// }
// }
// }
func (f *File) GetAllPictures(sheet string) (map[string][]Picture, error) {
// GetPictureCells returns all picture cell references in a worksheet by a
// specific worksheet name.
func (f *File) GetPictureCells(sheet string) ([]string, error) {
f.mu.Lock()
ws, err := f.workSheetReader(sheet)
if err != nil {
Expand All @@ -521,7 +496,7 @@ func (f *File) GetAllPictures(sheet string) (map[string][]Picture, error) {
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")

return f.getAllPictures(drawingXML, drawingRelationships)
return f.getPictureCells(drawingXML, drawingRelationships)
}

// DeletePicture provides a function to delete all pictures in a cell by given
Expand Down Expand Up @@ -579,81 +554,93 @@ func (f *File) DeletePicture(sheet, cell string) error {
// embed in spreadsheet by given coordinates and drawing relationships.
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) {
var (
ok bool
deWsDr *decodeWsDr
deCellAnchor *decodeCellAnchor
drawRel *xlsxRelationship
wsDr *xlsxWsDr
deWsDr = new(decodeWsDr)
wsDr *xlsxWsDr
)

if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return
}
pics = f.getPicturesFromWsDr(row, col, drawingRelationships, wsDr)
deWsDr = new(decodeWsDr)
anchorCond := func(a *xdrCellAnchor) bool { return a.From.Col == col && a.From.Row == row }
anchorCb := func(a *xdrCellAnchor, r *xlsxRelationship) {
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
pics = append(pics, pic)
}
}
f.extractCellAnchor(drawingRelationships, wsDr, anchorCond, anchorCb)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
Decode(deWsDr); err != nil && err != io.EOF {
return
}
err = nil
extractAnchor := func(anchor *decodeCellAnchor) {
deCellAnchor = new(decodeCellAnchor)
if err := f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.Content + "</decodeCellAnchor>")).
Decode(deCellAnchor); err != nil && err != io.EOF {
return
}
if err = nil; deCellAnchor.From != nil && deCellAnchor.Pic != nil {
if deCellAnchor.From.Col == col && deCellAnchor.From.Row == row {
drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = deCellAnchor.Pic.NvPicPr.CNvPr.Descr
pics = append(pics, pic)
}
}
}
decodeAnchorCond := func(a *decodeCellAnchor) bool { return a.From.Col == col && a.From.Row == row }
decodeAnchorCb := func(a *decodeCellAnchor, r *xlsxRelationship) {
pic := Picture{Extension: filepath.Ext(r.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = a.Pic.NvPicPr.CNvPr.Descr
pics = append(pics, pic)
}
}
for _, anchor := range deWsDr.TwoCellAnchor {
extractAnchor(anchor)
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
for _, anchor := range deWsDr.OneCellAnchor {
extractAnchor(anchor)
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
return
}

// getPicturesFromWsDr provides a function to get picture base name and raw
// content in worksheet drawing by given coordinates and drawing
// relationships.
func (f *File) getPicturesFromWsDr(row, col int, drawingRelationships string, wsDr *xlsxWsDr) (pics []Picture) {
// extractCellAnchor extract drawing object from cell anchor by giving drawing
// cell anchor, drawing relationships part path, conditional and callback
// function.
func (f *File) extractCellAnchor(drawingRelationships string, wsDr *xlsxWsDr,
cond func(anchor *xdrCellAnchor) bool, cb func(anchor *xdrCellAnchor, rels *xlsxRelationship),
) {
var (
ok bool
anchor *xdrCellAnchor
drawRel *xlsxRelationship
)
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
for _, anchor = range wsDr.TwoCellAnchor {
if anchor.From != nil && anchor.Pic != nil {
if anchor.From.Col == col && anchor.From.Row == row {
if cond(anchor) {
if drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = anchor.Pic.NvPicPr.CNvPr.Descr
pics = append(pics, pic)
}
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
cb(anchor, drawRel)
}
}
}
}
}
return
}

// extractDecodeCellAnchor extract drawing object from cell anchor by giving
// decoded drawing cell anchor, drawing relationships part path, conditional and
// callback function.
func (f *File) extractDecodeCellAnchor(anchor *decodeCellAnchor, drawingRelationships string,
cond func(anchor *decodeCellAnchor) bool, cb func(anchor *decodeCellAnchor, rels *xlsxRelationship),
) {
var (
drawRel *xlsxRelationship
deCellAnchor = new(decodeCellAnchor)
)
if err := f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.Content + "</decodeCellAnchor>")).
Decode(deCellAnchor); err != nil && err != io.EOF {
return
}
if deCellAnchor.From != nil && deCellAnchor.Pic != nil {
if cond(deCellAnchor) {
drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
cb(deCellAnchor, drawRel)
}
}
}
}

// getDrawingRelationships provides a function to get drawing relationships
Expand Down Expand Up @@ -701,10 +688,7 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr
if inMergeCell {
continue
}
if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err != nil {
return
}
if inMergeCell {
if inMergeCell, err = f.checkCellInRangeRef(cell, mergeCell[0]); err == nil {
rng, _ = cellRefsToCoordinates(mergeCell.GetStartAxis(), mergeCell.GetEndAxis())
_ = sortCoordinates(rng)
}
Expand Down Expand Up @@ -732,81 +716,46 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr
return
}

// getAllPictures provides a function to get all pictures base name and raw content
// embed in spreadsheet by given drawing relationships, return a map[cellName][]Picture.
func (f *File) getAllPictures(drawingXML, drawingRelationships string) (pics map[string][]Picture, err error) {
// getPictureCells provides a function to get all picture cell references in a
// worksheet by given drawing part path and drawing relationships path.
func (f *File) getPictureCells(drawingXML, drawingRelationships string) ([]string, error) {
var (
ok bool
deWsDr *decodeWsDr
deCellAnchor *decodeCellAnchor
drawRel *xlsxRelationship
wsDr *xlsxWsDr
cells []string
err error
deWsDr *decodeWsDr
wsDr *xlsxWsDr
)

if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return
return cells, err
}
pics = f.getPicturesMapFromWsDr(drawingRelationships, wsDr)
anchorCond := func(a *xdrCellAnchor) bool { return true }
anchorCb := func(a *xdrCellAnchor, r *xlsxRelationship) {
if _, ok := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); ok {
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
cells = append(cells, cell)
}
}
}
f.extractCellAnchor(drawingRelationships, wsDr, anchorCond, anchorCb)
deWsDr = new(decodeWsDr)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
Decode(deWsDr); err != nil && err != io.EOF {
return
return cells, err
}
err = nil
extractAnchor := func(anchor *decodeCellAnchor) {
deCellAnchor = new(decodeCellAnchor)
if err := f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.Content + "</decodeCellAnchor>")).
Decode(deCellAnchor); err != nil && err != io.EOF {
return
}
if err = nil; deCellAnchor.From != nil && deCellAnchor.Pic != nil {
drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = deCellAnchor.Pic.NvPicPr.CNvPr.Descr
if cellName, err := CoordinatesToCellName(deCellAnchor.From.Col, deCellAnchor.From.Row); err == nil {
pics[cellName] = append(pics[cellName], pic)
}
}
decodeAnchorCond := func(a *decodeCellAnchor) bool { return true }
decodeAnchorCb := func(a *decodeCellAnchor, r *xlsxRelationship) {
if _, ok := f.Pkg.Load(strings.ReplaceAll(r.Target, "..", "xl")); ok {
if cell, err := CoordinatesToCellName(a.From.Col+1, a.From.Row+1); err == nil && inStrSlice(cells, cell, true) == -1 {
cells = append(cells, cell)
}
}
}
for _, anchor := range deWsDr.TwoCellAnchor {
extractAnchor(anchor)
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
for _, anchor := range deWsDr.OneCellAnchor {
extractAnchor(anchor)
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
return
}

func (f *File) getPicturesMapFromWsDr(drawingRelationships string, wsDr *xlsxWsDr) (pics map[string][]Picture) {
var (
ok bool
anchor *xdrCellAnchor
drawRel *xlsxRelationship
)
pics = make(map[string][]Picture)
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
for _, anchor = range wsDr.TwoCellAnchor {
if anchor.From != nil && anchor.Pic != nil {
if drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok = supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
pic := Picture{Extension: filepath.Ext(drawRel.Target), Format: &GraphicOptions{}}
if buffer, _ := f.Pkg.Load(strings.ReplaceAll(drawRel.Target, "..", "xl")); buffer != nil {
pic.File = buffer.([]byte)
pic.Format.AltText = anchor.Pic.NvPicPr.CNvPr.Descr
if cellName, err := CoordinatesToCellName(anchor.From.Col, anchor.From.Row); err == nil {
pics[cellName] = append(pics[cellName], pic)
}
}
}
}
}
}
return
return cells, err
}
52 changes: 50 additions & 2 deletions picture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ func TestAddPicture(t *testing.T) {
// Test add picture to worksheet from bytes with illegal cell reference
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", &Picture{Extension: ".png", File: file, Format: &GraphicOptions{AltText: "Excel Logo"}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())

for cell, ext := range map[string]string{"Q8": "gif", "Q15": "jpg", "Q22": "tif", "Q28": "bmp"} {
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", fmt.Sprintf("excel.%s", ext)), nil))
for _, preset := range [][]string{{"Q8", "gif"}, {"Q15", "jpg"}, {"Q22", "tif"}, {"Q28", "bmp"}} {
assert.NoError(t, f.AddPicture("Sheet1", preset[0], filepath.Join("test", "images", fmt.Sprintf("excel.%s", preset[1])), nil))
}

// Test write file to given path
Expand All @@ -77,6 +77,32 @@ func TestAddPicture(t *testing.T) {
pics, err := f.GetPictures("Sheet1", "A30")
assert.NoError(t, err)
assert.Len(t, pics, 2)

// Test get picture cells
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"A30", "F21", "B30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
assert.NoError(t, f.Close())

f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
assert.NoError(t, err)
path := "xl/drawings/drawing1.xml"
f.Drawings.Delete(path)
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"F21", "A30", "B30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
// Test get picture cells with unsupported charset
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())

f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
assert.NoError(t, err)
// Test get picture cells with unsupported charset
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())

// Test add picture with unsupported charset content types
Expand Down Expand Up @@ -185,6 +211,10 @@ func TestGetPicture(t *testing.T) {
pics, err = f.GetPictures("Sheet2", "K16")
assert.NoError(t, err)
assert.Len(t, pics, 1)
// Try to get picture cells with one cell anchor
cells, err := f.GetPictureCells("Sheet2")
assert.NoError(t, err)
assert.Equal(t, []string{"K16"}, cells)

// Test get picture from none drawing worksheet
f = NewFile()
Expand Down Expand Up @@ -344,3 +374,21 @@ func TestAddContentTypePart(t *testing.T) {
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.addContentTypePart(0, "unknown"), "XML syntax error on line 1: invalid UTF-8")
}

func TestGetPictureCells(t *testing.T) {
f := NewFile()
// Test get picture cells on a worksheet which not contains any pictures
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Empty(t, cells)
// Test get picture cells on not exists worksheet
_, err = f.GetPictureCells("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
}

func TestExtractDecodeCellAnchor(t *testing.T) {
f := NewFile()
cond := func(a *decodeCellAnchor) bool { return true }
cb := func(a *decodeCellAnchor, r *xlsxRelationship) {}
f.extractDecodeCellAnchor(&decodeCellAnchor{Content: string(MacintoshCyrillicCharset)}, "", cond, cb)
}

0 comments on commit 33d91d8

Please sign in to comment.