1use core::fmt;
24
25use crate::engine::sheet_registry::SheetRegistry; use formualizer_common::{
27 ExcelError, ExcelErrorKind, RelativeCoord, SheetCellRef as CommonSheetCellRef,
28 SheetId as CommonSheetId, SheetLocator as CommonSheetLocator,
29 SheetRangeRef as CommonSheetRangeRef, SheetRef as CommonSheetRef,
30};
31use formualizer_parse::parser::ReferenceType;
32
33pub type SharedSheetId = CommonSheetId;
38pub type SharedSheetLocator<'a> = CommonSheetLocator<'a>;
39pub type SharedCellRef<'a> = CommonSheetCellRef<'a>;
40pub type SharedRangeRef<'a> = CommonSheetRangeRef<'a>;
41pub type SharedRef<'a> = CommonSheetRef<'a>;
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
52pub struct Coord(RelativeCoord);
53
54impl Coord {
55 #[inline]
63 pub fn new(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
64 Self(RelativeCoord::new(row, col, row_abs, col_abs))
65 }
66
67 #[inline]
70 pub fn try_new(
71 row: u32,
72 col: u32,
73 row_abs: bool,
74 col_abs: bool,
75 ) -> Result<Self, formualizer_common::CoordError> {
76 RelativeCoord::try_new(row, col, row_abs, col_abs).map(Self)
77 }
78
79 #[inline]
85 pub fn from_excel(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
86 let row0 = row.saturating_sub(1);
87 let col0 = col.saturating_sub(1);
88 Self(RelativeCoord::new(row0, col0, row_abs, col_abs))
89 }
90
91 #[inline]
92 pub fn row(self) -> u32 {
93 self.0.row()
94 }
95
96 #[inline]
97 pub fn col(self) -> u32 {
98 self.0.col()
99 }
100
101 #[inline]
102 pub fn row_abs(self) -> bool {
103 self.0.row_abs()
104 }
105
106 #[inline]
107 pub fn col_abs(self) -> bool {
108 self.0.col_abs()
109 }
110
111 #[inline]
112 pub fn with_row_abs(self, abs: bool) -> Self {
113 Self(self.0.with_row_abs(abs))
114 }
115
116 #[inline]
117 pub fn with_col_abs(self, abs: bool) -> Self {
118 Self(self.0.with_col_abs(abs))
119 }
120
121 #[inline]
122 pub fn offset(self, drow: i32, dcol: i32) -> Self {
123 Self(self.0.offset(drow, dcol))
124 }
125
126 #[inline]
127 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
128 Self(self.0.rebase(origin.0, target.0))
129 }
130
131 #[inline]
132 pub fn into_inner(self) -> RelativeCoord {
133 self.0
134 }
135
136 pub fn col_to_letters(col: u32) -> String {
137 RelativeCoord::col_to_letters(col)
138 }
139
140 pub fn letters_to_col(s: &str) -> Option<u32> {
141 RelativeCoord::letters_to_col(s)
142 }
143}
144
145type SheetBounds = (Option<String>, (u32, u32, u32, u32));
146
147pub fn combine_references(
152 a: &ReferenceType,
153 b: &ReferenceType,
154) -> Result<ReferenceType, ExcelError> {
155 fn to_bounds(r: &ReferenceType) -> Option<SheetBounds> {
157 match r {
158 ReferenceType::Cell {
159 sheet, row, col, ..
160 } => Some((sheet.clone(), (*row, *col, *row, *col))),
161 ReferenceType::Range {
162 sheet,
163 start_row,
164 start_col,
165 end_row,
166 end_col,
167 ..
168 } => {
169 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
170 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
171 _ => return None,
172 };
173 Some((sheet.clone(), (sr, sc, er, ec)))
174 }
175 _ => None,
176 }
177 }
178
179 for endpoint in [a, b] {
183 if let ReferenceType::NamedRange(name) = endpoint {
184 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
185 "Defined name '{name}' used as a range endpoint (':') is not supported yet"
186 )));
187 }
188 }
189
190 let (sheet_a, (a_sr, a_sc, a_er, a_ec)) = to_bounds(a).ok_or_else(|| {
191 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
192 })?;
193 let (sheet_b, (b_sr, b_sc, b_er, b_ec)) = to_bounds(b).ok_or_else(|| {
194 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
195 })?;
196
197 if sheet_a != sheet_b {
199 return Err(ExcelError::new(ExcelErrorKind::Ref)
200 .with_message("Cannot combine references across sheets"));
201 }
202
203 let sr = a_sr.min(b_sr);
204 let sc = a_sc.min(b_sc);
205 let er = a_er.max(b_er);
206 let ec = a_ec.max(b_ec);
207
208 Ok(ReferenceType::Range {
209 sheet: sheet_a,
210 start_row: Some(sr),
211 start_col: Some(sc),
212 end_row: Some(er),
213 end_col: Some(ec),
214 start_row_abs: false,
215 start_col_abs: false,
216 end_row_abs: false,
217 end_col_abs: false,
218 })
219}
220
221impl fmt::Display for Coord {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 if self.col_abs() {
224 write!(f, "$")?;
225 }
226 write!(f, "{}", Self::col_to_letters(self.col()))?;
227 if self.row_abs() {
228 write!(f, "$")?;
229 }
230 write!(f, "{}", self.row() + 1)
232 }
233}
234
235pub type SheetId = u16; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
245pub struct CellRef {
246 pub sheet_id: SheetId,
247 pub coord: Coord,
248}
249
250impl CellRef {
251 #[inline]
252 pub const fn new(sheet_id: SheetId, coord: Coord) -> Self {
253 Self { sheet_id, coord }
254 }
255
256 #[inline]
257 pub fn new_absolute(sheet_id: SheetId, row: u32, col: u32) -> Self {
258 Self {
259 sheet_id,
260 coord: Coord::new(row, col, true, true),
261 }
262 }
263
264 #[inline]
266 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
267 Self {
268 sheet_id: self.sheet_id,
269 coord: self.coord.rebase(origin, target),
270 }
271 }
272
273 #[inline]
274 pub fn sheet_name<'a>(&self, sheet_reg: &'a SheetRegistry) -> &'a str {
275 sheet_reg.name(self.sheet_id)
276 }
277
278 #[inline]
279 pub fn to_shared(self) -> SharedCellRef<'static> {
280 SharedCellRef::new(
281 SharedSheetLocator::Id(self.sheet_id),
282 self.coord.into_inner(),
283 )
284 }
285
286 pub fn try_from_shared(cell: SharedCellRef<'_>) -> Result<Self, ExcelError> {
290 let owned = cell.into_owned();
291 let sheet_id = match owned.sheet {
292 SharedSheetLocator::Id(id) => id,
293 SharedSheetLocator::Current | SharedSheetLocator::Name(_) => {
294 return Err(ExcelError::new(ExcelErrorKind::Ref));
295 }
296 };
297 Ok(Self::new(sheet_id, Coord(owned.coord)))
298 }
299}
300
301impl fmt::Display for CellRef {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 write!(f, "Sheet{}!", self.sheet_id)?;
305 write!(f, "{}", self.coord)
306 }
307}
308
309#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
314pub struct RangeRef {
315 pub start: CellRef,
316 pub end: CellRef, }
318
319impl RangeRef {
320 #[inline]
321 pub const fn new(start: CellRef, end: CellRef) -> Self {
322 Self { start, end }
323 }
324
325 pub fn try_to_shared(self) -> Result<SharedRangeRef<'static>, ExcelError> {
326 if self.start.sheet_id != self.end.sheet_id {
327 return Err(ExcelError::new(ExcelErrorKind::Ref));
328 }
329 let sheet = SharedSheetLocator::Id(self.start.sheet_id);
330 let sr =
331 formualizer_common::AxisBound::new(self.start.coord.row(), self.start.coord.row_abs());
332 let sc =
333 formualizer_common::AxisBound::new(self.start.coord.col(), self.start.coord.col_abs());
334 let er = formualizer_common::AxisBound::new(self.end.coord.row(), self.end.coord.row_abs());
335 let ec = formualizer_common::AxisBound::new(self.end.coord.col(), self.end.coord.col_abs());
336 SharedRangeRef::from_parts(sheet, Some(sr), Some(sc), Some(er), Some(ec))
337 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))
338 }
339
340 pub fn try_from_shared(range: SharedRangeRef<'_>) -> Result<Self, ExcelError> {
342 let owned = range.into_owned();
343 let sheet_id = match owned.sheet {
344 SharedSheetLocator::Id(id) => id,
345 SharedSheetLocator::Current | SharedSheetLocator::Name(_) => {
346 return Err(ExcelError::new(ExcelErrorKind::Ref));
347 }
348 };
349 let (sr, sc, er, ec) = match (
350 owned.start_row,
351 owned.start_col,
352 owned.end_row,
353 owned.end_col,
354 ) {
355 (Some(sr), Some(sc), Some(er), Some(ec)) => (sr, sc, er, ec),
356 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
357 };
358 let start = CellRef::new(sheet_id, Coord::new(sr.index, sc.index, sr.abs, sc.abs));
359 let end = CellRef::new(sheet_id, Coord::new(er.index, ec.index, er.abs, ec.abs));
360 Ok(Self::new(start, end))
361 }
362}
363
364impl fmt::Display for RangeRef {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 if self.start.sheet_id == self.end.sheet_id {
367 write!(f, "{}:{}", self.start, self.end.coord)
369 } else {
370 write!(f, "{}:{}", self.start, self.end)
372 }
373 }
374}
375
376#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn test_display_coord() {
386 let c = Coord::new(0, 0, false, false);
387 assert_eq!(c.to_string(), "A1");
388 let c = Coord::new(7, 27, true, true); assert_eq!(c.to_string(), "$AB$8");
390 }
391
392 #[test]
393 fn test_rebase() {
394 let origin = Coord::new(0, 0, false, false);
395 let target = Coord::new(1, 1, false, false);
396 let formula_coord = Coord::new(2, 0, false, true); let rebased = formula_coord.rebase(origin, target);
398 assert_eq!(rebased, Coord::new(3, 0, false, true));
400 }
401
402 #[test]
403 fn test_range_display() {
404 let a1 = CellRef::new(0, Coord::new(0, 0, false, false));
405 let b2 = CellRef::new(0, Coord::new(1, 1, false, false));
406 let r = RangeRef::new(a1, b2);
407 assert_eq!(r.to_string(), "Sheet0!A1:B2");
408 }
409}