Skip to main content

cell_sheet_core/
model.rs

1use std::collections::HashMap;
2use std::fmt;
3
4pub type CellPos = (usize, usize);
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum CellError {
8    DivZero,
9    Value,
10    Ref,
11    Circ,
12    Name,
13    Parse,
14}
15
16impl fmt::Display for CellError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            CellError::DivZero => write!(f, "#DIV/0!"),
20            CellError::Value => write!(f, "#VALUE!"),
21            CellError::Ref => write!(f, "#REF!"),
22            CellError::Circ => write!(f, "#CIRC!"),
23            CellError::Name => write!(f, "#NAME?"),
24            CellError::Parse => write!(f, "#PARSE!"),
25        }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub enum CellValue {
31    Number(f64),
32    Text(String),
33    Bool(bool),
34    Error(CellError),
35    Empty,
36}
37
38impl fmt::Display for CellValue {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            CellValue::Number(n) => {
42                if n.fract() == 0.0 {
43                    write!(f, "{}", *n as i64)
44                } else {
45                    write!(f, "{}", n)
46                }
47            }
48            CellValue::Text(s) => write!(f, "{}", s),
49            CellValue::Bool(b) => write!(f, "{}", if *b { "TRUE" } else { "FALSE" }),
50            CellValue::Error(e) => write!(f, "{}", e),
51            CellValue::Empty => write!(f, ""),
52        }
53    }
54}
55
56#[derive(Debug, Clone)]
57pub struct Cell {
58    pub raw: String,
59    pub value: CellValue,
60    pub dirty: bool,
61}
62
63pub struct Sheet {
64    pub cells: HashMap<CellPos, Cell>,
65    pub col_widths: Vec<u16>,
66    pub row_count: usize,
67    pub col_count: usize,
68}
69
70impl Default for Sheet {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl Sheet {
77    pub fn new() -> Self {
78        Sheet {
79            cells: HashMap::new(),
80            col_widths: Vec::new(),
81            row_count: 0,
82            col_count: 0,
83        }
84    }
85
86    pub fn get_cell(&self, pos: CellPos) -> Option<&Cell> {
87        self.cells.get(&pos)
88    }
89
90    pub fn set_cell(&mut self, pos: CellPos, raw: &str) {
91        let value = if raw.is_empty() {
92            CellValue::Empty
93        } else if raw.starts_with('=') {
94            // Formula — will be evaluated by formula engine later.
95            // For now, store as text.
96            CellValue::Text(raw.to_string())
97        } else if let Ok(n) = raw.parse::<f64>() {
98            CellValue::Number(n)
99        } else {
100            CellValue::Text(raw.to_string())
101        };
102
103        self.cells.insert(
104            pos,
105            Cell {
106                raw: raw.to_string(),
107                value,
108                dirty: raw.starts_with('='),
109            },
110        );
111
112        self.row_count = self.row_count.max(pos.0 + 1);
113        self.col_count = self.col_count.max(pos.1 + 1);
114    }
115
116    pub fn clear_cell(&mut self, pos: CellPos) {
117        self.cells.remove(&pos);
118    }
119
120    pub fn sort_by_column(&mut self, col: usize, ascending: bool) {
121        if self.row_count == 0 {
122            return;
123        }
124
125        let mut row_indices: Vec<usize> = (0..self.row_count).collect();
126
127        row_indices.sort_by(|&a, &b| {
128            let cell_a = self.get_cell((a, col));
129            let cell_b = self.get_cell((b, col));
130
131            let ord = match (cell_a.map(|c| &c.value), cell_b.map(|c| &c.value)) {
132                (Some(CellValue::Number(na)), Some(CellValue::Number(nb))) => {
133                    na.partial_cmp(nb).unwrap_or(std::cmp::Ordering::Equal)
134                }
135                (Some(va), Some(vb)) => va.to_string().cmp(&vb.to_string()),
136                (Some(_), None) => std::cmp::Ordering::Less,
137                (None, Some(_)) => std::cmp::Ordering::Greater,
138                (None, None) => std::cmp::Ordering::Equal,
139            };
140
141            if ascending {
142                ord
143            } else {
144                ord.reverse()
145            }
146        });
147
148        let mut new_cells = std::collections::HashMap::new();
149        for (new_row, &old_row) in row_indices.iter().enumerate() {
150            for c in 0..self.col_count {
151                if let Some(cell) = self.cells.get(&(old_row, c)) {
152                    new_cells.insert((new_row, c), cell.clone());
153                }
154            }
155        }
156        self.cells = new_cells;
157    }
158}
159
160pub fn col_index_to_label(mut col: usize) -> String {
161    let mut label = String::new();
162    loop {
163        label.insert(0, (b'A' + (col % 26) as u8) as char);
164        if col < 26 {
165            break;
166        }
167        col = col / 26 - 1;
168    }
169    label
170}
171
172pub fn col_label_to_index(label: &str) -> Option<usize> {
173    let mut index = 0usize;
174    for (i, c) in label.chars().enumerate() {
175        if !c.is_ascii_uppercase() {
176            return None;
177        }
178        if i > 0 {
179            index = (index + 1) * 26;
180        }
181        index += (c as usize) - ('A' as usize);
182    }
183    Some(index)
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn cell_value_display_number() {
192        assert_eq!(CellValue::Number(42.0).to_string(), "42");
193        assert_eq!(CellValue::Number(3.15).to_string(), "3.15");
194        assert_eq!(CellValue::Number(0.0).to_string(), "0");
195    }
196
197    #[test]
198    fn cell_value_display_text() {
199        assert_eq!(CellValue::Text("hello".into()).to_string(), "hello");
200    }
201
202    #[test]
203    fn cell_value_display_bool() {
204        assert_eq!(CellValue::Bool(true).to_string(), "TRUE");
205        assert_eq!(CellValue::Bool(false).to_string(), "FALSE");
206    }
207
208    #[test]
209    fn cell_value_display_errors() {
210        assert_eq!(CellValue::Error(CellError::DivZero).to_string(), "#DIV/0!");
211        assert_eq!(CellValue::Error(CellError::Value).to_string(), "#VALUE!");
212        assert_eq!(CellValue::Error(CellError::Ref).to_string(), "#REF!");
213        assert_eq!(CellValue::Error(CellError::Circ).to_string(), "#CIRC!");
214        assert_eq!(CellValue::Error(CellError::Name).to_string(), "#NAME?");
215        assert_eq!(CellValue::Error(CellError::Parse).to_string(), "#PARSE!");
216    }
217
218    #[test]
219    fn cell_value_display_empty() {
220        assert_eq!(CellValue::Empty.to_string(), "");
221    }
222
223    #[test]
224    fn sheet_new_is_empty() {
225        let sheet = Sheet::new();
226        assert_eq!(sheet.row_count, 0);
227        assert_eq!(sheet.col_count, 0);
228        assert!(sheet.cells.is_empty());
229    }
230
231    #[test]
232    fn sheet_set_and_get_cell() {
233        let mut sheet = Sheet::new();
234        sheet.set_cell((0, 0), "hello");
235        let cell = sheet.get_cell((0, 0)).unwrap();
236        assert_eq!(cell.raw, "hello");
237        assert_eq!(cell.value, CellValue::Text("hello".into()));
238    }
239
240    #[test]
241    fn sheet_set_cell_updates_extent() {
242        let mut sheet = Sheet::new();
243        sheet.set_cell((5, 3), "x");
244        assert_eq!(sheet.row_count, 6);
245        assert_eq!(sheet.col_count, 4);
246    }
247
248    #[test]
249    fn sheet_set_cell_parses_number() {
250        let mut sheet = Sheet::new();
251        sheet.set_cell((0, 0), "42");
252        assert_eq!(
253            sheet.get_cell((0, 0)).unwrap().value,
254            CellValue::Number(42.0)
255        );
256    }
257
258    #[test]
259    fn sheet_set_cell_parses_float() {
260        let mut sheet = Sheet::new();
261        sheet.set_cell((0, 0), "3.15");
262        assert_eq!(
263            sheet.get_cell((0, 0)).unwrap().value,
264            CellValue::Number(3.15)
265        );
266    }
267
268    #[test]
269    fn sheet_get_cell_empty() {
270        let sheet = Sheet::new();
271        assert!(sheet.get_cell((0, 0)).is_none());
272    }
273
274    #[test]
275    fn sheet_clear_cell() {
276        let mut sheet = Sheet::new();
277        sheet.set_cell((0, 0), "hello");
278        sheet.clear_cell((0, 0));
279        assert!(sheet.get_cell((0, 0)).is_none());
280    }
281
282    #[test]
283    fn col_index_to_label_single() {
284        assert_eq!(col_index_to_label(0), "A");
285        assert_eq!(col_index_to_label(25), "Z");
286    }
287
288    #[test]
289    fn col_index_to_label_double() {
290        assert_eq!(col_index_to_label(26), "AA");
291        assert_eq!(col_index_to_label(27), "AB");
292        assert_eq!(col_index_to_label(51), "AZ");
293        assert_eq!(col_index_to_label(52), "BA");
294    }
295
296    #[test]
297    fn col_label_to_index_roundtrip() {
298        for i in 0..100 {
299            assert_eq!(col_label_to_index(&col_index_to_label(i)).unwrap(), i);
300        }
301    }
302
303    #[test]
304    fn cell_value_number_display_no_trailing_zeros() {
305        assert_eq!(CellValue::Number(1.0).to_string(), "1");
306        assert_eq!(CellValue::Number(1.10).to_string(), "1.1");
307        assert_eq!(CellValue::Number(1.123).to_string(), "1.123");
308    }
309
310    #[test]
311    fn sheet_sort_by_column_ascending() {
312        let mut sheet = Sheet::new();
313        sheet.set_cell((0, 0), "Charlie");
314        sheet.set_cell((0, 1), "3");
315        sheet.set_cell((1, 0), "Alice");
316        sheet.set_cell((1, 1), "1");
317        sheet.set_cell((2, 0), "Bob");
318        sheet.set_cell((2, 1), "2");
319        sheet.sort_by_column(0, true);
320        assert_eq!(sheet.get_cell((0, 0)).unwrap().raw, "Alice");
321        assert_eq!(sheet.get_cell((1, 0)).unwrap().raw, "Bob");
322        assert_eq!(sheet.get_cell((2, 0)).unwrap().raw, "Charlie");
323        assert_eq!(sheet.get_cell((0, 1)).unwrap().raw, "1");
324        assert_eq!(sheet.get_cell((1, 1)).unwrap().raw, "2");
325        assert_eq!(sheet.get_cell((2, 1)).unwrap().raw, "3");
326    }
327
328    #[test]
329    fn sheet_sort_numeric_column() {
330        let mut sheet = Sheet::new();
331        sheet.set_cell((0, 0), "30");
332        sheet.set_cell((1, 0), "5");
333        sheet.set_cell((2, 0), "100");
334        sheet.sort_by_column(0, true);
335        assert_eq!(
336            sheet.get_cell((0, 0)).unwrap().value,
337            CellValue::Number(5.0)
338        );
339        assert_eq!(
340            sheet.get_cell((1, 0)).unwrap().value,
341            CellValue::Number(30.0)
342        );
343        assert_eq!(
344            sheet.get_cell((2, 0)).unwrap().value,
345            CellValue::Number(100.0)
346        );
347    }
348}