Support update volatile dependencies on inserting/deleting columns/rows

This commit is contained in:
xuri 2023-11-02 00:15:41 +08:00
parent b41a6cc3cd
commit 7291e787bd
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
9 changed files with 192 additions and 32 deletions

View File

@ -38,7 +38,7 @@ const (
// row: Index number of the row we're inserting/deleting before
// offset: Number of rows/column to insert/delete negative values indicate deletion
//
// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
// TODO: adjustComments, adjustDataValidations, adjustDrawings, adjustPageBreaks, adjustProtectedCells
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
@ -66,6 +66,9 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
return err
}
if err = f.adjustVolatileDeps(dir, num, offset, sheetID); err != nil {
return err
}
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
ws.MergeCells = nil
}
@ -498,7 +501,8 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
t.AutoFilter.Ref = t.Ref
}
_ = f.setTableColumns(sheet, true, x1, y1, x2, &t)
t.TotalsRowCount = 0
// Currently doesn't support query table
t.TableType, t.TotalsRowCount, t.ConnectionID = "", 0, 0
table, _ := xml.Marshal(t)
f.saveFileList(tableXML, table)
}
@ -578,7 +582,6 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
if dir == rows {
if y1 == num && y2 == num && offset < 0 {
f.deleteMergeCell(ws, i)
i--
continue
}
@ -586,7 +589,6 @@ func (f *File) adjustMergeCells(ws *xlsxWorksheet, dir adjustDirection, num, off
} else {
if x1 == num && x2 == num && offset < 0 {
f.deleteMergeCell(ws, i)
i--
continue
}
@ -642,18 +644,15 @@ func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
}
}
// adjustCalcChainRef update the cell reference in calculation chain when
// inserting or deleting rows or columns.
func (f *File) adjustCalcChainRef(i, c, r, offset int, dir adjustDirection) {
// adjustCellName returns updated cell name by giving column/row number and
// offset on inserting or deleting rows or columns.
func adjustCellName(cell string, dir adjustDirection, c, r, offset int) (string, error) {
if dir == rows {
if rn := r + offset; rn > 0 {
f.CalcChain.C[i].R, _ = CoordinatesToCellName(c, rn)
return CoordinatesToCellName(c, rn)
}
return
}
if nc := c + offset; nc > 0 {
f.CalcChain.C[i].R, _ = CoordinatesToCellName(nc, r)
}
return CoordinatesToCellName(c+offset, r)
}
// 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
// If sheet ID is omitted, it is assumed to be the same as the i value of
// the previous cell.
var prevSheetID int
for index, c := range f.CalcChain.C {
for i := 0; i < len(f.CalcChain.C); i++ {
c := f.CalcChain.C[i]
if c.I == 0 {
c.I = prevSheetID
}
@ -680,16 +680,72 @@ func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) er
if dir == rows && num <= rowNum {
if num == rowNum && offset == -1 {
_ = f.deleteCalcChain(c.I, c.R)
i--
continue
}
f.adjustCalcChainRef(index, colNum, rowNum, offset, dir)
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
}
if dir == columns && num <= colNum {
if num == colNum && offset == -1 {
_ = f.deleteCalcChain(c.I, c.R)
i--
continue
}
f.adjustCalcChainRef(index, colNum, rowNum, offset, dir)
f.CalcChain.C[i].R, _ = adjustCellName(c.R, dir, colNum, rowNum, offset)
}
}
return nil
}
// adjustVolatileDepsTopic updates the volatile dependencies topic when
// inserting or deleting rows or columns.
func (vt *xlsxVolTypes) adjustVolatileDepsTopic(cell string, dir adjustDirection, indexes []int) (int, error) {
num, offset, i1, i2, i3, i4 := indexes[0], indexes[1], indexes[2], indexes[3], indexes[4], indexes[5]
colNum, rowNum, err := CellNameToCoordinates(cell)
if err != nil {
return i4, err
}
if dir == rows && num <= rowNum {
if num == rowNum && offset == -1 {
vt.deleteVolTopicRef(i1, i2, i3, i4)
i4--
return i4, err
}
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
}
if dir == columns && num <= colNum {
if num == colNum && offset == -1 {
vt.deleteVolTopicRef(i1, i2, i3, i4)
i4--
return i4, err
}
if name, _ := adjustCellName(cell, dir, colNum, rowNum, offset); name != "" {
vt.VolType[i1].Main[i2].Tp[i3].Tr[i4].R, _ = adjustCellName(cell, dir, colNum, rowNum, offset)
}
}
return i4, err
}
// adjustVolatileDeps updates the volatile dependencies when inserting or
// deleting rows or columns.
func (f *File) adjustVolatileDeps(dir adjustDirection, num, offset, sheetID int) error {
volTypes, err := f.volatileDepsReader()
if err != nil || volTypes == nil {
return err
}
for i1 := 0; i1 < len(volTypes.VolType); i1++ {
for i2 := 0; i2 < len(volTypes.VolType[i1].Main); i2++ {
for i3 := 0; i3 < len(volTypes.VolType[i1].Main[i2].Tp); i3++ {
for i4 := 0; i4 < len(volTypes.VolType[i1].Main[i2].Tp[i3].Tr); i4++ {
ref := volTypes.VolType[i1].Main[i2].Tp[i3].Tr[i4]
if ref.S != sheetID {
continue
}
if i4, err = volTypes.adjustVolatileDepsTopic(ref.R, dir, []int{num, offset, i1, i2, i3, i4}); err != nil {
return err
}
}
}
}
}
return nil

View File

@ -927,3 +927,23 @@ func TestAdjustFormula(t *testing.T) {
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
})
}
func TestAdjustVolatileDeps(t *testing.T) {
f := NewFile()
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)))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
assert.Equal(t, "D3", f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr[1].R)
assert.NoError(t, f.RemoveCol("Sheet1", "D"))
assert.NoError(t, f.RemoveRow("Sheet1", 4))
assert.Len(t, f.VolatileDeps.VolType[0].Main[0].Tp[0].Tr, 1)
f = NewFile()
f.Pkg.Store(defaultXMLPathVolatileDeps, MacintoshCyrillicCharset)
assert.EqualError(t, f.InsertRows("Sheet1", 2, 1), "XML syntax error on line 1: invalid UTF-8")
f = NewFile()
f.Pkg.Store(defaultXMLPathVolatileDeps, []byte(fmt.Sprintf(`<volTypes xmlns="%s"><volType><main><tp><tr r="A" s="1"/></tp></main></volType></volTypes>`, NameSpaceSpreadSheet.Value)))
assert.Equal(t, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")), f.InsertCols("Sheet1", "A", 1))
f.volatileDepsWriter()
}

View File

@ -81,3 +81,39 @@ func (c xlsxCalcChainCollection) Filter(fn func(v xlsxCalcChainC) bool) []xlsxCa
}
return results
}
// volatileDepsReader provides a function to get the pointer to the structure
// after deserialization of xl/volatileDependencies.xml.
func (f *File) volatileDepsReader() (*xlsxVolTypes, error) {
if f.VolatileDeps == nil {
volatileDeps, ok := f.Pkg.Load(defaultXMLPathVolatileDeps)
if !ok {
return f.VolatileDeps, nil
}
f.VolatileDeps = new(xlsxVolTypes)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(volatileDeps.([]byte)))).
Decode(f.VolatileDeps); err != nil && err != io.EOF {
return f.VolatileDeps, err
}
}
return f.VolatileDeps, nil
}
// volatileDepsWriter provides a function to save xl/volatileDependencies.xml
// after serialize structure.
func (f *File) volatileDepsWriter() {
if f.VolatileDeps != nil {
output, _ := xml.Marshal(f.VolatileDeps)
f.saveFileList(defaultXMLPathVolatileDeps, output)
}
}
// deleteVolTopicRef provides a function to remove cell reference on the
// volatile dependencies topic.
func (vt *xlsxVolTypes) deleteVolTopicRef(i1, i2, i3, i4 int) {
for i := range vt.VolType[i1].Main[i2].Tp[i3].Tr {
if i == i4 {
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:]...)
}
}
}

View File

@ -29,31 +29,32 @@ import (
// File define a populated spreadsheet file struct.
type File struct {
mu sync.Mutex
options *Options
xmlAttr sync.Map
checked sync.Map
options *Options
sharedStringItem [][]uint
sharedStringsMap map[string]int
sharedStringTemp *os.File
sheetMap map[string]string
streams map[string]*StreamWriter
tempFiles sync.Map
sharedStringsMap map[string]int
sharedStringItem [][]uint
sharedStringTemp *os.File
xmlAttr sync.Map
CalcChain *xlsxCalcChain
CharsetReader charsetTranscoderFn
Comments map[string]*xlsxComments
ContentTypes *xlsxTypes
DecodeVMLDrawing map[string]*decodeVmlDrawing
Drawings sync.Map
Path string
Pkg sync.Map
Relationships sync.Map
SharedStrings *xlsxSST
Sheet sync.Map
SheetCount int
Styles *xlsxStyleSheet
Theme *decodeTheme
DecodeVMLDrawing map[string]*decodeVmlDrawing
VMLDrawing map[string]*vmlDrawing
VolatileDeps *xlsxVolTypes
WorkBook *xlsxWorkbook
Relationships sync.Map
Pkg sync.Map
CharsetReader charsetTranscoderFn
}
// charsetTranscoderFn set user-defined codepage transcoder function for open

View File

@ -176,6 +176,7 @@ func (f *File) writeToZip(zw *zip.Writer) error {
f.commentsWriter()
f.contentTypesWriter()
f.drawingsWriter()
f.volatileDepsWriter()
f.vmlDrawingWriter()
f.workBookWriter()
f.workSheetWriter()

View File

@ -292,7 +292,7 @@ func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int,
}
header = append(header, name)
if column := getTableColumn(name); column != nil {
column.ID, column.DataDxfID = idx, 0
column.ID, column.DataDxfID, column.QueryTableFieldID = idx, 0, 0
tableColumns = append(tableColumns, column)
continue
}

View File

@ -218,16 +218,17 @@ const (
)
const (
defaultTempFileSST = "sharedStrings"
defaultXMLPathCalcChain = "xl/calcChain.xml"
defaultXMLPathContentTypes = "[Content_Types].xml"
defaultXMLPathDocPropsApp = "docProps/app.xml"
defaultXMLPathDocPropsCore = "docProps/core.xml"
defaultXMLPathCalcChain = "xl/calcChain.xml"
defaultXMLPathSharedStrings = "xl/sharedStrings.xml"
defaultXMLPathStyles = "xl/styles.xml"
defaultXMLPathTheme = "xl/theme/theme1.xml"
defaultXMLPathVolatileDeps = "xl/volatileDependencies.xml"
defaultXMLPathWorkbook = "xl/workbook.xml"
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
defaultTempFileSST = "sharedStrings"
)
// IndexedColorMapping is the table of default mappings from indexed color value

View File

@ -11,10 +11,15 @@
package excelize
import "encoding/xml"
import (
"encoding/xml"
"sync"
)
// xlsxCalcChain directly maps the calcChain element. This element represents the root of the calculation chain.
// xlsxCalcChain directly maps the calcChain element. This element represents
// the root of the calculation chain.
type xlsxCalcChain struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main calcChain"`
C []xlsxCalcChainC `xml:"c"`
}
@ -82,3 +87,43 @@ type xlsxCalcChainC struct {
T bool `xml:"t,attr,omitempty"`
A bool `xml:"a,attr,omitempty"`
}
// xlsxVolTypes maps the volatileDependencies part provides a cache of data that
// supports Real Time Data (RTD) and CUBE functions in the workbook.
type xlsxVolTypes struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main volTypes"`
VolType []xlsxVolType `xml:"volType"`
ExtLst *xlsxExtLst `xml:"extLst"`
}
// xlsxVolType represents dependency information for a specific type of external
// data server.
type xlsxVolType struct {
Type string `xml:"type,attr"`
Main []xlsxVolMain `xml:"main"`
}
// xlsxVolMain represents dependency information for all topics within a
// volatile dependency type that share the same first string or function
// argument.
type xlsxVolMain struct {
First string `xml:"first,attr"`
Tp []xlsxVolTopic `xml:"tp"`
}
// xlsxVolTopic represents dependency information for all topics within a
// volatile dependency type that share the same first string or argument.
type xlsxVolTopic struct {
T string `xml:"t,attr,omitempty"`
V string `xml:"v"`
Stp []string `xml:"stp"`
Tr []xlsxVolTopicRef `xml:"tr"`
}
// xlsxVolTopicRef represents the reference to a cell that depends on this
// topic. Each topic can have one or more cells dependencies.
type xlsxVolTopicRef struct {
R string `xml:"r,attr"`
S int `xml:"s,attr"`
}

View File

@ -37,12 +37,12 @@ type xlsxTable struct {
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
TableBorderDxfId int `xml:"tableBorderDxfId,attr,omitempty"`
TotalsRowBorderDxfId int `xml:"totalsRowBorderDxfId,attr,omitempty"`
TableBorderDxfID int `xml:"tableBorderDxfId,attr,omitempty"`
TotalsRowBorderDxfID int `xml:"totalsRowBorderDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
ConnectionId int `xml:"connectionId,attr,omitempty"`
ConnectionID int `xml:"connectionId,attr,omitempty"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
TableColumns *xlsxTableColumns `xml:"tableColumns"`
TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"`