-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcoordinate.go
More file actions
128 lines (113 loc) · 3.14 KB
/
coordinate.go
File metadata and controls
128 lines (113 loc) · 3.14 KB
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
package gospreadsheet
import (
"errors"
"fmt"
"strconv"
"strings"
"unicode"
)
// ColumnIndexToName converts a 0-based column index to a column name (e.g., 0 -> "A", 25 -> "Z", 26 -> "AA").
func ColumnIndexToName(index int) (string, error) {
if index < 0 {
return "", errors.New("column index must be non-negative")
}
result := ""
for {
result = string(rune('A'+index%26)) + result
index = index/26 - 1
if index < 0 {
break
}
}
return result, nil
}
// ColumnNameToIndex converts a column name to a 0-based column index (e.g., "A" -> 0, "Z" -> 25, "AA" -> 26).
func ColumnNameToIndex(name string) (int, error) {
name = strings.ToUpper(strings.TrimSpace(name))
if name == "" {
return -1, errors.New("column name cannot be empty")
}
result := 0
for _, ch := range name {
if ch < 'A' || ch > 'Z' {
return -1, fmt.Errorf("invalid column name character: %c", ch)
}
result = result*26 + int(ch-'A') + 1
}
return result - 1, nil
}
// CellReference represents a parsed cell reference like "A1", "B2", etc.
type CellReference struct {
Column string
ColumnIdx int
Row int // 1-based row number
}
// ParseCellReference parses a cell reference string (e.g., "A1") into its components.
func ParseCellReference(ref string) (*CellReference, error) {
ref = strings.TrimSpace(ref)
if ref == "" {
return nil, errors.New("cell reference cannot be empty")
}
// Split into column letters and row number using index tracking
splitIdx := -1
for i, ch := range ref {
if unicode.IsDigit(ch) {
splitIdx = i
break
}
if !unicode.IsLetter(ch) {
return nil, fmt.Errorf("invalid character in cell reference: %c", ch)
}
}
if splitIdx <= 0 {
return nil, fmt.Errorf("invalid cell reference: %s", ref)
}
colPart := ref[:splitIdx]
rowPart := ref[splitIdx:]
// Validate row part contains only digits
for _, ch := range rowPart {
if !unicode.IsDigit(ch) {
return nil, fmt.Errorf("invalid cell reference: %s", ref)
}
}
if rowPart == "" {
return nil, fmt.Errorf("invalid cell reference: %s", ref)
}
colIdx, err := ColumnNameToIndex(colPart)
if err != nil {
return nil, err
}
row, err := strconv.Atoi(rowPart)
if err != nil || row < 1 {
return nil, fmt.Errorf("invalid row number in cell reference: %s", ref)
}
return &CellReference{
Column: strings.ToUpper(colPart),
ColumnIdx: colIdx,
Row: row,
}, nil
}
// CellName returns the cell reference string (e.g., "A1") for a 0-based row and column.
func CellName(row, col int) (string, error) {
colName, err := ColumnIndexToName(col)
if err != nil {
return "", err
}
return fmt.Sprintf("%s%d", colName, row+1), nil
}
// ParseRange parses a range string like "A1:C3" into two CellReferences.
func ParseRange(rangeStr string) (*CellReference, *CellReference, error) {
parts := strings.Split(rangeStr, ":")
if len(parts) != 2 {
return nil, nil, fmt.Errorf("invalid range: %s", rangeStr)
}
start, err := ParseCellReference(parts[0])
if err != nil {
return nil, nil, fmt.Errorf("invalid range start: %w", err)
}
end, err := ParseCellReference(parts[1])
if err != nil {
return nil, nil, fmt.Errorf("invalid range end: %w", err)
}
return start, end, nil
}