Skip to main content

cell_sheet_core/io/
cell_format.rs

1use crate::model::{col_index_to_label, col_label_to_index, CellValue, Sheet};
2use std::io::{BufRead, BufReader, Read, Write};
3
4fn parse_address(addr: &str) -> Option<(usize, usize)> {
5    let mut col_end = 0;
6    for (i, c) in addr.chars().enumerate() {
7        if c.is_ascii_uppercase() {
8            col_end = i + 1;
9        } else {
10            break;
11        }
12    }
13    if col_end == 0 || col_end >= addr.len() {
14        return None;
15    }
16    let col_label = &addr[..col_end];
17    let row_str = &addr[col_end..];
18    let col = col_label_to_index(col_label)?;
19    let row: usize = row_str.parse().ok()?;
20    Some((row, col))
21}
22
23fn format_address(row: usize, col: usize) -> String {
24    format!("{}{}", col_index_to_label(col), row)
25}
26
27pub fn write_cell_format<W: Write>(
28    sheet: &Sheet,
29    mut writer: W,
30) -> Result<(), Box<dyn std::error::Error>> {
31    writeln!(writer, "# cell v1")?;
32    writeln!(writer)?;
33    writeln!(writer, "size {} {}", sheet.row_count, sheet.col_count)?;
34    writeln!(writer)?;
35
36    for (i, &width) in sheet.col_widths.iter().enumerate() {
37        writeln!(writer, "col-width {} {}", i, width)?;
38    }
39    if !sheet.col_widths.is_empty() {
40        writeln!(writer)?;
41    }
42
43    let mut positions: Vec<_> = sheet.cells.keys().cloned().collect();
44    positions.sort();
45
46    for pos in positions {
47        let cell = &sheet.cells[&pos];
48        let addr = format_address(pos.0, pos.1);
49
50        if cell.raw.starts_with('=') {
51            writeln!(writer, "formula {} = {}", addr, cell.raw)?;
52        } else {
53            match &cell.value {
54                CellValue::Number(_) => {
55                    writeln!(writer, "let {} = {}", addr, cell.raw)?;
56                }
57                CellValue::Text(s) => {
58                    writeln!(writer, "label {} = \"{}\"", addr, s)?;
59                }
60                CellValue::Bool(b) => {
61                    writeln!(
62                        writer,
63                        "let {} = {}",
64                        addr,
65                        if *b { "TRUE" } else { "FALSE" }
66                    )?;
67                }
68                CellValue::Empty => {}
69                CellValue::Error(_) => {
70                    writeln!(writer, "label {} = \"{}\"", addr, cell.raw)?;
71                }
72            }
73        }
74    }
75
76    Ok(())
77}
78
79pub fn read_cell_format<R: Read>(reader: R) -> Result<Sheet, Box<dyn std::error::Error>> {
80    let mut sheet = Sheet::new();
81    let buf = BufReader::new(reader);
82
83    for line in buf.lines() {
84        let line = line?;
85        let line = line.trim();
86
87        if line.is_empty() || line.starts_with('#') {
88            continue;
89        }
90
91        if let Some(rest) = line.strip_prefix("size ") {
92            let parts: Vec<&str> = rest.split_whitespace().collect();
93            if parts.len() == 2 {
94                sheet.row_count = parts[0].parse().unwrap_or(0);
95                sheet.col_count = parts[1].parse().unwrap_or(0);
96            }
97        } else if let Some(rest) = line.strip_prefix("col-width ") {
98            let parts: Vec<&str> = rest.split_whitespace().collect();
99            if parts.len() == 2 {
100                let idx: usize = parts[0].parse().unwrap_or(0);
101                let width: u16 = parts[1].parse().unwrap_or(10);
102                if idx >= sheet.col_widths.len() {
103                    sheet.col_widths.resize(idx + 1, 10);
104                }
105                sheet.col_widths[idx] = width;
106            }
107        } else if let Some(rest) = line.strip_prefix("let ") {
108            if let Some((addr_str, value_str)) = rest.split_once(" = ") {
109                if let Some(pos) = parse_address(addr_str.trim()) {
110                    sheet.set_cell(pos, value_str.trim());
111                }
112            }
113        } else if let Some(rest) = line.strip_prefix("label ") {
114            if let Some((addr_str, value_str)) = rest.split_once(" = ") {
115                if let Some(pos) = parse_address(addr_str.trim()) {
116                    let s = value_str.trim();
117                    let s = if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
118                        &s[1..s.len() - 1]
119                    } else {
120                        s
121                    };
122                    sheet.set_cell(pos, s);
123                }
124            }
125        } else if let Some(rest) = line.strip_prefix("formula ") {
126            if let Some((addr_str, formula_str)) = rest.split_once(" = ") {
127                if let Some(pos) = parse_address(addr_str.trim()) {
128                    sheet.set_cell(pos, formula_str.trim());
129                }
130            }
131        }
132    }
133
134    Ok(sheet)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::model::CellValue;
141
142    #[test]
143    fn write_and_read_roundtrip() {
144        let mut sheet = Sheet::new();
145        sheet.set_cell((0, 0), "42");
146        sheet.set_cell((0, 1), "hello");
147        sheet.set_cell((1, 0), "=A1+1");
148        sheet.cells.get_mut(&(1, 0)).unwrap().value = CellValue::Number(43.0);
149        sheet.col_widths = vec![12, 8];
150
151        let mut buf = Vec::new();
152        write_cell_format(&sheet, &mut buf).unwrap();
153        let output = String::from_utf8(buf.clone()).unwrap();
154
155        assert!(output.contains("size 2 2"));
156        assert!(output.contains("let A0 = 42"));
157        assert!(output.contains("label B0 = \"hello\""));
158        assert!(output.contains("formula A1 = =A1+1"));
159        assert!(output.contains("col-width 0 12"));
160
161        let sheet2 = read_cell_format(buf.as_slice()).unwrap();
162        assert_eq!(sheet2.row_count, 2);
163        assert_eq!(sheet2.col_count, 2);
164        assert_eq!(
165            sheet2.get_cell((0, 0)).unwrap().value,
166            CellValue::Number(42.0)
167        );
168        assert_eq!(
169            sheet2.get_cell((0, 1)).unwrap().value,
170            CellValue::Text("hello".into())
171        );
172        assert_eq!(sheet2.get_cell((1, 0)).unwrap().raw, "=A1+1");
173        assert_eq!(sheet2.col_widths, vec![12, 8]);
174    }
175
176    #[test]
177    fn read_comments_and_blanks() {
178        let data = "# comment\n\nsize 1 1\nlet A0 = 5\n";
179        let sheet = read_cell_format(data.as_bytes()).unwrap();
180        assert_eq!(
181            sheet.get_cell((0, 0)).unwrap().value,
182            CellValue::Number(5.0)
183        );
184    }
185
186    #[test]
187    fn read_label_with_spaces() {
188        let data = "size 1 1\nlabel A0 = \"hello world\"\n";
189        let sheet = read_cell_format(data.as_bytes()).unwrap();
190        assert_eq!(
191            sheet.get_cell((0, 0)).unwrap().value,
192            CellValue::Text("hello world".into())
193        );
194    }
195
196    #[test]
197    fn write_empty_sheet() {
198        let sheet = Sheet::new();
199        let mut buf = Vec::new();
200        write_cell_format(&sheet, &mut buf).unwrap();
201        let output = String::from_utf8(buf).unwrap();
202        assert!(output.contains("size 0 0"));
203    }
204
205    #[test]
206    fn read_float_value() {
207        let data = "size 1 1\nlet A0 = 3.15\n";
208        let sheet = read_cell_format(data.as_bytes()).unwrap();
209        assert_eq!(
210            sheet.get_cell((0, 0)).unwrap().value,
211            CellValue::Number(3.15)
212        );
213    }
214}