| 1 | // Package calc owns everything about formulas: references, lexing, parsing, |
| 2 | // evaluation and the dependency graph. |
| 3 | package calc |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | // ColLabel renders a zero-based column position as a spreadsheet label. |
| 11 | // This is bijective base-26, not ordinary base-26: there is no zero digit, |
| 12 | // which is why 25 is "Z" and 26 is "AA" rather than "BA". |
| 13 | func ColLabel(pos int) string { |
| 14 | if pos < 0 { |
| 15 | return "" |
| 16 | } |
| 17 | var sb strings.Builder |
| 18 | pos++ |
| 19 | for pos > 0 { |
| 20 | pos-- |
| 21 | sb.WriteByte(byte('A' + pos%26)) |
| 22 | pos /= 26 |
| 23 | } |
| 24 | b := []byte(sb.String()) |
| 25 | for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { |
| 26 | b[i], b[j] = b[j], b[i] |
| 27 | } |
| 28 | return string(b) |
| 29 | } |
| 30 | |
| 31 | // ParseColLabel is ColLabel's inverse. |
| 32 | func ParseColLabel(s string) (int, error) { |
| 33 | if s == "" { |
| 34 | return 0, errors.New("calc: empty column label") |
| 35 | } |
| 36 | n := 0 |
| 37 | for i := 0; i < len(s); i++ { |
| 38 | c := s[i] |
| 39 | if c >= 'a' && c <= 'z' { |
| 40 | c -= 'a' - 'A' |
| 41 | } |
| 42 | if c < 'A' || c > 'Z' { |
| 43 | return 0, errors.New("calc: bad column label") |
| 44 | } |
| 45 | n = n*26 + int(c-'A') + 1 |
| 46 | } |
| 47 | return n - 1, nil |
| 48 | } |
| 49 | |
| 50 | // A1 renders zero-based positions as an A1 reference. Positions, not ids: |
| 51 | // stable ids are the key, A1 is the view. |
| 52 | func A1(rowPos, colPos int) string { |
| 53 | return ColLabel(colPos) + itoa(rowPos+1) |
| 54 | } |
| 55 | |
| 56 | func itoa(n int) string { |
| 57 | if n == 0 { |
| 58 | return "0" |
| 59 | } |
| 60 | var b [20]byte |
| 61 | i := len(b) |
| 62 | for n > 0 { |
| 63 | i-- |
| 64 | b[i] = byte('0' + n%10) |
| 65 | n /= 10 |
| 66 | } |
| 67 | return string(b[i:]) |
| 68 | } |
| 69 | |