Support update volatile dependencies on inserting/deleting columns/rows
This commit is contained in:
parent
b41a6cc3cd
commit
7291e787bd
86
adjust.go
86
adjust.go
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
36
calcchain.go
36
calcchain.go
|
@ -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:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
19
excelize.go
19
excelize.go
|
@ -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
|
||||
|
|
1
file.go
1
file.go
|
@ -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()
|
||||
|
|
2
table.go
2
table.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
Loading…
Reference in New Issue