Skip to content

Commit 7291e78

Browse files
committed
Support update volatile dependencies on inserting/deleting columns/rows
1 parent b41a6cc commit 7291e78

9 files changed

+192
-32
lines changed

adjust.go

+71-15
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const (
3838
// row: Index number of the row we're inserting/deleting before
3939
// offset: Number of rows/column to insert/delete negative values indicate deletion
4040
//
41-
// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
41+
// TODO: adjustComments, adjustDataValidations, adjustDrawings, adjustPageBreaks, adjustProtectedCells
4242
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
4343
ws, err := f.workSheetReader(sheet)
4444
if err != nil {
@@ -66,6 +66,9 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
6666
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
6767
return err
6868
}
69+
if err = f.adjustVolatileDeps(dir, num, offset, sheetID); err != nil {
70+
return err
71+
}
6972
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
7073
ws.MergeCells = nil
7174
}
@@ -498,7 +501,8 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
498501
t.AutoFilter.Ref = t.Ref
499502
}
500503
_ = f.setTableColumns(sheet, true, x1, y1, x2, &t)
501-
t.TotalsRowCount = 0
504+
// Currently doesn't support query table
505+
t.TableType, t.TotalsRowCount, t.ConnectionID = "", 0, 0
502506
table, _ := xml.Marshal(t)
503507
f.saveFileList(tableXML, table)
504508
}
@@ -578,15 +582,13 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
578582
if dir == rows {
579583
if y1 == num && y2 == num && offset < 0 {
580584
f.deleteMergeCell(ws, i)
581-
i--
582585
continue
583586
}
584587

585588
y1, y2 = f.adjustMergeCellsHelper(y1, y2, num, offset)
586589
} else {
587590
if x1 == num && x2 == num && offset < 0 {
588591
f.deleteMergeCell(ws, i)
589-
i--
590592
continue
591593
}
592594

@@ -642,18 +644,15 @@ func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
642644
}
643645
}
644646

645-
// adjustCalcChainRef update the cell reference in calculation chain when
646-
// inserting or deleting rows or columns.
647-
func (f *File) adjustCalcChainRef(i, c, r, offset int, dir adjustDirection) {
647+
// adjustCellName returns updated cell name by giving column/row number and
648+
// offset on inserting or deleting rows or columns.
649+
func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string, error) {
648650
if dir == rows {
649651
if rn := r + offset; rn > 0 {
650-
f.CalcChain.C[i].R, _ = CoordinatesToCellName(c, rn)
652+
return CoordinatesToCellName(c, rn)
651653
}
652-
return
653-
}
654-
if nc := c + offset; nc > 0 {
655-
f.CalcChain.C[i].R, _ = CoordinatesToCellName(nc, r)
656654
}
655+
return CoordinatesToCellName(c+offset, r)
657656
}
658657

659658
// adjustCalcChain provides a function to update the calculation chain when
@@ -665,7 +664,8 @@ func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) er
665664
// If sheet ID is omitted, it is assumed to be the same as the i value of
666665
// the previous cell.
667666
var prevSheetID int
668-
for index, c := range f.CalcChain.C {
667+
for i := 0; i < len(f.CalcChain.C); i++ {
668+
c := f.CalcChain.C[i]
669669
if c.I == 0 {
670670
c.I = prevSheetID
671671
}
@@ -680,16 +680,72 @@ func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) er
680680
if dir == rows && num <= rowNum {
681681
if num == rowNum && offset == -1 {
682682
_ = f.deleteCalcChain(c.I, c.R)
683+
i--
683684
continue
684685
}
685-
f.adjustCalcChainRef(index, colNum, rowNum, offset, dir)
686+
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
686687
}
687688
if dir == columns && num <= colNum {
688689
if num == colNum && offset == -1 {
689690
_ = f.deleteCalcChain(c.I, c.R)
691+
i--
690692
continue
691693
}
692-
f.adjustCalcChainRef(index, colNum, rowNum, offset, dir)
694+
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
695+
}
696+
}
697+
return nil
698+
}
699+
700+
// adjustVolatileDepsTopic updates the volatile dependencies topic when
701+
// inserting or deleting rows or columns.
702+
func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection, indexes []int) (int, error) {
703+
num, offset, i1, i2, i3, i4 := indexes[0], indexes[1], indexes[2], indexes[3], indexes[4], indexes[5]
704+
colNum, rowNum, err := CellNameToCoordinates(cell)
705+
if err != nil {
706+
return i4, err
707+
}
708+
if dir == rows && num <= rowNum {
709+
if num == rowNum && offset == -1 {
710+
vt.deleteVolTopicRef(i1, i2, i3, i4)
711+
i4--
712+
return i4, err
713+
}
714+
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
715+
}
716+
if dir == columns && num <= colNum {
717+
if num == colNum && offset == -1 {
718+
vt.deleteVolTopicRef(i1, i2, i3, i4)
719+
i4--
720+
return i4, err
721+
}
722+
if name, _ := adjustCellName(cell, dir, colNum, rowNum, offset); name != "" {
723+
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
724+
}
725+
}
726+
return i4, err
727+
}
728+
729+
// adjustVolatileDeps updates the volatile dependencies when inserting or
730+
// deleting rows or columns.
731+
func (f *File) adjustVolatileDeps(dir adjustDirection, num, offset, sheetID int) error {
732+
volTypes, err := f.volatileDepsReader()
733+
if err != nil || volTypes == nil {
734+
return err
735+
}
736+
for i1 := 0; i1 < len(volTypes.VolType); i1++ {
737+
for i2 := 0; i2 < len(volTypes.VolType[i1].Main); i2++ {
738+
for i3 := 0; i3 < len(volTypes.VolType[i1].Main[i2].Tp); i3++ {
739+
for i4 := 0; i4 < len(volTypes.VolType[i1].Main[i2].Tp[i3].Tr); i4++ {
740+
ref := volTypes.VolType[i1].Main[i2].Tp[i3].Tr[i4]
741+
if ref.S != sheetID {
742+
continue
743+
}
744+
if i4, err = volTypes.adjustVolatileDepsTopic(ref.R, dir, []int{num, offset, i1, i2, i3, i4}); err != nil {
745+
return err
746+
}
747+
}
748+
}
693749
}
694750
}
695751
return nil

adjust_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -927,3 +927,23 @@ func TestAdjustFormula(t *testing.T) {
927927
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
928928
})
929929
}
930+
931+
func TestAdjustVolatileDeps(t *testing.T) {
932+
f := NewFile()
933+
f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`<volTypes xmlns="%s"><volType><main><tp><tr r="C2" s="2"/><tr r="C2" s="1"/><tr r="D3" s="1"/></tp></main></volType></volTypes>`, NameSpaceSpreadSheet.Value)))
934+
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
935+
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
936+
assert.Equal(t, "D3", f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr[1].R)
937+
assert.NoError(t, f.RemoveCol("Sheet1", "D"))
938+
assert.NoError(t, f.RemoveRow("Sheet1", 4))
939+
assert.Len(t, f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr, 1)
940+
941+
f = NewFile()
942+
f.Pkg.Store(defaultXMLPathVolatileDeps, MacintoshCyrillicCharset)
943+
assert.EqualError(t, f.InsertRows("Sheet1", 2, 1), "XML syntax error on line 1: invalid UTF-8")
944+
945+
f = NewFile()
946+
f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`<volTypes xmlns="%s"><volType><main><tp><tr r="A" s="1"/></tp></main></volType></volTypes>`, NameSpaceSpreadSheet.Value)))
947+
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.InsertCols("Sheet1", "A", 1))
948+
f.volatileDepsWriter()
949+
}

calcchain.go

+36
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,39 @@ func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCa
8181
}
8282
return results
8383
}
84+
85+
// volatileDepsReader provides a function to get the pointer to the structure
86+
// after deserialization of xl/volatileDependencies.xml.
87+
func (f *File) volatileDepsReader() (*xlsxVolTypes, error) {
88+
if f.VolatileDeps == nil {
89+
volatileDeps, ok := f.Pkg.Load(defaultXMLPathVolatileDeps)
90+
if !ok {
91+
return f.VolatileDeps, nil
92+
}
93+
f.VolatileDeps = new(xlsxVolTypes)
94+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(volatileDeps.([]byte)))).
95+
Decode(f.VolatileDeps); err != nil && err != io.EOF {
96+
return f.VolatileDeps, err
97+
}
98+
}
99+
return f.VolatileDeps, nil
100+
}
101+
102+
// volatileDepsWriter provides a function to save xl/volatileDependencies.xml
103+
// after serialize structure.
104+
func (f *File) volatileDepsWriter() {
105+
if f.VolatileDeps != nil {
106+
output, _ := xml.Marshal(f.VolatileDeps)
107+
f.saveFileList(defaultXMLPathVolatileDeps, output)
108+
}
109+
}
110+
111+
// deleteVolTopicRef provides a function to remove cell reference on the
112+
// volatile dependencies topic.
113+
func (vt *xlsxVolTypes) deleteVolTopicRef(i1, i2, i3, i4 int) {
114+
for i := range vt.VolType[i1].Main[i2].Tp[i3].Tr {
115+
if i == i4 {
116+
vt.VolType[i1].Main[i2].Tp[i3].Tr = append(vt.VolType[i1].Main[i2].Tp[i3].Tr[:i], vt.VolType[i1].Main[i2].Tp[i3].Tr[i+1:]...)
117+
}
118+
}
119+
}

excelize.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -29,31 +29,32 @@ import (
2929
// File define a populated spreadsheet file struct.
3030
type File struct {
3131
mu sync.Mutex
32-
options *Options
33-
xmlAttr sync.Map
3432
checked sync.Map
33+
options *Options
34+
sharedStringItem [][]uint
35+
sharedStringsMap map[string]int
36+
sharedStringTemp *os.File
3537
sheetMap map[string]string
3638
streams map[string]*StreamWriter
3739
tempFiles sync.Map
38-
sharedStringsMap map[string]int
39-
sharedStringItem [][]uint
40-
sharedStringTemp *os.File
40+
xmlAttr sync.Map
4141
CalcChain *xlsxCalcChain
42+
CharsetReader charsetTranscoderFn
4243
Comments map[string]*xlsxComments
4344
ContentTypes *xlsxTypes
45+
DecodeVMLDrawing map[string]*decodeVmlDrawing
4446
Drawings sync.Map
4547
Path string
48+
Pkg sync.Map
49+
Relationships sync.Map
4650
SharedStrings *xlsxSST
4751
Sheet sync.Map
4852
SheetCount int
4953
Styles *xlsxStyleSheet
5054
Theme *decodeTheme
51-
DecodeVMLDrawing map[string]*decodeVmlDrawing
5255
VMLDrawing map[string]*vmlDrawing
56+
VolatileDeps *xlsxVolTypes
5357
WorkBook *xlsxWorkbook
54-
Relationships sync.Map
55-
Pkg sync.Map
56-
CharsetReader charsetTranscoderFn
5758
}
5859

5960
// charsetTranscoderFn set user-defined codepage transcoder function for open

file.go

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
176176
f.commentsWriter()
177177
f.contentTypesWriter()
178178
f.drawingsWriter()
179+
f.volatileDepsWriter()
179180
f.vmlDrawingWriter()
180181
f.workBookWriter()
181182
f.workSheetWriter()

table.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int,
292292
}
293293
header = append(header, name)
294294
if column := getTableColumn(name); column != nil {
295-
column.ID, column.DataDxfID = idx, 0
295+
column.ID, column.DataDxfID, column.QueryTableFieldID = idx, 0, 0
296296
tableColumns = append(tableColumns, column)
297297
continue
298298
}

templates.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,17 @@ const (
218218
)
219219

220220
const (
221+
defaultTempFileSST = "sharedStrings"
222+
defaultXMLPathCalcChain = "xl/calcChain.xml"
221223
defaultXMLPathContentTypes = "[Content_Types].xml"
222224
defaultXMLPathDocPropsApp = "docProps/app.xml"
223225
defaultXMLPathDocPropsCore = "docProps/core.xml"
224-
defaultXMLPathCalcChain = "xl/calcChain.xml"
225226
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
226227
defaultXMLPathStyles = "xl/styles.xml"
227228
defaultXMLPathTheme = "xl/theme/theme1.xml"
229+
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
228230
defaultXMLPathWorkbook = "xl/workbook.xml"
229231
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
230-
defaultTempFileSST = "sharedStrings"
231232
)
232233

233234
// IndexedColorMapping is the table of default mappings from indexed color value

xmlCalcChain.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@
1111

1212
package excelize
1313

14-
import "encoding/xml"
14+
import (
15+
"encoding/xml"
16+
"sync"
17+
)
1518

16-
// xlsxCalcChain directly maps the calcChain element. This element represents the root of the calculation chain.
19+
// xlsxCalcChain directly maps the calcChain element. This element represents
20+
// the root of the calculation chain.
1721
type xlsxCalcChain struct {
22+
mu sync.Mutex
1823
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"`
1924
C []xlsxCalcChainC `xml:"c"`
2025
}
@@ -82,3 +87,43 @@ type xlsxCalcChainC struct {
8287
T bool `xml:"t,attr,omitempty"`
8388
A bool `xml:"a,attr,omitempty"`
8489
}
90+
91+
// xlsxVolTypes maps the volatileDependencies part provides a cache of data that
92+
// supports Real Time Data (RTD) and CUBE functions in the workbook.
93+
type xlsxVolTypes struct {
94+
mu sync.Mutex
95+
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main volTypes"`
96+
VolType []xlsxVolType `xml:"volType"`
97+
ExtLst *xlsxExtLst `xml:"extLst"`
98+
}
99+
100+
// xlsxVolType represents dependency information for a specific type of external
101+
// data server.
102+
type xlsxVolType struct {
103+
Type string `xml:"type,attr"`
104+
Main []xlsxVolMain `xml:"main"`
105+
}
106+
107+
// xlsxVolMain represents dependency information for all topics within a
108+
// volatile dependency type that share the same first string or function
109+
// argument.
110+
type xlsxVolMain struct {
111+
First string `xml:"first,attr"`
112+
Tp []xlsxVolTopic `xml:"tp"`
113+
}
114+
115+
// xlsxVolTopic represents dependency information for all topics within a
116+
// volatile dependency type that share the same first string or argument.
117+
type xlsxVolTopic struct {
118+
T string `xml:"t,attr,omitempty"`
119+
V string `xml:"v"`
120+
Stp []string `xml:"stp"`
121+
Tr []xlsxVolTopicRef `xml:"tr"`
122+
}
123+
124+
// xlsxVolTopicRef represents the reference to a cell that depends on this
125+
// topic. Each topic can have one or more cells dependencies.
126+
type xlsxVolTopicRef struct {
127+
R string `xml:"r,attr"`
128+
S int `xml:"s,attr"`
129+
}

xmlTable.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ type xlsxTable struct {
3737
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
3838
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
3939
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
40-
TableBorderDxfId int `xml:"tableBorderDxfId,attr,omitempty"`
41-
TotalsRowBorderDxfId int `xml:"totalsRowBorderDxfId,attr,omitempty"`
40+
TableBorderDxfID int `xml:"tableBorderDxfId,attr,omitempty"`
41+
TotalsRowBorderDxfID int `xml:"totalsRowBorderDxfId,attr,omitempty"`
4242
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
4343
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
4444
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
45-
ConnectionId int `xml:"connectionId,attr,omitempty"`
45+
ConnectionID int `xml:"connectionId,attr,omitempty"`
4646
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
4747
TableColumns *xlsxTableColumns `xml:"tableColumns"`
4848
TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"`

0 commit comments

Comments
 (0)