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 }