1use crate::traits::SpreadsheetReader;
2use crate::traits::{DefinedNameDefinition, DefinedNameScope};
3use formualizer_common::{
4 LiteralValue,
5 error::{ExcelError, ExcelErrorKind},
6};
7use formualizer_eval::function::Function;
8use formualizer_eval::traits::{
9 FunctionProvider, InMemoryRange, NamedRangeResolver, Range, RangeResolver, ReferenceResolver,
10 Resolver, Table, TableResolver,
11};
12use formualizer_parse::parser::TableReference;
13use parking_lot::RwLock;
14use std::sync::Arc;
15
16pub struct IoResolver<B: SpreadsheetReader> {
18 backend: RwLock<B>,
19}
20
21impl<B: SpreadsheetReader> IoResolver<B> {
22 pub fn new(backend: B) -> Self {
23 Self {
24 backend: RwLock::new(backend),
25 }
26 }
27}
28
29impl<B: SpreadsheetReader> RangeResolver for IoResolver<B> {
32 fn resolve_range_reference(
33 &self,
34 sheet: Option<&str>,
35 sr: Option<u32>,
36 sc: Option<u32>,
37 er: Option<u32>,
38 ec: Option<u32>,
39 ) -> Result<Box<dyn Range>, ExcelError> {
40 let sheet_name = sheet.ok_or_else(|| {
41 ExcelError::new(ExcelErrorKind::Ref).with_message("Missing sheet name")
42 })?;
43 let (sr, sc, er, ec) = normalize_range(sr, sc, er, ec)?;
44
45 let mut guard = self.backend.write();
47 let map = guard
48 .read_range(sheet_name, (sr, sc), (er, ec))
49 .map_err(|e| ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string()))?;
50
51 let height = (er - sr + 1) as usize;
52 let width = (ec - sc + 1) as usize;
53 let mut rows = vec![vec![LiteralValue::Empty; width]; height];
54 for ((r, c), cell) in map.into_iter() {
55 let rr = (r - sr) as usize;
56 let cc = (c - sc) as usize;
57 if let Some(v) = cell.value {
58 rows[rr][cc] = v;
59 } else {
60 rows[rr][cc] = LiteralValue::Empty;
61 }
62 }
63 Ok(Box::new(InMemoryRange::new(rows)))
64 }
65}
66
67impl<B: SpreadsheetReader> NamedRangeResolver for IoResolver<B> {
68 fn resolve_named_range_reference(
69 &self,
70 _name: &str,
71 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
72 if !self.backend.read().capabilities().named_ranges {
74 return Err(ExcelError::new(ExcelErrorKind::Name)
75 .with_message("Backend doesn't support named ranges"));
76 }
77
78 let name = _name;
79
80 let mut guard = self.backend.write();
81 let defined = guard
82 .defined_names()
83 .map_err(|e| ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string()))?;
84
85 let mut matches = defined
88 .into_iter()
89 .filter(|dn| dn.name == name)
90 .collect::<Vec<_>>();
91
92 if matches.is_empty() {
93 return Err(ExcelError::new(ExcelErrorKind::Name)
94 .with_message(format!("Undefined name: {name}")));
95 }
96
97 let chosen = if let Some(wb) = matches
99 .iter()
100 .position(|dn| matches!(dn.scope, DefinedNameScope::Workbook))
101 {
102 if matches.len() > 1 {
104 return Err(ExcelError::new(ExcelErrorKind::Name)
105 .with_message(format!("Ambiguous name without sheet context: {name}")));
106 }
107 matches.swap_remove(wb)
108 } else {
109 if matches.len() != 1 {
110 return Err(ExcelError::new(ExcelErrorKind::Name)
111 .with_message(format!("Ambiguous name without sheet context: {name}")));
112 }
113 matches.pop().unwrap()
114 };
115
116 match chosen.definition {
117 DefinedNameDefinition::Range { address } => {
118 let range = guard
120 .read_range(
121 &address.sheet,
122 (address.start_row, address.start_col),
123 (address.end_row, address.end_col),
124 )
125 .map_err(|e| {
126 ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string())
127 })?;
128
129 let h = (address.end_row - address.start_row + 1) as usize;
130 let w = (address.end_col - address.start_col + 1) as usize;
131 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
132 for ((r, c), cell) in range.into_iter() {
133 let rr = (r - address.start_row) as usize;
134 let cc = (c - address.start_col) as usize;
135 rows[rr][cc] = cell.value.unwrap_or(LiteralValue::Empty);
136 }
137 Ok(rows)
138 }
139 DefinedNameDefinition::Literal { value } => Ok(vec![vec![value]]),
140 }
141 }
142}
143
144impl<B: SpreadsheetReader> TableResolver for IoResolver<B> {
145 fn resolve_table_reference(
146 &self,
147 _tref: &TableReference,
148 ) -> Result<Box<dyn Table>, ExcelError> {
149 if !self.backend.read().capabilities().tables {
151 return Err(ExcelError::new(ExcelErrorKind::NImpl)
152 .with_message("Backend doesn't support tables"));
153 }
154
155 let tref = _tref;
156
157 let mut guard = self.backend.write();
159 let sheets = guard
160 .sheet_names()
161 .map_err(|e| ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string()))?;
162
163 let mut found: Option<(String, crate::traits::TableDefinition)> = None;
164 for s in sheets {
165 let sd = guard
166 .read_sheet(&s)
167 .map_err(|e| ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string()))?;
168 if let Some(td) = sd.tables.into_iter().find(|t| t.name == tref.name) {
169 found = Some((s, td));
170 break;
171 }
172 }
173
174 let (sheet, table) = found.ok_or_else(|| {
175 ExcelError::new(ExcelErrorKind::Name)
176 .with_message(format!("Undefined table: {}", tref.name))
177 })?;
178
179 let (sr, sc, er, ec) = table.range;
180 let map = guard
181 .read_range(&sheet, (sr, sc), (er, ec))
182 .map_err(|e| ExcelError::new(ExcelErrorKind::NImpl).with_message(e.to_string()))?;
183 let height = (er - sr + 1) as usize;
184 let width = (ec - sc + 1) as usize;
185 let mut rows = vec![vec![LiteralValue::Empty; width]; height];
186 for ((r, c), cell) in map.into_iter() {
187 let rr = (r - sr) as usize;
188 let cc = (c - sc) as usize;
189 rows[rr][cc] = cell.value.unwrap_or(LiteralValue::Empty);
190 }
191
192 Ok(Box::new(BackendTable {
193 headers: table.headers,
194 header_row: table.header_row,
195 totals_row: table.totals_row,
196 full: rows,
197 }))
198 }
199}
200
201#[derive(Clone, Debug)]
202struct BackendTable {
203 headers: Vec<String>,
204 header_row: bool,
205 totals_row: bool,
206 full: Vec<Vec<LiteralValue>>, }
208
209impl BackendTable {
210 fn col_index(&self, header: &str) -> Option<usize> {
211 self.headers
212 .iter()
213 .position(|h| h.eq_ignore_ascii_case(header))
214 }
215
216 fn body_bounds(&self) -> (usize, usize) {
217 let h = self.full.len();
218 let start = if self.header_row { 1 } else { 0 };
219 let end_exclusive = if self.totals_row && h > 0 {
220 h.saturating_sub(1)
221 } else {
222 h
223 };
224 (start.min(h), end_exclusive.min(h))
225 }
226}
227
228impl Table for BackendTable {
229 fn get_cell(&self, r: usize, c: &str) -> Result<LiteralValue, ExcelError> {
230 let idx = self.col_index(c).ok_or_else(|| {
231 ExcelError::new(ExcelErrorKind::Ref)
232 .with_message("Column refers to unknown table column".to_string())
233 })?;
234 let (start, end_excl) = self.body_bounds();
235 let body_h = end_excl.saturating_sub(start);
236 if r >= body_h {
237 return Err(ExcelError::new(ExcelErrorKind::Ref)
238 .with_message("Row out of range for table data".to_string()));
239 }
240 Ok(self.full[start + r]
241 .get(idx)
242 .cloned()
243 .unwrap_or(LiteralValue::Empty))
244 }
245
246 fn get_column(&self, c: &str) -> Result<Box<dyn Range>, ExcelError> {
247 let idx = self.col_index(c).ok_or_else(|| {
248 ExcelError::new(ExcelErrorKind::Ref)
249 .with_message("Column refers to unknown table column".to_string())
250 })?;
251 let (start, end_excl) = self.body_bounds();
252 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(end_excl.saturating_sub(start));
253 for r in start..end_excl {
254 let v = self.full[r]
255 .get(idx)
256 .cloned()
257 .unwrap_or(LiteralValue::Empty);
258 out.push(vec![v]);
259 }
260 Ok(Box::new(InMemoryRange::new(out)))
261 }
262
263 fn columns(&self) -> Vec<String> {
264 self.headers.clone()
265 }
266
267 fn data_height(&self) -> usize {
268 let (start, end_excl) = self.body_bounds();
269 end_excl.saturating_sub(start)
270 }
271
272 fn has_headers(&self) -> bool {
273 self.header_row
274 }
275
276 fn has_totals(&self) -> bool {
277 self.totals_row
278 }
279
280 fn headers_row(&self) -> Option<Box<dyn Range>> {
281 if !self.header_row || self.full.is_empty() {
282 return None;
283 }
284 Some(Box::new(InMemoryRange::new(vec![self.full[0].clone()])))
285 }
286
287 fn totals_row(&self) -> Option<Box<dyn Range>> {
288 if !self.totals_row || self.full.is_empty() {
289 return None;
290 }
291 Some(Box::new(InMemoryRange::new(vec![
292 self.full[self.full.len() - 1].clone(),
293 ])))
294 }
295
296 fn data_body(&self) -> Option<Box<dyn Range>> {
297 let (start, end_excl) = self.body_bounds();
298 if start >= end_excl {
299 return Some(Box::new(InMemoryRange::new(vec![])));
300 }
301 Some(Box::new(InMemoryRange::new(
302 self.full[start..end_excl].to_vec(),
303 )))
304 }
305
306 fn clone_box(&self) -> Box<dyn Table> {
307 Box::new(self.clone())
308 }
309}
310
311impl<B: SpreadsheetReader> FunctionProvider for IoResolver<B> {
312 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
313 formualizer_eval::function_registry::get(ns, name)
315 }
316}
317
318impl<B: SpreadsheetReader> ReferenceResolver for IoResolver<B> {
321 fn resolve_cell_reference(
322 &self,
323 _sheet: Option<&str>,
324 _row: u32,
325 _col: u32,
326 ) -> Result<LiteralValue, ExcelError> {
327 Err(ExcelError::new(ExcelErrorKind::Ref)
330 .with_message("IoResolver doesn't handle cell references"))
331 }
332}
333
334impl<B: SpreadsheetReader> Resolver for IoResolver<B> {}
335
336fn normalize_range(
337 sr: Option<u32>,
338 sc: Option<u32>,
339 er: Option<u32>,
340 ec: Option<u32>,
341) -> Result<(u32, u32, u32, u32), ExcelError> {
342 let sr = sr.unwrap_or(1);
344 let sc = sc.unwrap_or(1);
345 let er = er.unwrap_or(sr);
346 let ec = ec.unwrap_or(sc);
347
348 if sr > er || sc > ec {
350 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message("Invalid range: start > end"));
351 }
352
353 Ok((sr, sc, er, ec))
354}