mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-31 07:38:32 +00:00
Merge PR #1525: Keep leading empty columns and rows when saving CSV/text
Conflicts: - crates/xlsx/src/csv.rs: write_csv doc comment merged (this PR's A1 rectangle sentence, main's per-field cap wording) - crates/engine/src/tests.rs: kept both appended tests
This commit is contained in:
commit
8709b41666
3 files changed
+20
-11
No files matched your search
@@ -3344,3 +3344,10 @@ fn csv_as_text_keeps_identifier_columns_as_written() {
|
||||
let saved = s.execute("file.saveBytes", json!({"format": "csv"})).unwrap();
|
||||
assert_eq!(crate::io::base64_decode(saved["base64"].as_str().unwrap()).unwrap(), csv.replace('\n', "\r\n").as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_csv_keeps_leading_empty_columns_and_rows() {
|
||||
let wb = gridcraft_xlsx::read_csv(b",x\r\n\r\n,,y\r\n", &gridcraft_xlsx::CsvOptions::default()).unwrap();
|
||||
let out = crate::io::save_bytes(&wb, "a.csv").unwrap();
|
||||
assert_eq!(String::from_utf8(out).unwrap(), ",x,\r\n,,\r\n,,y\r\n");
|
||||
}
|
||||
@@ -196,12 +196,13 @@ pub fn read_csv(bytes: &[u8], opts: &CsvOptions) -> Result<Workbook, IoError> {
|
||||
Ok(wb)
|
||||
}
|
||||
|
||||
/// Writes the sheet's used range as CSV (CRLF line ends, UTF-8 without BOM). `cell_text` gives
|
||||
/// Writes the sheet as CSV from A1 to the end of its used range, so leading empty columns and rows
|
||||
/// stay as empty fields and every cell keeps its position (CRLF line ends, UTF-8 without BOM). `cell_text` gives
|
||||
/// each cell's field text: the caller decides how values look (Save As CSV writes them as
|
||||
/// displayed, number formats applied); quoting is done here.
|
||||
///
|
||||
/// The output is capped at [`MAX_CSV_BYTES`]: two cells far apart make the used range, and so the
|
||||
/// file, as big as the whole rectangle between them (`A1` and `XFD1048576` are 16 GiB of commas).
|
||||
/// The output is capped at [`MAX_CSV_BYTES`]: a cell far from A1 makes the file as big as the whole
|
||||
/// rectangle between them (`A1` and `XFD1048576` are 16 GiB of commas).
|
||||
/// An export that would pass the cap is refused before anything is allocated, or at the first
|
||||
/// field or line end that takes it past the cap (a field is measured before it is written), with
|
||||
/// [`IoError::TooLarge`]; accepted exports keep every field in its place.
|
||||
@@ -218,15 +219,15 @@ pub fn write_csv_limited(sheet: &Sheet, delimiter: u8, limit: usize, mut cell_te
|
||||
|what: String| IoError::TooLarge(format!("the CSV would be {what}, over the limit of {} MiB; export a smaller range", limit >> 20));
|
||||
// Every row has a delimiter between its fields and a CRLF, whatever the cells hold: a lower
|
||||
// bound on the output that needs no cell text.
|
||||
let (rows, cols) = (u64::from(r.end.row - r.start.row) + 1, u64::from(r.end.col - r.start.col) + 1);
|
||||
let (rows, cols) = (u64::from(r.end.row) + 1, u64::from(r.end.col) + 1);
|
||||
let per_row = (cols - 1).saturating_mul(delim.len_utf8() as u64).saturating_add(2);
|
||||
let least = rows.saturating_mul(per_row);
|
||||
if least > limit as u64 {
|
||||
return Err(too_large(format!("at least {} MiB ({rows} rows by {cols} columns)", least >> 20)));
|
||||
}
|
||||
for row in r.start.row..=r.end.row {
|
||||
for col in r.start.col..=r.end.col {
|
||||
if col > r.start.col {
|
||||
for row in 0..=r.end.row {
|
||||
for col in 0..=r.end.col {
|
||||
if col > 0 {
|
||||
out.push(delim);
|
||||
}
|
||||
let t = cell_text(CellRef::new(row, col));
|
||||
|
||||
@@ -110,14 +110,15 @@ fn writing() {
|
||||
Value::Array(_) => String::new(),
|
||||
};
|
||||
let out = String::from_utf8(write_csv(sh, b',', raw).unwrap()).unwrap();
|
||||
assert_eq!(out, "\"a,b\",\"q\"\"t\",\r\n0.1,FALSE,\r\n#DIV/0!,,\"x\ny\"\r\n");
|
||||
// The first cell is B2: the file starts at A1, so the leading empty row and column stay.
|
||||
assert_eq!(out, ",,,\r\n,\"a,b\",\"q\"\"t\",\r\n,0.1,FALSE,\r\n,#DIV/0!,,\"x\ny\"\r\n");
|
||||
let semi = String::from_utf8(write_csv(sh, b';', raw).unwrap()).unwrap();
|
||||
assert!(semi.starts_with("a,b;"));
|
||||
assert!(semi.starts_with(";;;\r\n;a,b;"));
|
||||
assert!(write_csv(&Sheet::new("E"), b',', raw).unwrap().is_empty());
|
||||
// Round trip.
|
||||
let back = read_csv(out.as_bytes(), &CsvOptions::default()).unwrap();
|
||||
assert_eq!(v(&back, 0, 0), Value::text("a,b"));
|
||||
assert_eq!(v(&back, 2, 2), Value::text("x\ny"));
|
||||
assert_eq!(v(&back, 1, 1), Value::text("a,b"));
|
||||
assert_eq!(v(&back, 3, 3), Value::text("x\ny"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user