This closes #1218 and closes #1689 (#1634)

- Introduce new exported function GetPictureCells
- Upgrade dependencies module golang.org/x/net from 0.16.0 to 0.17.0
- Update unit tests
This commit is contained in:
壹次心 2023-10-13 00:06:07 +08:00 committed by GitHub
parent f752f2ddf4
commit 27f1056929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 51 deletions

2
go.mod
View File

@ -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
go.sum
View File

@ -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=

View File

@ -478,6 +478,27 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
return f.getPicture(row, col, drawingXML, drawingRelationships)
}
// 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 {
f.mu.Unlock()
return nil, err
}
f.mu.Unlock()
if ws.Drawing == nil {
return nil, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.ReplaceAll(target, "..", "xl")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
return f.getPictureCells(drawingXML, drawingRelationships)
}
// DeletePicture provides a function to delete all pictures in a cell by given
// worksheet name and cell reference.
func (f *File) DeletePicture(sheet, cell string) error {
@ -533,58 +554,52 @@ 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
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 {
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 = deCellAnchor.Pic.NvPicPr.CNvPr.Descr
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
)
@ -592,22 +607,40 @@ func (f *File) getPicturesFromWsDr(row, col int, drawingRelationships string, ws
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)
}
}
}
}
}
}
// 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
@ -655,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)
}
@ -685,3 +715,47 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr
w, h = int(width*opts.ScaleX), int(height*opts.ScaleY)
return
}
// 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 (
cells []string
err error
deWsDr *decodeWsDr
wsDr *xlsxWsDr
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return cells, err
}
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 cells, err
}
err = nil
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 {
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
for _, anchor := range deWsDr.OneCellAnchor {
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
}
return cells, err
}

View File

@ -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
@ -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
@ -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()
@ -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)
}