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 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}