Skip to main content

gridline_engine/engine/
cell_ref.rs

1//! Cell reference parsing and formatting.
2//!
3//! Provides bidirectional conversion between spreadsheet-style cell references
4//! (e.g., "A1", "B2", "AA100") and zero-indexed row/column coordinates.
5//!
6//! # Examples
7//!
8//! ```ignore
9//! let cell = CellRef::from_str("B3").unwrap();
10//! assert_eq!(cell.row, 2);  // 0-indexed
11//! assert_eq!(cell.col, 1);
12//! assert_eq!(cell.to_string(), "B3");
13//! ```
14
15use regex::Regex;
16use serde::{Deserialize, Serialize};
17use std::fmt;
18
19/// A reference to a cell by row and column indices (0-indexed).
20#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
21pub struct CellRef {
22    pub row: usize,
23    pub col: usize,
24}
25
26impl CellRef {
27    pub fn new(row: usize, col: usize) -> CellRef {
28        CellRef { row, col }
29    }
30
31    /// Parse a cell reference from spreadsheet notation (e.g., "A1", "B2", "AA10").
32    /// Returns None if the input is invalid.
33    #[allow(clippy::should_implement_trait)]
34    pub fn from_str(name: &str) -> Option<CellRef> {
35        Self::parse_a1(name)
36    }
37
38    fn parse_a1(name: &str) -> Option<CellRef> {
39        let re = Regex::new(r"^(?<letters>[A-Za-z]+)(?<numbers>[0-9]+)$").unwrap();
40        let caps = re.captures(name)?;
41        let letters = &caps["letters"];
42        let numbers = &caps["numbers"];
43
44        let col = letters
45            .to_ascii_uppercase()
46            .bytes()
47            .fold(0usize, |acc, c| acc * 26 + (c - b'A') as usize + 1)
48            - 1;
49
50        let row = numbers.parse::<usize>().ok()?.checked_sub(1)?;
51
52        Some(CellRef::new(row, col))
53    }
54
55    /// Convert column index to spreadsheet-style letters (0 -> A, 25 -> Z, 26 -> AA).
56    pub fn col_to_letters(col: usize) -> String {
57        let mut result = String::new();
58        let mut n = col + 1;
59        while n > 0 {
60            n -= 1;
61            result.insert(0, (b'A' + (n % 26) as u8) as char);
62            n /= 26;
63        }
64        result
65    }
66}
67
68impl std::str::FromStr for CellRef {
69    type Err = String;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        Self::parse_a1(s).ok_or_else(|| format!("Invalid cell reference: {}", s))
73    }
74}
75
76impl fmt::Display for CellRef {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}{}", CellRef::col_to_letters(self.col), self.row + 1)
79    }
80}