Merge PR #1115: Cap CSV import fields per record at the worksheet column limit (#1037)

This commit is contained in:
aiongg
2026-10-22 02:34:34 +00:00
2 changed files with 37 additions and 7 deletions
+14 -5
View File
@@ -87,8 +87,9 @@ pub fn sniff_delimiter(text: &str, quote: char) -> u8 {
best.0
}
/// Splits CSV text into records of fields.
fn parse_records(text: &str, delim: char, quote: char, mut on_record: impl FnMut(Vec<String>) -> bool) {
/// Splits CSV text into records of fields. A record keeps at most [`MAX_COLS`] fields; the rest are
/// dropped as they are read, so a row of endless delimiters cannot allocate a string per field.
pub(crate) fn parse_records(text: &str, delim: char, quote: char, mut on_record: impl FnMut(Vec<String>) -> bool) {
let mut field = String::new();
let mut record: Vec<String> = vec![];
let mut in_q = false;
@@ -112,13 +113,19 @@ fn parse_records(text: &str, delim: char, quote: char, mut on_record: impl FnMut
in_q = true;
field_started_quoted = true;
} else if c == delim {
record.push(std::mem::take(&mut field));
let f = std::mem::take(&mut field);
if record.len() < MAX_COLS as usize {
record.push(f);
}
field_started_quoted = false;
} else if c == '\r' || c == '\n' {
if c == '\r' && chars.peek() == Some(&'\n') {
chars.next();
}
record.push(std::mem::take(&mut field));
let f = std::mem::take(&mut field);
if record.len() < MAX_COLS as usize {
record.push(f);
}
field_started_quoted = false;
if !on_record(std::mem::take(&mut record)) {
return;
@@ -128,7 +135,9 @@ fn parse_records(text: &str, delim: char, quote: char, mut on_record: impl FnMut
}
}
if !field.is_empty() || !record.is_empty() || field_started_quoted {
record.push(field);
if record.len() < MAX_COLS as usize {
record.push(field);
}
on_record(record);
}
}
+23 -2
View File
@@ -1,7 +1,7 @@
use gridcraft_core::{CellRef, DateSystem, Value};
use gridcraft_core::{CellRef, DateSystem, MAX_COLS, Value};
use gridcraft_model::{Sheet, Workbook};
use crate::csv::{decode_text, sniff_delimiter};
use crate::csv::{decode_text, parse_records, sniff_delimiter};
use crate::{CsvOptions, Format, IoError, read_csv, sniff, write_csv, write_csv_limited};
fn v(wb: &Workbook, r: u32, c: u32) -> Value {
@@ -209,3 +209,24 @@ fn the_cap_counts_the_bytes_the_cells_hold_and_accepted_exports_keep_every_posit
let tabs = write_csv_limited(&s, b'\t', 14, text_of(&s)).unwrap();
assert_eq!(tabs.len(), 14);
}
#[test]
fn parser_stops_collecting_fields_at_max_cols() {
let extra = 50;
// MAX_COLS + extra fields on the first row (the last one filled), then a short second row.
let mut text = ",".repeat((MAX_COLS + extra) as usize - 1);
text.push_str("end\nx,y\n");
let mut lens = vec![];
parse_records(&text, ',', '"', |rec| {
lens.push(rec.len());
true
});
assert_eq!(lens, vec![MAX_COLS as usize, 2]);
// An unterminated last row is capped the same way.
let mut lens = vec![];
parse_records(&",".repeat(MAX_COLS as usize + extra as usize), ',', '"', |rec| {
lens.push(rec.len());
true
});
assert_eq!(lens, vec![MAX_COLS as usize]);
}