1use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::engine::range_view::RangeView;
8use crate::function::Function;
9use crate::traits::{
10 EvaluationContext, FunctionProvider, NamedRangeResolver, Range, RangeResolver,
11 ReferenceResolver, Resolver, Table, TableResolver,
12};
13use formualizer_common::{ExcelError, LiteralValue};
14use formualizer_parse::{
15 ExcelErrorKind,
16 parser::{ReferenceType, TableReference},
17};
18
19type V = LiteralValue;
20type CellKey = (u32, u32); #[derive(Default, Clone)]
23struct Sheet {
24 cells: HashMap<CellKey, V>,
25}
26
27#[derive(Default)]
28pub struct TestWorkbook {
29 sheets: HashMap<String, Sheet>,
30 named: HashMap<String, Vec<Vec<V>>>,
31 tables: HashMap<String, Box<dyn Table>>,
32 fns: HashMap<(String, String), Arc<dyn Function>>,
33 aliases: HashMap<(String, String), (String, String)>,
34}
35
36impl TestWorkbook {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn with_cell<S: Into<String>>(mut self, sheet: S, row: u32, col: u32, v: V) -> Self {
44 let sh = self.sheets.entry(sheet.into()).or_default();
45 sh.cells.insert((row, col), v);
46 self
47 }
48
49 pub fn with_cell_a1<S: Into<String>, A: AsRef<str>>(self, sheet: S, a1: A, v: V) -> Self {
50 let (col, row) = parse_a1(a1.as_ref()).expect("bad A1 ref in with_cell_a1");
51 self.with_cell(sheet, row, col, v)
52 }
53
54 pub fn with_range<S: Into<String>>(
55 mut self,
56 sheet: S,
57 row: u32,
58 col: u32,
59 data: Vec<Vec<V>>,
60 ) -> Self {
61 let sh = self.sheets.entry(sheet.into()).or_default();
62 for (r_off, r) in data.into_iter().enumerate() {
63 for (c_off, v) in r.into_iter().enumerate() {
64 sh.cells.insert((row + r_off as u32, col + c_off as u32), v);
65 }
66 }
67 self
68 }
69
70 pub fn with_named_range<S: Into<String>>(mut self, name: S, data: Vec<Vec<V>>) -> Self {
72 self.named.insert(name.into(), data);
73 self
74 }
75
76 pub fn with_table<T: Table + 'static, S: Into<String>>(mut self, name: S, table: T) -> Self {
78 self.tables.insert(name.into(), Box::new(table));
79 self
80 }
81
82 pub fn with_simple_table<S: Into<String>>(
85 mut self,
86 name: S,
87 headers: Vec<String>,
88 data: Vec<Vec<V>>,
89 totals: Option<Vec<V>>,
90 ) -> Self {
91 #[derive(Debug)]
92 struct SimpleTable {
93 headers: Vec<String>,
94 data: Vec<Vec<V>>, totals: Option<Vec<V>>,
96 }
97 impl Table for SimpleTable {
98 fn get_cell(&self, row: usize, column: &str) -> Result<V, ExcelError> {
99 let col_idx = self
101 .headers
102 .iter()
103 .position(|h| h.eq_ignore_ascii_case(column))
104 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Ref))?;
105 self.data
106 .get(row)
107 .and_then(|r| r.get(col_idx))
108 .cloned()
109 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Ref))
110 }
111 fn get_column(&self, column: &str) -> Result<Box<dyn Range>, ExcelError> {
112 let col_idx = self
113 .headers
114 .iter()
115 .position(|h| h.eq_ignore_ascii_case(column))
116 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Ref))?;
117 let mut col: Vec<Vec<V>> = Vec::with_capacity(self.data.len());
118 for r in &self.data {
119 col.push(vec![r.get(col_idx).cloned().unwrap_or(V::Empty)]);
120 }
121 Ok(Box::new(crate::traits::InMemoryRange::new(col)))
122 }
123 fn columns(&self) -> Vec<String> {
124 self.headers.clone()
125 }
126 fn data_height(&self) -> usize {
127 self.data.len()
128 }
129 fn has_headers(&self) -> bool {
130 true
131 }
132 fn has_totals(&self) -> bool {
133 self.totals.is_some()
134 }
135 fn headers_row(&self) -> Option<Box<dyn Range>> {
136 Some(Box::new(crate::traits::InMemoryRange::new(vec![
137 self.headers
138 .iter()
139 .cloned()
140 .map(LiteralValue::Text)
141 .collect(),
142 ])))
143 }
144 fn totals_row(&self) -> Option<Box<dyn Range>> {
145 self.totals.as_ref().map(|t| {
146 Box::new(crate::traits::InMemoryRange::new(vec![t.clone()])) as Box<dyn Range>
147 })
148 }
149 fn data_body(&self) -> Option<Box<dyn Range>> {
150 Some(Box::new(crate::traits::InMemoryRange::new(
151 self.data.clone(),
152 )))
153 }
154 fn clone_box(&self) -> Box<dyn Table> {
155 Box::new(SimpleTable {
156 headers: self.headers.clone(),
157 data: self.data.clone(),
158 totals: self.totals.clone(),
159 })
160 }
161 }
162 let table = SimpleTable {
163 headers,
164 data,
165 totals,
166 };
167 self.tables.insert(name.into(), Box::new(table));
168 self
169 }
170
171 pub fn with_function(mut self, func: Arc<dyn Function>) -> Self {
173 let ns = func.namespace().to_uppercase();
174 let name = func.name().to_uppercase();
175 for &alias in func.aliases() {
176 if !alias.eq_ignore_ascii_case(&name) {
177 let akey = (ns.clone(), alias.to_uppercase());
179 self.aliases.insert(akey, (ns.clone(), name.clone()));
180 }
181 }
182 self.fns.insert((ns, name), func);
183 self
184 }
185
186 pub fn with_alias<S: AsRef<str>>(
188 mut self,
189 ns: S,
190 alias: S,
191 target_ns: S,
192 target_name: S,
193 ) -> Self {
194 let key = (ns.as_ref().to_uppercase(), alias.as_ref().to_uppercase());
195 let val = (
196 target_ns.as_ref().to_uppercase(),
197 target_name.as_ref().to_uppercase(),
198 );
199 self.aliases.insert(key, val);
200 self
201 }
202
203 pub fn interpreter(&self) -> crate::interpreter::Interpreter<'_> {
205 crate::interpreter::Interpreter::new(self, "Sheet1")
206 }
207}
208
209impl EvaluationContext for TestWorkbook {
211 fn resolve_range_view<'c>(
212 &'c self,
213 reference: &ReferenceType,
214 _current_sheet: &str,
215 ) -> Result<RangeView<'c>, ExcelError> {
216 use formualizer_parse::parser::ReferenceType as RT;
217 match reference {
218 RT::Cell { sheet, row, col } => {
220 let v = match self.resolve_cell_reference(sheet.as_deref(), *row, *col) {
221 Ok(val) => val,
222 Err(e) => V::Error(e),
223 };
224 let owned = vec![vec![v]];
225 Ok(RangeView::from_borrowed(Box::leak(Box::new(owned))))
226 }
227 RT::NamedRange(name) => {
229 let rows = self.resolve_named_range_reference(name)?;
230 Ok(RangeView::from_borrowed(Box::leak(Box::new(rows))))
231 }
232 _ => {
234 let range_box = self.resolve_range_like(reference)?;
235 let owned: Vec<Vec<V>> = range_box.materialise().into_owned();
236 Ok(RangeView::from_borrowed(Box::leak(Box::new(owned))))
237 }
238 }
239 }
240
241 fn used_rows_for_columns(
242 &self,
243 sheet: &str,
244 start_col: u32,
245 end_col: u32,
246 ) -> Option<(u32, u32)> {
247 let sh = self.sheets.get(sheet)?;
248 let mut min_r: Option<u32> = None;
249 let mut max_r: Option<u32> = None;
250 for &(r, c) in sh.cells.keys() {
251 if c >= start_col && c <= end_col {
252 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
253 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
254 }
255 }
256 match (min_r, max_r) {
257 (Some(a), Some(b)) => Some((a, b)),
258 _ => None,
259 }
260 }
261
262 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
263 let sh = self.sheets.get(sheet)?;
264 let mut min_c: Option<u32> = None;
265 let mut max_c: Option<u32> = None;
266 for &(r, c) in sh.cells.keys() {
267 if r >= start_row && r <= end_row {
268 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
269 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
270 }
271 }
272 match (min_c, max_c) {
273 (Some(a), Some(b)) => Some((a, b)),
274 _ => None,
275 }
276 }
277
278 fn sheet_bounds(&self, _sheet: &str) -> Option<(u32, u32)> {
279 Some((1_048_576, 16_384))
280 }
281
282 fn backend_caps(&self) -> crate::traits::BackendCaps {
283 crate::traits::BackendCaps {
284 streaming: false,
285 used_region: true,
286 write: false,
287 tables: false,
288 async_stream: false,
289 }
290 }
291}
292impl ReferenceResolver for TestWorkbook {
293 fn resolve_cell_reference(
294 &self,
295 sheet: Option<&str>,
296 row: u32,
297 col: u32,
298 ) -> Result<V, ExcelError> {
299 let sheet_name = sheet.unwrap_or("Sheet1");
300 self.sheets
301 .get(sheet_name)
302 .and_then(|sh| sh.cells.get(&(row, col)).cloned())
303 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Ref))
304 }
305}
306
307impl RangeResolver for TestWorkbook {
308 fn resolve_range_reference(
309 &self,
310 sheet: Option<&str>,
311 sr: Option<u32>,
312 sc: Option<u32>,
313 er: Option<u32>,
314 ec: Option<u32>,
315 ) -> Result<Box<dyn Range>, ExcelError> {
316 let (sr, sc, er, ec) = match (sr, sc, er, ec) {
317 (Some(sr), Some(sc), Some(er), Some(ec)) => (sr, sc, er, ec),
318 _ => return Err(ExcelError::from(ExcelErrorKind::NImpl)),
319 };
320 let sheet_name = sheet.unwrap_or("Sheet1");
321 let sh = self
322 .sheets
323 .get(sheet_name)
324 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Ref))?;
325 let mut data = Vec::with_capacity((er - sr + 1) as usize);
326 for r in sr..=er {
327 let mut row_vec = Vec::with_capacity((ec - sc + 1) as usize);
328 for c in sc..=ec {
329 row_vec.push(sh.cells.get(&(r, c)).cloned().unwrap_or(V::Empty));
330 }
331 data.push(row_vec);
332 }
333 Ok(Box::new(crate::traits::InMemoryRange::new(data)))
334 }
335}
336
337impl NamedRangeResolver for TestWorkbook {
338 fn resolve_named_range_reference(&self, name: &str) -> Result<Vec<Vec<V>>, ExcelError> {
339 self.named
340 .get(name)
341 .cloned()
342 .ok_or_else(|| ExcelError::from(ExcelErrorKind::Name))
343 }
344}
345
346impl TableResolver for TestWorkbook {
347 fn resolve_table_reference(&self, tref: &TableReference) -> Result<Box<dyn Table>, ExcelError> {
348 self.tables
349 .get(&tref.name)
350 .map(|table_box| table_box.as_ref().clone_box())
351 .ok_or_else(|| ExcelError::from(ExcelErrorKind::NImpl))
352 }
353}
354
355impl FunctionProvider for TestWorkbook {
356 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
357 let nns = ns.to_uppercase();
358 let nname = name.to_uppercase();
359 if let Some(f) = self.fns.get(&(nns.clone(), nname.clone())) {
361 return Some(f.clone());
362 }
363 if let Some((t_ns, t_name)) = self.aliases.get(&(nns.clone(), nname.clone())) {
365 if let Some(f) = self.fns.get(&(t_ns.clone(), t_name.clone())) {
366 return Some(f.clone());
367 }
368 }
369 crate::function_registry::get(&nns, &nname)
371 }
372}
373
374impl Resolver for TestWorkbook {}
376
377fn parse_a1(a1: &str) -> Option<(u32, u32)> {
379 let s = a1.replace('$', "").to_uppercase();
380 let mut col = 0u32;
381 let mut row_str = String::new();
382 for ch in s.chars() {
383 if ch.is_ascii_alphabetic() {
384 col = col * 26 + (ch as u32 - 'A' as u32 + 1);
385 } else if ch.is_ascii_digit() {
386 row_str.push(ch);
387 } else {
388 return None;
389 }
390 }
391 if col == 0 || row_str.is_empty() {
392 return None;
393 }
394 let row = row_str.parse::<u32>().ok()?;
395 Some((col, row))
396}