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