404 lines
10 KiB
Go
404 lines
10 KiB
Go
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.
|
|
// Column numbers start from 1.
|
|
func NewColForRange(min, max int) *Col {
|
|
if min < 1 {
|
|
panic("min col must be >= 1")
|
|
}
|
|
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)
|
|
}
|