Files
hiscaler-gox/spreedsheetx/column.go
scheibling b4eb50ab55
Some checks failed
Go / build (push) Failing after 7s
Created
2025-04-08 19:16:39 +02:00

193 lines
3.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package spreedsheetx
import (
"errors"
"fmt"
"math"
"regexp"
"strings"
)
// https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3?ui=en-us&rs=en-us&ad=us
// Max index is 16384 XFD
var (
rxColumnName = regexp.MustCompile("^[A-Za-z]{1,3}$")
)
const (
minNumber = 1
maxNumber = 16384
a = 64
)
type Column struct {
startName string // 最开始操作的列
endName string // 最远到达的列
current string // 当前列
}
func isValidName(name string) bool {
return rxColumnName.MatchString(name) && toNumber(name) <= maxNumber
}
func reverse(name string) []rune {
d := []rune(name)
for i, j := 0, len(d)-1; i < j; i, j = i+1, j-1 {
d[i], d[j] = d[j], d[i]
}
return d
}
func toNumber(name string) int {
name = strings.ToUpper(name)
switch len(name) {
case 0:
return 0
case 1:
return int(rune(name[0])) - a
default:
number := 0
for i, r := range reverse(name) {
if i == 0 {
number += int(r) - a
} else {
number += (int(r) - a) * int(math.Pow(26, float64(i)))
}
}
return number
}
}
func NewColumn(name string) *Column {
name = strings.ToUpper(name)
if !isValidName(name) {
panic("invalid column name")
}
return &Column{
startName: name,
endName: name,
current: name,
}
}
// ToFirst 到第一列,总是返回 A 列
func (c *Column) ToFirst() *Column {
c.current = "A"
return c
}
// Next 当前列的下一列
func (c *Column) Next() (*Column, error) {
return c.RightShift(1)
}
func (c *Column) Prev() (*Column, error) {
return c.LeftShift(1)
}
// StartName 返回最开始的列名
func (c Column) StartName() string {
return c.startName
}
func (c *Column) setEndName(name string) *Column {
if c.endName < name || len(c.endName) < len(name) {
c.endName = name
}
return c
}
// EndName 返回最远到达的列名
func (c Column) EndName() string {
return c.endName
}
// Name 当前列名
func (c Column) Name() string {
return c.current
}
// NameWithRow 带行号的列名比如A1
func (c Column) NameWithRow(row int) string {
return fmt.Sprintf("%s%d", c.current, row)
}
// Reset 重置到最开始的列NewColumn 创建时的列)
func (c *Column) Reset() *Column {
c.current = c.startName
c.endName = c.startName
return c
}
// To 跳转到指定的列
func (c *Column) To(name string) (*Column, error) {
name = strings.ToUpper(name)
if !isValidName(name) {
return c, fmt.Errorf("invalid column name %s", name)
}
c.current = name
c.setEndName(name)
return c, nil
}
func (c *Column) RightShift(steps int) (*Column, error) {
if steps <= 0 {
return c, nil
}
return c.shift(steps)
}
func (c *Column) LeftShift(steps int) (*Column, error) {
if steps <= 0 {
return c, nil
}
return c.shift(-steps)
}
// RightShift 基于当前位置右移多少列
func (c *Column) shift(steps int) (*Column, error) {
if steps == 0 {
return c, nil
}
number := toNumber(c.current)
number += steps
if number > maxNumber {
return c, errors.New("out of max columns")
} else if number < minNumber {
return c, errors.New("out of min columns")
}
sb := strings.Builder{}
sb.Grow(3) // Max 3 letters
times := 0
for {
times++
quotient := number / 26
remainder := number % 26
if remainder == 0 {
sb.WriteRune('Z')
} else {
sb.WriteRune(rune(a + remainder))
}
if quotient == 0 {
break
} else if quotient <= 26 {
if quotient != 1 || (times >= 1 && remainder != 0) {
sb.WriteRune(rune(a + quotient))
}
break
}
number = quotient
}
c.current = string(reverse(sb.String()))
if steps > 0 {
// Is right shift
c.setEndName(c.current)
}
return c, nil
}