From ed01b2ff70dc70d4efc6bdfce68051b8bf82ca91 Mon Sep 17 00:00:00 2001 From: Rushmore75 Date: Wed, 12 Nov 2025 12:15:20 -0700 Subject: [PATCH] fix #18 --- Cargo.lock | 1 + Cargo.toml | 4 +-- src/app/logic/calc.rs | 69 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4fb0bd..40396aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,7 @@ dependencies = [ [[package]] name = "evalexpr" version = "13.0.0" +source = "git+https://github.com/Rushmore75/evalexpr.git#f7b8fa3436bfc3e114da3f84d1d62603307fed70" [[package]] name = "fnv" diff --git a/Cargo.toml b/Cargo.toml index 32114ba..4d1b6b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" [dependencies] # evalexpr = "13.0.0" -evalexpr ={ path = "../evalexpr"} -# evalexpr = { git="https://github.com/Rushmore75/evalexpr.git" } +# evalexpr ={ path = "../evalexpr"} +evalexpr = { git="https://github.com/Rushmore75/evalexpr.git" } ratatui = "0.29.0" diff --git a/src/app/logic/calc.rs b/src/app/logic/calc.rs index db81364..7ec6ee2 100644 --- a/src/app/logic/calc.rs +++ b/src/app/logic/calc.rs @@ -28,6 +28,9 @@ impl std::fmt::Debug for Grid { } } +const CSV_DELIMITER: char = ','; +const CSV_ESCAPE: char = '"'; + impl Grid { pub fn needs_to_be_saved(&self) -> bool { self.dirty @@ -51,8 +54,20 @@ impl Grid { for y in 0..=my { for x in 0..=mx { let cell = &self.cells[x][y]; - let display = cell.as_ref().map(|f| f.to_string()).unwrap_or(String::new()); - write!(f, "{display},")?; + let mut display = cell.as_ref().map(|f| f.to_string()).unwrap_or(String::new()); + + // escape quotes " -> "" + let needs_escaping = display.char_indices().filter(|f| f.1==CSV_ESCAPE).map(|f| f.0).collect::>(); + for idx in needs_escaping.iter().rev() { + display.insert(*idx, CSV_ESCAPE); + } + + // escape string of it has a comma + if display.contains(CSV_DELIMITER) { + write!(f, "\"{display}\"{CSV_DELIMITER}")?; + } else { + write!(f, "{display}{CSV_DELIMITER}")?; + } } write!(f, "\n")?; } @@ -92,9 +107,55 @@ impl Grid { let mut buf = String::new(); file.read_to_string(&mut buf)?; for (yi, line) in buf.lines().enumerate() { - for (xi, cell) in line.split(',').enumerate() { + + // 1, 2, "=avg(A0,B0)", she said: """wow""", + + let mut cells = Vec::new(); + + let mut inside_quotes = false; + let mut token = Vec::new(); + + let mut iter = line.as_bytes().iter().map(|f| *f as char).peekable(); + while let Some(c) = iter.next() { + // we just finished + if c == CSV_DELIMITER && !inside_quotes { + if !token.is_empty() { + cells.push(Some(token.iter().collect::())); + } else { + cells.push(None); + } + token.clear(); + continue; + } + // start reading an escaped cell + if c == '"' { + if inside_quotes { + // we might be escaping a quote + if let Some(next) = iter.peek() { + // check if the next cell is a quote, if it is, that's because it's being escaped by the current quote + if *next == '"' { + // don't save the escape char + continue; + } else { + // escaped cell over + inside_quotes = false; + continue; + } + } else { + // we are at the end of the row, so idk if it matters anymore, as there won't be a next() + } + } else { + inside_quotes = true; + // don't save the scape char + continue; + } + } + token.push(c) + } + + for (xi, cell) in cells.into_iter().enumerate() { // This gets automatically duck-typed - grid.set_cell_raw((xi, yi), Some(cell.to_string())); + grid.set_cell_raw((xi, yi), cell); } }