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