forked from plandem/xlsx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sheet_readwrite.go
379 lines (299 loc) · 9.9 KB
/
sheet_readwrite.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Copyright (c) 2017 Andrey Gayvoronsky <[email protected]>
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package xlsx
import (
"github.com/plandem/xlsx/internal/ml"
"github.com/plandem/xlsx/types"
"math"
)
type sheetReadWrite struct {
*sheetInfo
}
var _ Sheet = (*sheetReadWrite)(nil)
func (s *sheetReadWrite) setDimension(cols, rows int, resize bool) {
if cols <= 0 {
cols = 1
}
if rows <= 0 {
rows = 1
}
//converts rows/cols into indexes
cols--
rows--
if resize {
s.expandIfRequired(cols, rows)
}
s.ml.Dimension = &ml.SheetDimension{Bounds: types.BoundsFromIndexes(0, 0, cols, rows)}
}
//SetDimension sets total number of cols and rows in sheet
func (s *sheetReadWrite) SetDimension(cols, rows int) {
s.setDimension(cols, rows, true)
}
//Cell returns a cell for 0-based indexes
func (s *sheetReadWrite) Cell(colIndex, rowIndex int) *Cell {
s.expandIfRequired(colIndex, rowIndex)
colIndex, rowIndex, _ = s.mergedCells.Resolve(colIndex, rowIndex)
data := s.ml.SheetData[rowIndex].Cells[colIndex]
//if there is no any data for this cell, then create it
if data == nil {
data = &ml.Cell{
Ref: types.CellRefFromIndexes(colIndex, rowIndex),
}
s.ml.SheetData[rowIndex].Cells[colIndex] = data
}
return &Cell{ml: data, sheet: s.sheetInfo}
}
//Row returns a row for 0-based index
func (s *sheetReadWrite) Row(index int) *Row {
s.expandIfRequired(0, index)
data := s.ml.SheetData[index]
return &Row{
data,
newRange(s, 0, len(data.Cells)-1, index, index),
}
}
//refreshRefs update refs for all rows/cells starting from row with 0-based index
func (s *sheetReadWrite) refreshAllRefs(index int) {
for iRow, rowMax := index, len(s.ml.SheetData); iRow < rowMax; iRow++ {
row := s.ml.SheetData[iRow]
row.Ref = iRow + 1
for iCol, cell := range row.Cells {
if !isCellEmpty(cell) {
cell.Ref = types.CellRefFromIndexes(iCol, int(row.Ref-1))
}
}
}
}
//refreshColRefs update refs only for cells at row 0-based index rowIndex and starting 0-based index colIndex
func (s *sheetReadWrite) refreshColRefs(colIndex, rowIndex int) {
for iCol, colMax := colIndex, len(s.ml.SheetData[rowIndex].Cells); iCol < colMax; iCol++ {
cell := s.ml.SheetData[rowIndex].Cells[iCol]
if !isCellEmpty(cell) {
cell.Ref = types.CellRefFromIndexes(iCol, rowIndex)
}
}
}
//InsertRow inserts a row at 0-based index and returns it. Using to insert a row between other rows.
func (s *sheetReadWrite) InsertRow(index int) *Row {
//getting current height
_, rows := s.Dimension()
//expand to a new height
s.expandIfRequired(0, rows)
//copy previous info
copy(s.ml.SheetData[index+1:], s.ml.SheetData[index:])
//clear previous info at this index
s.ml.SheetData[index] = &ml.Row{Cells: make([]*ml.Cell, len(s.ml.SheetData[index].Cells))}
//refresh refs
s.refreshAllRefs(index)
return s.Row(index)
}
//DeleteRow deletes a row at 0-based index
func (s *sheetReadWrite) DeleteRow(index int) {
s.expandIfRequired(0, index)
s.ml.SheetData = append(s.ml.SheetData[:index], s.ml.SheetData[index+1:]...)
//now we must updated refs
s.refreshAllRefs(index)
//update dimension for a new size
cols, rows := s.Dimension()
s.setDimension(cols, rows-1, false)
}
//Col returns a col for 0-based index
func (s *sheetReadWrite) Col(index int) *Col {
s.expandIfRequired(index, 0)
_, rows := s.Dimension()
return &Col{
s.columns.Resolve(index),
newRange(s, index, index, 0, rows-1),
}
}
//InsertCol inserts a col at 0-based index and returns it. Using to insert a col between other cols.
func (s *sheetReadWrite) InsertCol(index int) *Col {
//getting current width
cols, _ := s.Dimension()
//expand to a new width
s.expandIfRequired(cols, 0)
for iRow, row := range s.ml.SheetData {
//copy previous info
copy(s.ml.SheetData[iRow].Cells[index+1:], s.ml.SheetData[iRow].Cells[index:])
//clear previous info at this index
s.ml.SheetData[iRow].Cells[index] = nil
//refresh refs
s.refreshColRefs(index, row.Ref-1)
}
return s.Col(index)
}
//DeleteCol deletes a col at 0-based index
func (s *sheetReadWrite) DeleteCol(index int) {
s.expandIfRequired(index, 0)
s.columns.Delete(index)
for iRow, row := range s.ml.SheetData {
//delete col
s.ml.SheetData[iRow].Cells = append(s.ml.SheetData[iRow].Cells[:index], s.ml.SheetData[iRow].Cells[index+1:]...)
//refresh refs
s.refreshColRefs(index, row.Ref-1)
}
//update dimension for a new size
cols, rows := s.Dimension()
s.setDimension(cols-1, rows, false)
}
//Cols returns iterator for all cols of sheet
func (s *sheetReadWrite) Cols() ColIterator {
cols, rows := s.Dimension()
s.expandIfRequired(cols-1, rows-1)
return newColIterator(s)
}
//Rows returns iterator for all rows of sheet
func (s *sheetReadWrite) Rows() RowIterator {
cols, rows := s.Dimension()
s.expandIfRequired(cols-1, rows-1)
return newRowIterator(s)
}
//resolveDimension check if there is a 'dimension' information(optional) and if there is no any, then calculate it from existing data
func (s *sheetReadWrite) resolveDimension(force bool) {
if !force && (s.ml.Dimension != nil && !s.ml.Dimension.Bounds.IsEmpty()) {
// We need to fix 'optimized' case, when Dimension holds only last part of Ref (e.g., C10 instead of instead of A1:C10).
// Normally such Ref means cell ref, but dimension is always a range
s.ml.Dimension.Bounds.FromRow = 0
s.ml.Dimension.Bounds.FromCol = 0
return
}
var (
maxWidth float64
maxHeight float64
)
//supposed that grid holds rows/cells with valid refs
for _, row := range s.ml.SheetData {
maxHeight = math.Max(maxHeight, float64(row.Ref)-1)
for _, cell := range row.Cells {
colIndex, _ := types.CellRef(cell.Ref).ToIndexes()
maxWidth = math.Max(maxWidth, float64(colIndex))
}
}
s.ml.Dimension = &ml.SheetDimension{Bounds: types.BoundsFromIndexes(0, 0, int(maxWidth), int(maxHeight))}
}
//expandOnInit expands grid to required dimension and copy existing data
func (s *sheetReadWrite) expandOnInit() {
force := (s.sheetMode & SheetModeIgnoreDimension) != 0
s.resolveDimension(force)
//during initialize phase we need to do hard work first time - expand grid to required size and copy it with existing data
nextWidth, nextHeight := s.Dimension()
//expand grid
grid := make([]*ml.Row, nextHeight)
for iRow := 0; iRow < nextHeight; iRow++ {
grid[iRow] = &ml.Row{
Ref: iRow + 1,
Cells: make([]*ml.Cell, nextWidth),
}
}
//fill grid with data
for _, row := range s.ml.SheetData {
iRow := int(row.Ref - 1)
for _, cell := range row.Cells {
//add cell info
if !isCellEmpty(cell) {
iCellCol, iCellRow := cell.Ref.ToIndexes()
grid[iCellRow].Cells[iCellCol] = cell
}
}
//add row info
row := row
row.Cells = grid[iRow].Cells
grid[iRow] = row
}
s.ml.SheetData = grid
s.isInitialized = true
s.setDimension(nextWidth, nextHeight, false)
}
//expandIfRequired expands grid to required dimension
func (s *sheetReadWrite) expandIfRequired(colIndex, rowIndex int) {
if !s.isInitialized {
s.expandOnInit()
}
s.resolveDimension(false)
//during expand phase we need to increase grid to new size only, without copying any info - it's already in place
curWidth, curHeight := s.Dimension()
nextWidth, nextHeight := colIndex+1, rowIndex+1
//shrink is not supported here, so fix for current size if required
if nextWidth < curWidth {
nextWidth = curWidth
}
if nextHeight < curHeight {
nextHeight = curHeight
}
//if size is fit to current, then ignore
if curWidth >= nextWidth && curHeight >= nextHeight {
return
}
//TODO: think about optimizing - use incremental step to decrease number of allocations for +1 step case, e.g.: size = (size * 3) / 2 + 1
//step to expand width
widthStep := nextWidth - curWidth
if widthStep > 0 {
for iRow, row := range s.ml.SheetData {
s.ml.SheetData[iRow].Cells = append(row.Cells, make([]*ml.Cell, widthStep)...)
}
}
//step to expand height
heightStep := nextHeight - curHeight
if heightStep > 0 {
s.ml.SheetData = append(s.ml.SheetData, make([]*ml.Row, heightStep)...)
for iRow := curHeight; iRow < nextHeight; iRow++ {
s.ml.SheetData[iRow] = &ml.Row{
Ref: iRow + 1,
Cells: make([]*ml.Cell, nextWidth),
}
}
}
//update dimension for a new size
s.setDimension(nextWidth, nextHeight, false)
}
//shrinkIfRequired shrinks grid to minimal size and set actual dimension. Called right before packing sheet data.
func (s *sheetReadWrite) shrinkIfRequired() {
grid := make([]*ml.Row, 0, len(s.ml.SheetData))
for _, row := range s.ml.SheetData {
nextRow := &ml.Row{}
*nextRow = *row
nextRow.Cells = make([]*ml.Cell, 0, len(row.Cells))
for iCol, cell := range row.Cells {
if !isCellEmpty(cell) {
cell.Ref = types.CellRefFromIndexes(iCol, int(row.Ref-1))
nextRow.Cells = append(nextRow.Cells, cell)
}
}
if !isRowEmpty(nextRow) {
grid = append(grid, nextRow)
}
}
s.ml.SheetData = grid
s.resolveDimension(true)
}
//BeforeMarshalXML shrinks data to optimize output and returns related ML information for marshaling
func (s *sheetReadWrite) BeforeMarshalXML() interface{} {
s.shrinkIfRequired()
s.isInitialized = false
//remove empty collections that don't have dedicated container
s.conditionals.pack()
s.filters.pack()
//we should add LegacyDrawing if any VML drawing was added, but
//we can't be sure in order of files for marshaling, so should call manually
s.drawingsVML.attachDrawingsRID()
return &s.ml
}
//afterOpen is callback that will be called right after requesting an already existing sheet. By default, it does nothing
func (s *sheetReadWrite) afterOpen() {
//make a grid
s.file.LoadIfRequired(s.expandOnInit)
//adds a styles for types
s.workbook.doc.styleSheet.addTypedStylesIfRequired()
//mark file as updated
s.file.MarkAsUpdated()
}
//afterCreate initializes a new sheet
func (s *sheetReadWrite) afterCreate(name string) {
//register file
s.sheetInfo.afterCreate(name)
//make a grid
s.expandOnInit()
//adds a styles for types
s.workbook.doc.styleSheet.addTypedStylesIfRequired()
}