From 45ee4eceed5710e81918b76b0df200c90d0fdd27 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sat, 14 Nov 2026 16:54:24 +0100 Subject: [PATCH] Keep mid-field quotes when splitting with Text to Columns (#2438) Found while testing crates/engine/src/cmd/data.rs:992 (split_delimited, used by text_to_columns) Co-authored-by: Claude Haiku 5.5 --- crates/engine/src/cmd/data/tools.rs | 7 ++++++- crates/engine/src/tests.rs | 10 ++++++++++ crates/engine/src/tests/python_cells.rs | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/data/tools.rs b/crates/engine/src/cmd/data/tools.rs index e4b9f028f..2d0ffc3f9 100644 --- a/crates/engine/src/cmd/data/tools.rs +++ b/crates/engine/src/cmd/data/tools.rs @@ -183,8 +183,11 @@ pub(super) fn split_delimited(text: &str, delims: &[char], consecutive: bool, qu let mut in_q = false; let mut last_delim = false; let mut chars = text.chars().peekable(); + let mut at_start = true; while let Some(c) = chars.next() { - if c == quote { + if c == quote && (in_q || at_start) { + at_start = false; + last_delim = false; if in_q && chars.peek() == Some("e) { cur.push(quote); chars.next(); @@ -199,9 +202,11 @@ pub(super) fn split_delimited(text: &str, delims: &[char], consecutive: bool, qu } out.push(std::mem::take(&mut cur)); last_delim = true; + at_start = true; continue; } last_delim = false; + at_start = false; cur.push(c); } out.push(cur); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f5c17ba16..f000eb9fe 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -6378,6 +6378,16 @@ mod objects; mod python_cells; mod python_commands; mod python_files; + +#[test] +fn text_to_columns_keeps_a_mid_field_quote() { + let mut s = s(); + s.execute("cell.set", json!({"cell": "A1", "input": "'5\" pipe,abc"})).unwrap(); + s.execute("data.textToColumns", json!({"range": "A1", "delimiters": [","]})).unwrap(); + assert_eq!(v(&s, "A1"), Value::text("5\" pipe")); + assert_eq!(v(&s, "B1"), Value::text("abc")); +} + mod seams; #[test] diff --git a/crates/engine/src/tests/python_cells.rs b/crates/engine/src/tests/python_cells.rs index 63cf1d92b..8c7805dc4 100644 --- a/crates/engine/src/tests/python_cells.rs +++ b/crates/engine/src/tests/python_cells.rs @@ -175,8 +175,8 @@ fn text_to_columns_keeps_a_py_piece_as_text() { s.execute("cell.set", json!({"cell": "A1", "input": "'=PY(\"x\");7"})).unwrap(); assert_eq!(super::v(&s, "A1"), Value::text("=PY(\"x\");7")); s.execute("data.textToColumns", json!({"range": "A1", "delimiters": [";"]})).unwrap(); - // The default text qualifier takes the quotes away, as in Excel. - assert_eq!(super::v(&s, "A1"), Value::text("=PY(x)"), "the piece is kept, not cleared"); + // A quote in the middle of a field is kept, as in Excel. + assert_eq!(super::v(&s, "A1"), Value::text("=PY(\"x\")"), "the piece is kept, not cleared"); assert_eq!(code(&s, 0, "A1"), None, "and it is not a Python cell"); assert_eq!(super::v(&s, "B1"), Value::Number(7.0)); }