This closes #1125, support update drawing objects on inserting/deleting columns/rows (#1127)

This commit is contained in:
Anton Petrov 2023-11-06 04:51:19 +03:00 committed by GitHub
parent f753e560fa
commit fe639faa45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 335 additions and 100 deletions

176
adjust.go
View File

@ -29,6 +29,28 @@ const (
rows adjustDirection = true
)
// adjustHelperFunc defines functions to adjust helper.
var adjustHelperFunc = [6]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustTable(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustAutoFilter(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDrawings(ws, sheet, dir, num, offset, sheetID)
},
}
// adjustHelper provides a function to adjust rows and columns dimensions,
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
// columns.
@ -56,23 +78,14 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
f.adjustHyperlinks(ws, sheet, dir, num, offset)
ws.checkSheet()
_ = ws.checkRow()
f.adjustTable(ws, sheet, dir, num, offset)
if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
return err
}
if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil {
return err
}
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
return err
}
if err = f.adjustVolatileDeps(dir, num, offset, sheetID); err != nil {
return err
for _, fn := range adjustHelperFunc {
if err := fn(f, ws, sheet, dir, num, offset, sheetID); err != nil {
return err
}
}
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
ws.MergeCells = nil
}
return nil
}
@ -460,9 +473,9 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
// adjustTable provides a function to update the table when inserting or
// deleting rows or columns.
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
return
return nil
}
for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
tbl := ws.TableParts.TableParts[idx]
@ -475,11 +488,11 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
t := xlsxTable{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return
return nil
}
coordinates, err := rangeRefToCoordinates(t.Ref)
if err != nil {
return
return err
}
// Remove the table when deleting the header row of the table
if dir == rows && num == coordinates[0] && offset == -1 {
@ -506,11 +519,12 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
table, _ := xml.Marshal(t)
f.saveFileList(tableXML, table)
}
return nil
}
// adjustAutoFilter provides a function to update the auto filter when
// inserting or deleting rows or columns.
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.AutoFilter == nil {
return nil
}
@ -563,7 +577,7 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu
// adjustMergeCells provides a function to update merged cells when inserting
// or deleting rows or columns.
func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
func (f *File) adjustMergeCells(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.MergeCells == nil {
return nil
}
@ -657,7 +671,7 @@ func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string,
// adjustCalcChain provides a function to update the calculation chain when
// inserting or deleting rows or columns.
func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error {
func (f *File) adjustCalcChain(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if f.CalcChain == nil {
return nil
}
@ -728,7 +742,7 @@ func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection
// adjustVolatileDeps updates the volatile dependencies when inserting or
// deleting rows or columns.
func (f *File) adjustVolatileDeps(dir adjustDirection, num, offset, sheetID int) error {
func (f *File) adjustVolatileDeps(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
volTypes, err := f.volatileDepsReader()
if err != nil || volTypes == nil {
return err
@ -750,3 +764,123 @@ func (f *File) adjustVolatileDeps(dir adjustDirection, num, offset, sheetID int)
}
return nil
}
// adjustDrawings updates the starting anchor of the two cell anchor pictures
// and charts object when inserting or deleting rows or columns.
func (from *xlsxFrom) adjustDrawings(dir adjustDirection, num, offset int, editAs string) (bool, error) {
var ok bool
if dir == columns && from.Col+1 >= num && from.Col+offset >= 0 {
if from.Col+offset >= MaxColumns {
return false, ErrColumnNumber
}
from.Col += offset
ok = editAs == "oneCell"
}
if dir == rows && from.Row+1 >= num && from.Row+offset >= 0 {
if from.Row+offset >= TotalRows {
return false, ErrMaxRows
}
from.Row += offset
ok = editAs == "oneCell"
}
return ok, nil
}
// adjustDrawings updates the ending anchor of the two cell anchor pictures
// and charts object when inserting or deleting rows or columns.
func (to *xlsxTo) adjustDrawings(dir adjustDirection, num, offset int, editAs string, ok bool) error {
if dir == columns && to.Col+1 >= num && to.Col+offset >= 0 && ok {
if to.Col+offset >= MaxColumns {
return ErrColumnNumber
}
to.Col += offset
}
if dir == rows && to.Row+1 >= num && to.Row+offset >= 0 && ok {
if to.Row+offset >= TotalRows {
return ErrMaxRows
}
to.Row += offset
}
return nil
}
// adjustDrawings updates the two cell anchor pictures and charts object when
// inserting or deleting rows or columns.
func (a *xdrCellAnchor) adjustDrawings(dir adjustDirection, num, offset int) error {
editAs := a.EditAs
if a.From == nil || a.To == nil || editAs == "absolute" {
return nil
}
ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
if err != nil {
return err
}
return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "")
}
// adjustDrawings updates the existing two cell anchor pictures and charts
// object when inserting or deleting rows or columns.
func (a *xlsxCellAnchorPos) adjustDrawings(dir adjustDirection, num, offset int, editAs string) error {
if a.From == nil || a.To == nil || editAs == "absolute" {
return nil
}
ok, err := a.From.adjustDrawings(dir, num, offset, editAs)
if err != nil {
return err
}
return a.To.adjustDrawings(dir, num, offset, editAs, ok || editAs == "")
}
// adjustDrawings updates the pictures and charts object when inserting or
// deleting rows or columns.
func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
if ws.Drawing == nil {
return nil
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
var (
err error
wsDr *xlsxWsDr
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return err
}
anchorCb := func(a *xdrCellAnchor) error {
if a.GraphicFrame == "" {
return a.adjustDrawings(dir, num, offset)
}
deCellAnchor := decodeCellAnchor{}
deCellAnchorPos := decodeCellAnchorPos{}
_ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + a.GraphicFrame + "</decodeCellAnchor>")).Decode(&deCellAnchor)
_ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchorPos>" + a.GraphicFrame + "</decodeCellAnchorPos>")).Decode(&deCellAnchorPos)
xlsxCellAnchorPos := xlsxCellAnchorPos(deCellAnchorPos)
for i := 0; i < len(xlsxCellAnchorPos.AlternateContent); i++ {
xlsxCellAnchorPos.AlternateContent[i].XMLNSMC = SourceRelationshipCompatibility.Value
}
if deCellAnchor.From != nil {
xlsxCellAnchorPos.From = &xlsxFrom{
Col: deCellAnchor.From.Col, ColOff: deCellAnchor.From.ColOff,
Row: deCellAnchor.From.Row, RowOff: deCellAnchor.From.RowOff,
}
}
if deCellAnchor.To != nil {
xlsxCellAnchorPos.To = &xlsxTo{
Col: deCellAnchor.To.Col, ColOff: deCellAnchor.To.ColOff,
Row: deCellAnchor.To.Row, RowOff: deCellAnchor.To.RowOff,
}
}
if err = xlsxCellAnchorPos.adjustDrawings(dir, num, offset, a.EditAs); err != nil {
return err
}
cellAnchor, _ := xml.Marshal(xlsxCellAnchorPos)
a.GraphicFrame = strings.TrimSuffix(strings.TrimPrefix(string(cellAnchor), "<xlsxCellAnchorPos>"), "</xlsxCellAnchorPos>")
return err
}
for _, anchor := range wsDr.TwoCellAnchor {
if err = anchorCb(anchor); err != nil {
return err
}
}
return nil
}

View File

@ -1,10 +1,13 @@
package excelize
import (
"encoding/xml"
"fmt"
"path/filepath"
"testing"
_ "image/jpeg"
"github.com/stretchr/testify/assert"
)
@ -19,7 +22,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
},
}, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -28,7 +31,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
},
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -37,7 +40,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
},
}, rows, 1, -1))
}, "Sheet1", rows, 1, -1, 1))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -46,7 +49,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
},
}, columns, 1, -1))
}, "Sheet1", columns, 1, -1, 1))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -55,7 +58,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
},
},
}, columns, 1, -1))
}, "Sheet1", columns, 1, -1, 1))
// Test adjust merge cells
var cases []struct {
@ -134,7 +137,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, 1))
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, 1, 1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label)
}
@ -223,7 +226,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1))
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1))
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
}
@ -271,7 +274,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
}
for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1))
assert.NoError(t, f.adjustMergeCells(c.ws, "Sheet1", c.dir, c.num, -1, 1))
assert.Len(t, c.ws.MergeCells.Cells, 0, c.label)
}
@ -291,18 +294,18 @@ func TestAdjustAutoFilter(t *testing.T) {
AutoFilter: &xlsxAutoFilter{
Ref: "A1:A3",
},
}, rows, 1, -1))
}, "Sheet1", rows, 1, -1, 1))
// Test adjustAutoFilter with illegal cell reference
assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A:B1",
},
}, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A1:B",
},
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
}, "Sheet1", rows, 0, 0, 1), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
}
func TestAdjustTable(t *testing.T) {
@ -334,7 +337,7 @@ func TestAdjustTable(t *testing.T) {
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with invalid table range reference
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.NoError(t, f.RemoveRow(sheetName, 1))
assert.Equal(t, ErrParameterInvalid, f.RemoveRow(sheetName, 1))
}
func TestAdjustHelper(t *testing.T) {
@ -947,3 +950,81 @@ func TestAdjustVolatileDeps(t *testing.T) {
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.InsertCols("Sheet1", "A", 1))
f.volatileDepsWriter()
}
func TestAdjustDrawings(t *testing.T) {
f := NewFile()
// Test add pictures to sheet with positioning
assert.NoError(t, f.AddPicture("Sheet1", "B2", filepath.Join("test", "images", "excel.jpg"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "B11", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "oneCell"}))
assert.NoError(t, f.AddPicture("Sheet1", "B21", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{Positioning: "absolute"}))
// Test adjust pictures on inserting columns and rows
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet1", "C", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
assert.NoError(t, f.InsertRows("Sheet1", 15, 1))
cells, err := f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"D3", "D13", "B21"}, cells)
wb := filepath.Join("test", "TestAdjustDrawings.xlsx")
assert.NoError(t, f.SaveAs(wb))
// Test adjust pictures on deleting columns and rows
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
assert.NoError(t, f.RemoveRow("Sheet1", 1))
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"C2", "C12", "B21"}, cells)
// Test adjust existing pictures on inserting columns and rows
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet1", "D", 1))
assert.NoError(t, f.InsertRows("Sheet1", 5, 1))
assert.NoError(t, f.InsertRows("Sheet1", 16, 1))
cells, err = f.GetPictureCells("Sheet1")
assert.NoError(t, err)
assert.Equal(t, []string{"F4", "F15", "B21"}, cells)
// Test adjust drawings with unsupported charset
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), "XML syntax error on line 1: invalid UTF-8")
errors := []error{ErrColumnNumber, ErrColumnNumber, ErrMaxRows, ErrMaxRows}
cells = []string{"XFD1", "XFB1"}
for i, cell := range cells {
f = NewFile()
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil))
assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.SaveAs(wb))
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.Equal(t, errors[i], f.InsertCols("Sheet1", "A", 1))
}
errors = []error{ErrMaxRows, ErrMaxRows}
cells = []string{"A1048576", "A1048570"}
for i, cell := range cells {
f = NewFile()
assert.NoError(t, f.AddPicture("Sheet1", cell, filepath.Join("test", "images", "excel.jpg"), nil))
assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.SaveAs(wb))
f, err = OpenFile(wb)
assert.NoError(t, err)
assert.Equal(t, errors[i], f.InsertRows("Sheet1", 1, 1))
}
a := xdrCellAnchor{}
assert.NoError(t, a.adjustDrawings(columns, 0, 0))
p := xlsxCellAnchorPos{}
assert.NoError(t, p.adjustDrawings(columns, 0, 0, ""))
f, err = OpenFile(wb)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`<wsDr xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><twoCellAnchor><from><col>0</col><colOff>0</colOff><row>0</row><rowOff>0</rowOff></from><to><col>1</col><colOff>0</colOff><row>1</row><rowOff>0</rowOff></to><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"></mc:AlternateContent><clientData/></twoCellAnchor></wsDr>`))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
}

View File

@ -15,7 +15,6 @@ import (
"bytes"
"encoding/xml"
"image"
"io"
"os"
"path"
"path/filepath"
@ -471,7 +470,7 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
return nil, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.ReplaceAll(target, "..", "xl")
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
@ -492,7 +491,7 @@ func (f *File) GetPictureCells(sheet string) ([]string, error) {
return nil, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.ReplaceAll(target, "..", "xl")
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
drawingRelationships := strings.ReplaceAll(
strings.ReplaceAll(target, "../drawings", "xl/drawings/_rels"), ".xml", ".xml.rels")
@ -553,15 +552,15 @@ func (f *File) DeletePicture(sheet, cell string) error {
// getPicture provides a function to get picture base name and raw content
// embed in spreadsheet by given coordinates and drawing relationships.
func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) (pics []Picture, err error) {
var (
deWsDr = new(decodeWsDr)
wsDr *xlsxWsDr
)
var wsDr *xlsxWsDr
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return
}
anchorCond := func(a *xdrCellAnchor) bool { return a.From.Col == col && a.From.Row == row }
anchorCb := func(a *xdrCellAnchor, r *xlsxRelationship) {
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
cond := func(from *xlsxFrom) bool { return from.Col == col && from.Row == row }
cond2 := func(from *decodeFrom) bool { return from.Col == col && from.Row == row }
cb := 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)
@ -569,14 +568,7 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
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
decodeAnchorCond := func(a *decodeCellAnchor) bool { return a.From.Col == col && a.From.Row == row }
decodeAnchorCb := func(a *decodeCellAnchor, r *xlsxRelationship) {
cb2 := 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)
@ -584,11 +576,11 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
pics = append(pics, pic)
}
}
for _, anchor := range deWsDr.TwoCellAnchor {
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
for _, anchor := range wsDr.TwoCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
for _, anchor := range deWsDr.OneCellAnchor {
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
for _, anchor := range wsDr.OneCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
return
}
@ -596,18 +588,14 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
// 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),
func (f *File) extractCellAnchor(anchor *xdrCellAnchor, drawingRelationships string,
cond func(from *xlsxFrom) bool, cb func(anchor *xdrCellAnchor, rels *xlsxRelationship),
cond2 func(from *decodeFrom) bool, cb2 func(anchor *decodeCellAnchor, rels *xlsxRelationship),
) {
var (
anchor *xdrCellAnchor
drawRel *xlsxRelationship
)
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
for _, anchor = range wsDr.TwoCellAnchor {
var drawRel *xlsxRelationship
if anchor.GraphicFrame == "" {
if anchor.From != nil && anchor.Pic != nil {
if cond(anchor) {
if cond(anchor.From) {
if drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
@ -616,25 +604,24 @@ func (f *File) extractCellAnchor(drawingRelationships string, wsDr *xlsxWsDr,
}
}
}
return
}
f.extractDecodeCellAnchor(anchor, drawingRelationships, cond2, cb2)
}
// 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),
func (f *File) extractDecodeCellAnchor(anchor *xdrCellAnchor, drawingRelationships string,
cond func(from *decodeFrom) 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
}
_ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.GraphicFrame + "</decodeCellAnchor>")).Decode(&deCellAnchor)
if deCellAnchor.From != nil && deCellAnchor.Pic != nil {
if cond(deCellAnchor) {
if cond(deCellAnchor.From) {
drawRel = f.getDrawingRelationships(drawingRelationships, deCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok := supportedImageTypes[strings.ToLower(filepath.Ext(drawRel.Target))]; ok {
cb(deCellAnchor, drawRel)
@ -720,42 +707,36 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *Gr
// 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
cells []string
err error
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) {
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
cond := func(from *xlsxFrom) bool { return true }
cond2 := func(from *decodeFrom) bool { return true }
cb := 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) {
cb2 := 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 wsDr.TwoCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
for _, anchor := range deWsDr.OneCellAnchor {
f.extractDecodeCellAnchor(anchor, drawingRelationships, decodeAnchorCond, decodeAnchorCb)
for _, anchor := range wsDr.OneCellAnchor {
f.extractCellAnchor(anchor, drawingRelationships, cond, cb, cond2, cb2)
}
return cells, err
}

View File

@ -81,7 +81,7 @@ func TestAddPicture(t *testing.T) {
// 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.Equal(t, []string{"F21", "A30", "B30", "Q1", "Q8", "Q15", "Q22", "Q28"}, cells)
assert.NoError(t, f.Close())
f, err = OpenFile(filepath.Join("test", "TestAddPicture1.xlsx"))
@ -92,6 +92,7 @@ func TestAddPicture(t *testing.T) {
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.Drawings.Delete(path)
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.GetPictureCells("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
@ -226,6 +227,7 @@ func TestGetPicture(t *testing.T) {
// Test get pictures with unsupported charset
path := "xl/drawings/drawing1.xml"
f.Drawings.Delete(path)
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
@ -388,7 +390,7 @@ func TestGetPictureCells(t *testing.T) {
func TestExtractDecodeCellAnchor(t *testing.T) {
f := NewFile()
cond := func(a *decodeCellAnchor) bool { return true }
cond := func(a *decodeFrom) bool { return true }
cb := func(a *decodeCellAnchor, r *xlsxRelationship) {}
f.extractDecodeCellAnchor(&decodeCellAnchor{Content: string(MacintoshCyrillicCharset)}, "", cond, cb)
f.extractDecodeCellAnchor(&xdrCellAnchor{GraphicFrame: string(MacintoshCyrillicCharset)}, "", cond, cb)
}

View File

@ -18,13 +18,32 @@ import "encoding/xml"
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
// element. It moves with cells and its extents are in EMU units.
type decodeCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
Content string `xml:",innerxml"`
EditAs string `xml:"editAs,attr,omitempty"`
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
Content string `xml:",innerxml"`
}
// decodeCellAnchorPos defines the structure used to deserialize the cell anchor
// for adjust drawing object on inserting/deleting column/rows.
type decodeCellAnchorPos struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *xlsxFrom `xml:"from"`
To *xlsxTo `xml:"to"`
Pos *xlsxInnerXML `xml:"pos"`
Ext *xlsxInnerXML `xml:"ext"`
Sp *xlsxInnerXML `xml:"sp"`
GrpSp *xlsxInnerXML `xml:"grpSp"`
GraphicFrame *xlsxInnerXML `xml:"graphicFrame"`
CxnSp *xlsxInnerXML `xml:"cxnSp"`
Pic *xlsxInnerXML `xml:"pic"`
ContentPart *xlsxInnerXML `xml:"contentPart"`
AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
ClientData *xlsxInnerXML `xml:"clientData"`
}
// xdrSp (Shape) directly maps the sp element. This element specifies the

View File

@ -230,6 +230,24 @@ type xdrCellAnchor struct {
ClientData *xdrClientData `xml:"xdr:clientData"`
}
// xlsxCellAnchorPos defines the structure used to serialize the cell anchor for
// adjust drawing object on inserting/deleting column/rows.
type xlsxCellAnchorPos struct {
EditAs string `xml:"editAs,attr,omitempty"`
From *xlsxFrom `xml:"xdr:from"`
To *xlsxTo `xml:"xdr:to"`
Pos *xlsxInnerXML `xml:"xdr:pos"`
Ext *xlsxInnerXML `xml:"xdr:ext"`
Sp *xlsxInnerXML `xml:"xdr:sp"`
GrpSp *xlsxInnerXML `xml:"xdr:grpSp"`
GraphicFrame *xlsxInnerXML `xml:"xdr:graphicFrame"`
CxnSp *xlsxInnerXML `xml:"xdr:cxnSp"`
Pic *xlsxInnerXML `xml:"xdr:pic"`
ContentPart *xlsxInnerXML `xml:"xdr:contentPart"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
ClientData *xlsxInnerXML `xml:"xdr:clientData"`
}
// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
type xlsxPoint2D struct {
XMLName xml.Name `xml:"xdr:pos"`