Skip to main content

cell_sheet_core/io/
csv.rs

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}