forked from tealeg/xlsx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
col.go
400 lines (363 loc) · 9.42 KB
/
col.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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
package xlsx
// Default column width in excel
const ColWidth = 9.5
const Excel2006MaxRowCount = 1048576
const Excel2006MaxRowIndex = Excel2006MaxRowCount - 1
type Col struct {
Min int
Max int
Hidden *bool
Width *float64
Collapsed *bool
OutlineLevel *uint8
BestFit *bool
CustomWidth *bool
Phonetic *bool
numFmt string
parsedNumFmt *parsedNumberFormat
style *Style
outXfID int
}
// NewColForRange return a pointer to a new Col, which will apply to
// columns in the range min to max (inclusive). Note, in order for
// this Col to do anything useful you must set some of its parameters
// and then apply it to a Sheet by calling sheet.SetColParameters.
func NewColForRange(min, max int) *Col {
if max < min {
// Nice try ;-)
return &Col{Min: max, Max: min}
}
return &Col{Min: min, Max: max}
}
// SetWidth sets the width of columns that have this Col applied to
// them. The width is expressed as the number of characters of the
// maximum digit width of the numbers 0-9 as rendered in the normal
// style's font.
func (c *Col) SetWidth(width float64) {
c.Width = &width
custom := true
c.CustomWidth = &custom
}
// SetType will set the format string of a column based on the type that you want to set it to.
// This function does not really make a lot of sense.
func (c *Col) SetType(cellType CellType) {
switch cellType {
case CellTypeString:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
case CellTypeNumeric:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_INT]
case CellTypeBool:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
case CellTypeInline:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
case CellTypeError:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL] //TEMP
case CellTypeDate:
// Cells that are stored as dates are not properly supported in this library.
// They should instead be stored as a Numeric with a date format.
c.numFmt = builtInNumFmt[builtInNumFmtIndex_GENERAL]
case CellTypeStringFormula:
c.numFmt = builtInNumFmt[builtInNumFmtIndex_STRING]
}
}
// GetStyle returns the Style associated with a Col
func (c *Col) GetStyle() *Style {
return c.style
}
// SetStyle sets the style of a Col
func (c *Col) SetStyle(style *Style) {
c.style = style
}
func (c *Col) SetOutlineLevel(outlineLevel uint8) {
c.OutlineLevel = &outlineLevel
}
// copyToRange is an internal convenience function to make a copy of a
// Col with a different Min and Max value, it is not intended as a
// general purpose Col copying function as you must still insert the
// resulting Col into the Col Store.
func (c *Col) copyToRange(min, max int) *Col {
return &Col{
Min: min,
Max: max,
Hidden: c.Hidden,
Width: c.Width,
Collapsed: c.Collapsed,
OutlineLevel: c.OutlineLevel,
BestFit: c.BestFit,
CustomWidth: c.CustomWidth,
Phonetic: c.Phonetic,
numFmt: c.numFmt,
parsedNumFmt: c.parsedNumFmt,
style: c.style,
}
}
type ColStoreNode struct {
Col *Col
Prev *ColStoreNode
Next *ColStoreNode
}
//
func (csn *ColStoreNode) findNodeForColNum(num int) *ColStoreNode {
switch {
case num >= csn.Col.Min && num <= csn.Col.Max:
return csn
case num < csn.Col.Min:
if csn.Prev == nil {
return nil
}
if csn.Prev.Col.Max < num {
return nil
}
return csn.Prev.findNodeForColNum(num)
case num > csn.Col.Max:
if csn.Next == nil {
return nil
}
if csn.Next.Col.Min > num {
return nil
}
return csn.Next.findNodeForColNum(num)
}
return nil
}
// ColStore is the working store of Col definitions, it will simplify all Cols added to it, to ensure there ar no overlapping definitions.
type ColStore struct {
Root *ColStoreNode
Len int
}
// Add a Col to the ColStore. If it overwrites all, or part of some
// existing Col's range of columns the that Col will be adjusted
// and/or split to make room for the new Col.
func (cs *ColStore) Add(col *Col) *ColStoreNode {
newNode := &ColStoreNode{Col: col}
if cs.Root == nil {
cs.Root = newNode
cs.Len = 1
return newNode
}
cs.makeWay(cs.Root, newNode)
return newNode
}
func (cs *ColStore) FindColByIndex(index int) *Col {
csn := cs.findNodeForColNum(index)
if csn != nil {
return csn.Col
}
return nil
}
func (cs *ColStore) findNodeForColNum(num int) *ColStoreNode {
if cs.Root == nil {
return nil
}
return cs.Root.findNodeForColNum(num)
}
func (cs *ColStore) removeNode(node *ColStoreNode) {
if node.Prev != nil {
if node.Next != nil {
node.Prev.Next = node.Next
} else {
node.Prev.Next = nil
}
}
if node.Next != nil {
if node.Prev != nil {
node.Next.Prev = node.Prev
} else {
node.Next.Prev = nil
}
}
if cs.Root == node {
switch {
case node.Prev != nil:
cs.Root = node.Prev
case node.Next != nil:
cs.Root = node.Next
default:
cs.Root = nil
}
}
node.Next = nil
node.Prev = nil
cs.Len -= 1
}
// makeWay will adjust the Min and Max of this ColStoreNode's Col to
// make way for a new ColStoreNode's Col. If necessary it will
// generate an additional ColStoreNode with a new Col covering the
// "tail" portion of this ColStoreNode's Col should the new node lay
// completely within the range of this one, but without reaching its
// maximum extent.
func (cs *ColStore) makeWay(node1, node2 *ColStoreNode) {
switch {
case node1.Col.Max < node2.Col.Min:
// The node2 starts after node1 ends, there's no overlap
//
// Node1 |----|
// Node2 |----|
if node1.Next != nil {
if node1.Next.Col.Min <= node2.Col.Max {
cs.makeWay(node1.Next, node2)
return
}
cs.addNode(node1, node2, node1.Next)
return
}
cs.addNode(node1, node2, nil)
return
case node1.Col.Min > node2.Col.Max:
// Node2 ends before node1 begins, there's no overlap
//
// Node1 |-----|
// Node2 |----|
if node1.Prev != nil {
if node1.Prev.Col.Max >= node2.Col.Min {
cs.makeWay(node1.Prev, node2)
return
}
cs.addNode(node1.Prev, node2, node1)
return
}
cs.addNode(nil, node2, node1)
return
case node1.Col.Min == node2.Col.Min && node1.Col.Max == node2.Col.Max:
// Exact match
//
// Node1 |xxx|
// Node2 |---|
prev := node1.Prev
next := node1.Next
cs.removeNode(node1)
cs.addNode(prev, node2, next)
// Remove node may have set the root to nil
if cs.Root == nil {
cs.Root = node2
}
return
case node1.Col.Min > node2.Col.Min && node1.Col.Max < node2.Col.Max:
// Node2 envelopes node1
//
// Node1 |xx|
// Node2 |----|
prev := node1.Prev
next := node1.Next
cs.removeNode(node1)
switch {
case prev == node2:
node2.Next = next
case next == node2:
node2.Prev = prev
default:
cs.addNode(prev, node2, next)
}
if node2.Prev != nil && node2.Prev.Col.Max >= node2.Col.Min {
cs.makeWay(prev, node2)
}
if node2.Next != nil && node2.Next.Col.Min <= node2.Col.Max {
cs.makeWay(next, node2)
}
if cs.Root == nil {
cs.Root = node2
}
case node1.Col.Min < node2.Col.Min && node1.Col.Max > node2.Col.Max:
// Node2 bisects node1:
//
// Node1 |---xx---|
// Node2 |--|
newCol := node1.Col.copyToRange(node2.Col.Max+1, node1.Col.Max)
newNode := &ColStoreNode{Col: newCol}
cs.addNode(node1, newNode, node1.Next)
node1.Col.Max = node2.Col.Min - 1
cs.addNode(node1, node2, newNode)
return
case node1.Col.Max >= node2.Col.Min && node1.Col.Min < node2.Col.Min:
// Node2 overlaps node1 at some point above it's minimum:
//
// Node1 |----xx|
// Node2 |-------|
next := node1.Next
node1.Col.Max = node2.Col.Min - 1
if next == node2 {
return
}
cs.addNode(node1, node2, next)
if next != nil && next.Col.Min <= node2.Col.Max {
cs.makeWay(next, node2)
}
return
case node1.Col.Min <= node2.Col.Max && node1.Col.Min > node2.Col.Min:
// Node2 overlaps node1 at some point below it's maximum:
//
// Node1: |------|
// Node2: |----xx|
prev := node1.Prev
node1.Col.Min = node2.Col.Max + 1
if prev == node2 {
return
}
cs.addNode(prev, node2, node1)
if prev != nil && prev.Col.Max >= node2.Col.Min {
cs.makeWay(node1.Prev, node2)
}
return
}
return
}
func (cs *ColStore) addNode(prev, this, next *ColStoreNode) {
if prev != nil {
prev.Next = this
}
this.Prev = prev
this.Next = next
if next != nil {
next.Prev = this
}
cs.Len += 1
}
func (cs *ColStore) getOrMakeColsForRange(start *ColStoreNode, min, max int) []*Col {
cols := []*Col{}
var csn *ColStoreNode
var newCol *Col
switch {
case start == nil:
newCol = NewColForRange(min, max)
csn = cs.Add(newCol)
case start.Col.Min <= min && start.Col.Max >= min:
csn = start
case start.Col.Min < min && start.Col.Max < min:
if start.Next != nil {
return cs.getOrMakeColsForRange(start.Next, min, max)
}
newCol = NewColForRange(min, max)
csn = cs.Add(newCol)
case start.Col.Min > min:
if start.Col.Min > max {
newCol = NewColForRange(min, max)
} else {
newCol = NewColForRange(min, start.Col.Min-1)
}
csn = cs.Add(newCol)
}
cols = append(cols, csn.Col)
if csn.Col.Max >= max {
return cols
}
cols = append(cols, cs.getOrMakeColsForRange(csn.Next, csn.Col.Max+1, max)...)
return cols
}
func chainOp(csn *ColStoreNode, fn func(idx int, col *Col)) {
for csn.Prev != nil {
csn = csn.Prev
}
var i int
for i = 0; csn.Next != nil; i++ {
fn(i, csn.Col)
csn = csn.Next
}
fn(i+1, csn.Col)
}
// ForEach calls the function fn for each Col defined in the ColStore.
func (cs *ColStore) ForEach(fn func(idx int, col *Col)) {
if cs.Root == nil {
return
}
chainOp(cs.Root, fn)
}