diff --git a/apps/gridcraft-cli/tests/text_to_columns_quotes.rs b/apps/gridcraft-cli/tests/text_to_columns_quotes.rs new file mode 100644 index 0000000..c35f857 --- /dev/null +++ b/apps/gridcraft-cli/tests/text_to_columns_quotes.rs @@ -0,0 +1,26 @@ +use std::process::Command; + +#[test] +fn text_to_columns_preserves_doubled_quotes() { + let dir = std::env::temp_dir().join(format!("gridcraft-t2c-{}", std::process::id())); + std::fs::create_dir_all(&dir).expect("make temp directory"); + let script = dir.join("steps.jsonl"); + std::fs::write( + &script, + concat!( + "{\"command\":\"cell.set\",\"params\":{\"cell\":\"A1\",\"input\":\"\\\"a\\\"\\\"b\\\",c\"}}\n", + "{\"command\":\"data.textToColumns\",\"params\":{\"range\":\"A1\",\"delimiters\":[\",\"]}}\n", + ), + ) + .expect("write commands"); + let output = Command::new(env!("CARGO_BIN_EXE_gridcraft-cli")) + .args(["run", "--script"]) + .arg(&script) + .args(["--print", "A1:B1", "--csv", "--quiet"]) + .output() + .expect("run Gridcraft CLI"); + std::fs::remove_file(script).expect("remove script"); + std::fs::remove_dir(dir).expect("remove temp directory"); + assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr)); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "\"a\"\"b\",c"); +} diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs index 8961d87..2de8e84 100644 --- a/crates/engine/src/cmd/data.rs +++ b/crates/engine/src/cmd/data.rs @@ -683,9 +683,15 @@ fn split_delimited(text: &str, delims: &[char], consecutive: bool, quote: char) let mut cur = String::new(); let mut in_q = false; let mut last_delim = false; - for c in text.chars() { + let mut chars = text.chars().peekable(); + while let Some(c) = chars.next() { if c == quote { - in_q = !in_q; + if in_q && chars.peek() == Some("e) { + cur.push(quote); + chars.next(); + } else { + in_q = !in_q; + } continue; } if !in_q && delims.contains(&c) {