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]
56 pub fn new(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
57 Self(RelativeCoord::new(row, col, row_abs, col_abs))
58 }
59
60 #[inline]
61 pub fn from_excel(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
62 let row0 = row.saturating_sub(1);
63 let col0 = col.saturating_sub(1);
64 Self(RelativeCoord::new(row0, col0, row_abs, col_abs))
65 }
66
67 #[inline]
68 pub fn row(self) -> u32 {
69 self.0.row()
70 }
71
72 #[inline]
73 pub fn col(self) -> u32 {
74 self.0.col()
75 }
76
77 #[inline]
78 pub fn row_abs(self) -> bool {
79 self.0.row_abs()
80 }
81
82 #[inline]
83 pub fn col_abs(self) -> bool {
84 self.0.col_abs()
85 }
86
87 #[inline]
88 pub fn with_row_abs(self, abs: bool) -> Self {
89 Self(self.0.with_row_abs(abs))
90 }
91
92 #[inline]
93 pub fn with_col_abs(self, abs: bool) -> Self {
94 Self(self.0.with_col_abs(abs))
95 }
96
97 #[inline]
98 pub fn offset(self, drow: i32, dcol: i32) -> Self {
99 Self(self.0.offset(drow, dcol))
100 }
101
102 #[inline]
103 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
104 Self(self.0.rebase(origin.0, target.0))
105 }
106
107 #[inline]
108 pub fn into_inner(self) -> RelativeCoord {
109 self.0
110 }
111
112 pub fn col_to_letters(col: u32) -> String {
113 RelativeCoord::col_to_letters(col)
114 }
115
116 pub fn letters_to_col(s: &str) -> Option<u32> {
117 RelativeCoord::letters_to_col(s)
118 }
119}
120
121type SheetBounds = (Option<String>, (u32, u32, u32, u32));
122
123pub fn combine_references(
127 a: &ReferenceType,
128 b: &ReferenceType,
129) -> Result<ReferenceType, ExcelError> {
130 fn to_bounds(r: &ReferenceType) -> Option<SheetBounds> {
132 match r {
133 ReferenceType::Cell { sheet, row, col } => {
134 Some((sheet.clone(), (*row, *col, *row, *col)))
135 }
136 ReferenceType::Range {
137 sheet,
138 start_row,
139 start_col,
140 end_row,
141 end_col,
142 } => {
143 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
144 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
145 _ => return None,
146 };
147 Some((sheet.clone(), (sr, sc, er, ec)))
148 }
149 _ => None,
150 }
151 }
152
153 let (sheet_a, (a_sr, a_sc, a_er, a_ec)) = to_bounds(a).ok_or_else(|| {
154 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
155 })?;
156 let (sheet_b, (b_sr, b_sc, b_er, b_ec)) = to_bounds(b).ok_or_else(|| {
157 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
158 })?;
159
160 if sheet_a != sheet_b {
162 return Err(ExcelError::new(ExcelErrorKind::Ref)
163 .with_message("Cannot combine references across sheets"));
164 }
165
166 let sr = a_sr.min(b_sr);
167 let sc = a_sc.min(b_sc);
168 let er = a_er.max(b_er);
169 let ec = a_ec.max(b_ec);
170
171 Ok(ReferenceType::Range {
172 sheet: sheet_a,
173 start_row: Some(sr),
174 start_col: Some(sc),
175 end_row: Some(er),
176 end_col: Some(ec),
177 })
178}
179
180impl fmt::Display for Coord {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 if self.col_abs() {
183 write!(f, "$")?;
184 }
185 write!(f, "{}", Self::col_to_letters(self.col()))?;
186 if self.row_abs() {
187 write!(f, "$")?;
188 }
189 write!(f, "{}", self.row() + 1)
191 }
192}
193
194pub type SheetId = u16; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
204pub struct CellRef {
205 pub sheet_id: SheetId,
206 pub coord: Coord,
207}
208
209impl CellRef {
210 #[inline]
211 pub const fn new(sheet_id: SheetId, coord: Coord) -> Self {
212 Self { sheet_id, coord }
213 }
214
215 #[inline]
216 pub fn new_absolute(sheet_id: SheetId, row: u32, col: u32) -> Self {
217 Self {
218 sheet_id,
219 coord: Coord::new(row, col, true, true),
220 }
221 }
222
223 #[inline]
225 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
226 Self {
227 sheet_id: self.sheet_id,
228 coord: self.coord.rebase(origin, target),
229 }
230 }
231
232 #[inline]
233 pub fn sheet_name<'a>(&self, sheet_reg: &'a SheetRegistry) -> &'a str {
234 sheet_reg.name(self.sheet_id)
235 }
236
237 #[inline]
238 pub fn to_shared(self) -> SharedCellRef<'static> {
239 SharedCellRef::new(
240 SharedSheetLocator::Id(self.sheet_id),
241 self.coord.into_inner(),
242 )
243 }
244
245 pub fn try_from_shared(cell: SharedCellRef<'_>) -> Result<Self, ExcelError> {
246 let owned = cell.into_owned();
247 let sheet_id = match owned.sheet {
248 SharedSheetLocator::Id(id) => id,
249 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
250 };
251 Ok(Self::new(sheet_id, Coord(owned.coord)))
252 }
253}
254
255impl fmt::Display for CellRef {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "Sheet{}!", self.sheet_id)?;
259 write!(f, "{}", self.coord)
260 }
261}
262
263#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
268pub struct RangeRef {
269 pub start: CellRef,
270 pub end: CellRef, }
272
273impl RangeRef {
274 #[inline]
275 pub const fn new(start: CellRef, end: CellRef) -> Self {
276 Self { start, end }
277 }
278
279 pub fn try_to_shared(self) -> Result<SharedRangeRef<'static>, ExcelError> {
280 if self.start.sheet_id != self.end.sheet_id {
281 return Err(ExcelError::new(ExcelErrorKind::Ref));
282 }
283 let sheet = SharedSheetLocator::Id(self.start.sheet_id);
284 let sr =
285 formualizer_common::AxisBound::new(self.start.coord.row(), self.start.coord.row_abs());
286 let sc =
287 formualizer_common::AxisBound::new(self.start.coord.col(), self.start.coord.col_abs());
288 let er = formualizer_common::AxisBound::new(self.end.coord.row(), self.end.coord.row_abs());
289 let ec = formualizer_common::AxisBound::new(self.end.coord.col(), self.end.coord.col_abs());
290 SharedRangeRef::from_parts(sheet, Some(sr), Some(sc), Some(er), Some(ec))
291 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))
292 }
293
294 pub fn try_from_shared(range: SharedRangeRef<'_>) -> Result<Self, ExcelError> {
295 let owned = range.into_owned();
296 let sheet_id = match owned.sheet {
297 SharedSheetLocator::Id(id) => id,
298 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
299 };
300 let (sr, sc, er, ec) = match (
301 owned.start_row,
302 owned.start_col,
303 owned.end_row,
304 owned.end_col,
305 ) {
306 (Some(sr), Some(sc), Some(er), Some(ec)) => (sr, sc, er, ec),
307 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
308 };
309 let start = CellRef::new(sheet_id, Coord::new(sr.index, sc.index, sr.abs, sc.abs));
310 let end = CellRef::new(sheet_id, Coord::new(er.index, ec.index, er.abs, ec.abs));
311 Ok(Self::new(start, end))
312 }
313}
314
315impl fmt::Display for RangeRef {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 if self.start.sheet_id == self.end.sheet_id {
318 write!(f, "{}:{}", self.start, self.end.coord)
320 } else {
321 write!(f, "{}:{}", self.start, self.end)
323 }
324 }
325}
326
327#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_display_coord() {
337 let c = Coord::new(0, 0, false, false);
338 assert_eq!(c.to_string(), "A1");
339 let c = Coord::new(7, 27, true, true); assert_eq!(c.to_string(), "$AB$8");
341 }
342
343 #[test]
344 fn test_rebase() {
345 let origin = Coord::new(0, 0, false, false);
346 let target = Coord::new(1, 1, false, false);
347 let formula_coord = Coord::new(2, 0, false, true); let rebased = formula_coord.rebase(origin, target);
349 assert_eq!(rebased, Coord::new(3, 0, false, true));
351 }
352
353 #[test]
354 fn test_range_display() {
355 let a1 = CellRef::new(0, Coord::new(0, 0, false, false));
356 let b2 = CellRef::new(0, Coord::new(1, 1, false, false));
357 let r = RangeRef::new(a1, b2);
358 assert_eq!(r.to_string(), "Sheet0!A1:B2");
359 }
360}