1use crate::model::Sheet;
2use std::io::{Read, Write};
3
4const MAX_COL_WIDTH: u16 = 40;
5const DEFAULT_COL_WIDTH: u16 = 10;
6
7pub fn read_csv<R: Read>(reader: R, delimiter: u8) -> Result<Sheet, Box<dyn std::error::Error>> {
8 let mut sheet = Sheet::new();
9 let mut csv_reader = csv::ReaderBuilder::new()
10 .has_headers(false)
11 .delimiter(delimiter)
12 .from_reader(reader);
13
14 let mut max_col = 0usize;
15 let mut col_content_widths: Vec<usize> = Vec::new();
16
17 for (row_idx, result) in csv_reader.records().enumerate() {
18 let record = result?;
19 if record.len() > max_col {
20 max_col = record.len();
21 col_content_widths.resize(max_col, 0);
22 }
23 for (col_idx, field) in record.iter().enumerate() {
24 if !field.is_empty() {
25 sheet.set_cell((row_idx, col_idx), field);
26 col_content_widths[col_idx] = col_content_widths[col_idx].max(field.len());
27 }
28 }
29 sheet.row_count = row_idx + 1;
30 }
31 sheet.col_count = max_col;
32
33 sheet.col_widths = col_content_widths
34 .iter()
35 .map(|&w| {
36 let width = (w as u16).max(DEFAULT_COL_WIDTH);
37 width.min(MAX_COL_WIDTH)
38 })
39 .collect();
40
41 Ok(sheet)
42}
43
44pub fn write_csv<W: Write>(
45 sheet: &Sheet,
46 writer: W,
47 delimiter: u8,
48) -> Result<(), Box<dyn std::error::Error>> {
49 let mut csv_writer = csv::WriterBuilder::new()
50 .delimiter(delimiter)
51 .from_writer(writer);
52
53 for row in 0..sheet.row_count {
54 let mut record = Vec::new();
55 for col in 0..sheet.col_count {
56 let value = match sheet.get_cell((row, col)) {
57 Some(cell) => cell.value.to_string(),
58 None => String::new(),
59 };
60 record.push(value);
61 }
62 csv_writer.write_record(&record)?;
63 }
64 csv_writer.flush()?;
65 Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::model::CellValue;
72
73 #[test]
74 fn read_csv_simple() {
75 let data = "Name,Score\nAlice,95\nBob,88\n";
76 let sheet = read_csv(data.as_bytes(), b',').unwrap();
77 assert_eq!(sheet.row_count, 3);
78 assert_eq!(sheet.col_count, 2);
79 assert_eq!(
80 sheet.get_cell((0, 0)).unwrap().value,
81 CellValue::Text("Name".into())
82 );
83 assert_eq!(
84 sheet.get_cell((1, 1)).unwrap().value,
85 CellValue::Number(95.0)
86 );
87 }
88
89 #[test]
90 fn read_tsv() {
91 let data = "A\tB\n1\t2\n";
92 let sheet = read_csv(data.as_bytes(), b'\t').unwrap();
93 assert_eq!(
94 sheet.get_cell((1, 0)).unwrap().value,
95 CellValue::Number(1.0)
96 );
97 assert_eq!(
98 sheet.get_cell((1, 1)).unwrap().value,
99 CellValue::Number(2.0)
100 );
101 }
102
103 #[test]
104 fn read_csv_empty_cells() {
105 let data = "a,,b\n,,\n";
106 let sheet = read_csv(data.as_bytes(), b',').unwrap();
107 assert_eq!(
108 sheet.get_cell((0, 0)).unwrap().value,
109 CellValue::Text("a".into())
110 );
111 assert!(sheet.get_cell((0, 1)).is_none());
112 assert_eq!(
113 sheet.get_cell((0, 2)).unwrap().value,
114 CellValue::Text("b".into())
115 );
116 }
117
118 #[test]
119 fn read_csv_quoted_fields() {
120 let data = "\"hello, world\",42\n";
121 let sheet = read_csv(data.as_bytes(), b',').unwrap();
122 assert_eq!(
123 sheet.get_cell((0, 0)).unwrap().value,
124 CellValue::Text("hello, world".into())
125 );
126 }
127
128 #[test]
129 fn read_csv_formula_as_text() {
130 let data = "=SUM(A1:A3)\n";
131 let sheet = read_csv(data.as_bytes(), b',').unwrap();
132 assert_eq!(sheet.get_cell((0, 0)).unwrap().raw, "=SUM(A1:A3)");
133 assert_eq!(
134 sheet.get_cell((0, 0)).unwrap().value,
135 CellValue::Text("=SUM(A1:A3)".into())
136 );
137 }
138
139 #[test]
140 fn write_csv_simple() {
141 let mut sheet = Sheet::new();
142 sheet.set_cell((0, 0), "Name");
143 sheet.set_cell((0, 1), "Score");
144 sheet.set_cell((1, 0), "Alice");
145 sheet.set_cell((1, 1), "95");
146 let mut buf = Vec::new();
147 write_csv(&sheet, &mut buf, b',').unwrap();
148 let output = String::from_utf8(buf).unwrap();
149 assert_eq!(output, "Name,Score\nAlice,95\n");
150 }
151
152 #[test]
153 fn write_csv_flattens_formula_values() {
154 let mut sheet = Sheet::new();
155 sheet.set_cell((0, 0), "=1+2");
156 sheet.cells.get_mut(&(0, 0)).unwrap().value = CellValue::Number(3.0);
157 let mut buf = Vec::new();
158 write_csv(&sheet, &mut buf, b',').unwrap();
159 let output = String::from_utf8(buf).unwrap();
160 assert_eq!(output, "3\n");
161 }
162
163 #[test]
164 fn write_csv_empty_cells() {
165 let mut sheet = Sheet::new();
166 sheet.set_cell((0, 0), "a");
167 sheet.set_cell((0, 2), "b");
168 sheet.row_count = 1;
169 sheet.col_count = 3;
170 let mut buf = Vec::new();
171 write_csv(&sheet, &mut buf, b',').unwrap();
172 let output = String::from_utf8(buf).unwrap();
173 assert_eq!(output, "a,,b\n");
174 }
175
176 #[test]
177 fn write_csv_needs_quoting() {
178 let mut sheet = Sheet::new();
179 sheet.set_cell((0, 0), "hello, world");
180 let mut buf = Vec::new();
181 write_csv(&sheet, &mut buf, b',').unwrap();
182 let output = String::from_utf8(buf).unwrap();
183 assert_eq!(output, "\"hello, world\"\n");
184 }
185
186 #[test]
187 fn col_widths_auto_sized() {
188 let data = "Name,Score\nAlice,95\n";
189 let sheet = read_csv(data.as_bytes(), b',').unwrap();
190 assert!(sheet.col_widths[0] >= 5);
191 assert!(sheet.col_widths[1] >= 5);
192 }
193}