cell_sheet_core/io/
cell_format.rs1use 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}