-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
490 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package cellwalker | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// Cell represents a cell in Excel | ||
type Cell struct { | ||
column int | ||
row int | ||
} | ||
|
||
func newCell(col, row int) *Cell { | ||
if col > ColumnsLimit { | ||
col = ColumnsLimit | ||
} else { | ||
if col < 1 { | ||
col = 1 | ||
} | ||
} | ||
if row < 1 { | ||
row = 1 | ||
} else { | ||
if row > RowsLimit { | ||
row = RowsLimit | ||
} | ||
} | ||
return &Cell{ | ||
column: col, | ||
row: row, | ||
} | ||
} | ||
|
||
func newCellByID(cellID string) *Cell { | ||
cleanCellID := strings.ToUpper(cellID) | ||
re := regexp.MustCompile(`^([A-Z]+)([0-9]*)$`) | ||
match := re.FindStringSubmatch(cleanCellID) | ||
|
||
col := match[1] | ||
row, err := strconv.ParseInt(fmt.Sprintf("0%s", match[2]), 10, 32) | ||
if err != nil || row == 0 { | ||
row = 1 | ||
} | ||
|
||
return newCell(ColumnNameToIndex(col), int(row)) | ||
} | ||
|
||
// String representation of Cell | ||
func (c *Cell) String() string { | ||
return fmt.Sprintf("%s%d", ColumnIndexToName(c.column), c.row) | ||
} | ||
|
||
// Clone creates a copy of Cell | ||
func (c *Cell) Clone() *Cell { | ||
return newCell(c.column, c.row) | ||
} | ||
|
||
// ColumnIndex returns column number | ||
func (c *Cell) ColumnIndex() int { | ||
return c.column | ||
} | ||
|
||
// RowIndex returns row number | ||
func (c *Cell) RowIndex() int { | ||
return c.row | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package cellwalker | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestCreateCell(t *testing.T) { | ||
result := newCell(1, 1).String() | ||
|
||
assert.Equal(t, result, "A1") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,168 +1,157 @@ | ||
package cellwalker | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// RowsLimit https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 | ||
const ( | ||
RowsLimit = 1048576 | ||
ColumnsLimit = 16384 | ||
) | ||
|
||
// CellWalker struct | ||
type CellWalker struct { | ||
column int | ||
row int | ||
position *Cell | ||
boundary *Range | ||
} | ||
|
||
func newCellWalker(col int, row int) *CellWalker { | ||
if col > ColumnsLimit { | ||
col = ColumnsLimit | ||
} else { | ||
if col < 1 { | ||
col = 1 | ||
} | ||
} | ||
if row < 1 { | ||
row = 1 | ||
} else { | ||
if row > RowsLimit { | ||
row = RowsLimit | ||
} | ||
} | ||
func newCellWalker(cell *Cell, boundary *Range) *CellWalker { | ||
return &CellWalker{ | ||
column: col, | ||
row: row, | ||
position: cell.Clone(), | ||
boundary: boundary.Clone(), | ||
} | ||
} | ||
|
||
// At initializes CellWalker by specify initial cell to start | ||
func At(cellID string) *CellWalker { | ||
cleanCellID := strings.ToUpper(cellID) | ||
re := regexp.MustCompile(`^([A-Z]+)([0-9]*)$`) | ||
match := re.FindStringSubmatch(cleanCellID) | ||
|
||
col := match[1] | ||
row, err := strconv.ParseInt(fmt.Sprintf("0%s", match[2]), 10, 32) | ||
if err != nil || row == 0 { | ||
row = 1 | ||
} | ||
|
||
return newCellWalker(ColumnNameToIndex(col), int(row)) | ||
return newCellWalker(newCellByID(cellID), Sheet()) | ||
} | ||
|
||
// ColumnIndexToName converts column index to default excel name | ||
func ColumnIndexToName(id int) string { | ||
name := "" | ||
dividend := id | ||
modulo := 0 | ||
|
||
for dividend > 0 { | ||
modulo = (dividend - 1) % 26 | ||
name = fmt.Sprintf("%c%s", rune(modulo+'A'), name) | ||
dividend = (dividend - modulo) / 26 | ||
} | ||
return name | ||
} | ||
|
||
// ColumnNameToIndex converts default excel column name to index, 1-based index | ||
// name must be uppercase start from A, B, C, ..., Z, AA, AB, ... ZZ, AAA, ..., ZZZ, ... | ||
func ColumnNameToIndex(name string) int { | ||
index := 0 | ||
for colCharIndex, colCharLen := 0, len(name); colCharIndex < colCharLen; colCharIndex++ { | ||
charNum := int(name[colCharIndex]-'A') + 1 | ||
digitNum := (colCharLen - (colCharIndex + 1)) | ||
columnWeight := int(math.Pow(26.0, float64(digitNum))) | ||
columnValue := charNum * columnWeight | ||
index += columnValue | ||
} | ||
return index | ||
func (c *CellWalker) String() string { | ||
return c.position.String() | ||
} | ||
|
||
// String representation of Cell | ||
func (c *CellWalker) String() string { | ||
return fmt.Sprintf("%s%d", ColumnIndexToName(c.column), c.row) | ||
// Clone creates a clone of cellwalker | ||
func (c *CellWalker) Clone() *CellWalker { | ||
return &CellWalker{ | ||
position: c.position, | ||
boundary: c.boundary, | ||
} | ||
} | ||
|
||
// Above to move up one row | ||
func (c *CellWalker) Above() *CellWalker { | ||
rowAbove := c.row - 1 | ||
if rowAbove < 1 { | ||
rowAbove = 1 | ||
if c.CanMoveUp() { | ||
rowAbove := c.position.RowIndex() - 1 | ||
return newCellWalker(newCell(c.position.ColumnIndex(), rowAbove), c.boundary) | ||
} | ||
return newCellWalker(c.column, rowAbove) | ||
return c.Clone() | ||
} | ||
|
||
// Below to move down one row | ||
func (c *CellWalker) Below() *CellWalker { | ||
rowBeneath := c.row + 1 | ||
if rowBeneath > RowsLimit { | ||
rowBeneath = RowsLimit | ||
if c.CanMoveDown() { | ||
rowBeneath := c.position.RowIndex() + 1 | ||
return newCellWalker(newCell(c.position.ColumnIndex(), rowBeneath), c.boundary) | ||
} | ||
return newCellWalker(c.column, rowBeneath) | ||
return c.Clone() | ||
} | ||
|
||
// Right to move right one column | ||
func (c *CellWalker) Right() *CellWalker { | ||
rowRight := c.column + 1 | ||
if rowRight > ColumnsLimit { | ||
rowRight = ColumnsLimit | ||
if c.CanMoveRight() { | ||
columnRight := c.position.ColumnIndex() + 1 | ||
return newCellWalker(newCell(columnRight, c.position.RowIndex()), c.boundary) | ||
} | ||
return newCellWalker(rowRight, c.row) | ||
return c.Clone() | ||
} | ||
|
||
// Left to move left one column | ||
func (c *CellWalker) Left() *CellWalker { | ||
rowLeft := c.column - 1 | ||
if rowLeft < 1 { | ||
rowLeft = 1 | ||
if c.CanMoveLeft() { | ||
columnLeft := c.position.ColumnIndex() - 1 | ||
return newCellWalker(newCell(columnLeft, c.position.RowIndex()), c.boundary) | ||
} | ||
return c.Clone() | ||
} | ||
|
||
// LeftMost to move leftmost column | ||
func (c *CellWalker) LeftMost() *CellWalker { | ||
if c.CanMoveLeft() { | ||
leftMost := c.boundary.LeftIndex() | ||
return newCellWalker(newCell(leftMost, c.position.RowIndex()), c.boundary) | ||
} | ||
return c.Clone() | ||
} | ||
|
||
// Rightmost to move rightmost column | ||
func (c *CellWalker) Rightmost() *CellWalker { | ||
if c.CanMoveRight() { | ||
rightMost := c.boundary.RightIndex() | ||
return newCellWalker(newCell(rightMost, c.position.RowIndex()), c.boundary) | ||
} | ||
return c.Clone() | ||
} | ||
|
||
// TopMost to move topmost column | ||
func (c *CellWalker) TopMost() *CellWalker { | ||
if c.CanMoveUp() { | ||
topMost := c.boundary.TopIndex() | ||
return newCellWalker(newCell(c.position.ColumnIndex(), topMost), c.boundary) | ||
} | ||
return newCellWalker(rowLeft, c.row) | ||
return c.Clone() | ||
} | ||
|
||
// Bottommost to move bottommost column | ||
func (c *CellWalker) Bottommost() *CellWalker { | ||
if c.CanMoveDown() { | ||
bottomMost := c.boundary.BottomIndex() | ||
return newCellWalker(newCell(c.position.ColumnIndex(), bottomMost), c.boundary) | ||
} | ||
return c.Clone() | ||
} | ||
|
||
// CanMoveLeft determines if it is at the left most cell | ||
func (c *CellWalker) CanMoveLeft() bool { | ||
return c.column > 1 | ||
return c.position.ColumnIndex() > c.boundary.left | ||
} | ||
|
||
// CanMoveRight determines if it is at the right most cell | ||
func (c *CellWalker) CanMoveRight() bool { | ||
return c.column < ColumnsLimit | ||
return c.position.ColumnIndex() < c.boundary.right | ||
} | ||
|
||
// CanMoveUp determines if it is at the up most cell | ||
func (c *CellWalker) CanMoveUp() bool { | ||
return c.row > 1 | ||
return c.position.RowIndex() > c.boundary.top | ||
} | ||
|
||
// CanMoveDown determines if it is at the bottom most cell | ||
func (c *CellWalker) CanMoveDown() bool { | ||
return c.row < RowsLimit | ||
return c.position.RowIndex() < c.boundary.bottom | ||
} | ||
|
||
// Column jump to a given colName | ||
// Column jumps to a given colName | ||
func (c *CellWalker) Column(colName string) *CellWalker { | ||
colIndex := ColumnNameToIndex(colName) | ||
|
||
return newCellWalker(colIndex, c.row) | ||
return newCellWalker(newCell(colIndex, c.position.RowIndex()), c.boundary) | ||
} | ||
|
||
// Row jump to a given row | ||
// Row jumps to a given row | ||
func (c *CellWalker) Row(row int) *CellWalker { | ||
return newCellWalker(c.column, row) | ||
return newCellWalker(newCell(c.position.ColumnIndex(), row), c.boundary) | ||
} | ||
|
||
// ColumnOffset return a cell with a given offset distance to column | ||
// ColumnOffset returns a cell with a given offset distance to column | ||
func (c *CellWalker) ColumnOffset(offset int) *CellWalker { | ||
return newCellWalker(c.column+offset, c.row) | ||
return newCellWalker(newCell(c.position.ColumnIndex()+offset, c.position.RowIndex()), c.boundary) | ||
} | ||
|
||
// RowOffset return a cell with a given offset distance to row | ||
// RowOffset returns a cell with a given offset distance to row | ||
func (c *CellWalker) RowOffset(offset int) *CellWalker { | ||
return newCellWalker(c.column, c.row+offset) | ||
return newCellWalker(newCell(c.position.ColumnIndex(), c.position.RowIndex()+offset), c.boundary) | ||
} | ||
|
||
// Tour traverses position to Right column first then first column of next row if hit the boundary edge. | ||
// Return nil if cannot make a further move | ||
func (c *CellWalker) Tour() *CellWalker { | ||
if c.CanMoveRight() { | ||
return c.Right() | ||
} | ||
if c.CanMoveDown() { | ||
return c.Below().LeftMost() | ||
} | ||
return nil | ||
} |
Oops, something went wrong.