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 {
134 sheet, row, col, ..
135 } => Some((sheet.clone(), (*row, *col, *row, *col))),
136 ReferenceType::Range {
137 sheet,
138 start_row,
139 start_col,
140 end_row,
141 end_col,
142 ..
143 } => {
144 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
145 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
146 _ => return None,
147 };
148 Some((sheet.clone(), (sr, sc, er, ec)))
149 }
150 _ => None,
151 }
152 }
153
154 let (sheet_a, (a_sr, a_sc, a_er, a_ec)) = to_bounds(a).ok_or_else(|| {
155 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
156 })?;
157 let (sheet_b, (b_sr, b_sc, b_er, b_ec)) = to_bounds(b).ok_or_else(|| {
158 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
159 })?;
160
161 if sheet_a != sheet_b {
163 return Err(ExcelError::new(ExcelErrorKind::Ref)
164 .with_message("Cannot combine references across sheets"));
165 }
166
167 let sr = a_sr.min(b_sr);
168 let sc = a_sc.min(b_sc);
169 let er = a_er.max(b_er);
170 let ec = a_ec.max(b_ec);
171
172 Ok(ReferenceType::Range {
173 sheet: sheet_a,
174 start_row: Some(sr),
175 start_col: Some(sc),
176 end_row: Some(er),
177 end_col: Some(ec),
178 start_row_abs: false,
179 start_col_abs: false,
180 end_row_abs: false,
181 end_col_abs: false,
182 })
183}
184
185impl fmt::Display for Coord {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 if self.col_abs() {
188 write!(f, "$")?;
189 }
190 write!(f, "{}", Self::col_to_letters(self.col()))?;
191 if self.row_abs() {
192 write!(f, "$")?;
193 }
194 write!(f, "{}", self.row() + 1)
196 }
197}
198
199pub type SheetId = u16; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
209pub struct CellRef {
210 pub sheet_id: SheetId,
211 pub coord: Coord,
212}
213
214impl CellRef {
215 #[inline]
216 pub const fn new(sheet_id: SheetId, coord: Coord) -> Self {
217 Self { sheet_id, coord }
218 }
219
220 #[inline]
221 pub fn new_absolute(sheet_id: SheetId, row: u32, col: u32) -> Self {
222 Self {
223 sheet_id,
224 coord: Coord::new(row, col, true, true),
225 }
226 }
227
228 #[inline]
230 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
231 Self {
232 sheet_id: self.sheet_id,
233 coord: self.coord.rebase(origin, target),
234 }
235 }
236
237 #[inline]
238 pub fn sheet_name<'a>(&self, sheet_reg: &'a SheetRegistry) -> &'a str {
239 sheet_reg.name(self.sheet_id)
240 }
241
242 #[inline]
243 pub fn to_shared(self) -> SharedCellRef<'static> {
244 SharedCellRef::new(
245 SharedSheetLocator::Id(self.sheet_id),
246 self.coord.into_inner(),
247 )
248 }
249
250 pub fn try_from_shared(cell: SharedCellRef<'_>) -> Result<Self, ExcelError> {
251 let owned = cell.into_owned();
252 let sheet_id = match owned.sheet {
253 SharedSheetLocator::Id(id) => id,
254 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
255 };
256 Ok(Self::new(sheet_id, Coord(owned.coord)))
257 }
258}
259
260impl fmt::Display for CellRef {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 write!(f, "Sheet{}!", self.sheet_id)?;
264 write!(f, "{}", self.coord)
265 }
266}
267
268#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
273pub struct RangeRef {
274 pub start: CellRef,
275 pub end: CellRef, }
277
278impl RangeRef {
279 #[inline]
280 pub const fn new(start: CellRef, end: CellRef) -> Self {
281 Self { start, end }
282 }
283
284 pub fn try_to_shared(self) -> Result<SharedRangeRef<'static>, ExcelError> {
285 if self.start.sheet_id != self.end.sheet_id {
286 return Err(ExcelError::new(ExcelErrorKind::Ref));
287 }
288 let sheet = SharedSheetLocator::Id(self.start.sheet_id);
289 let sr =
290 formualizer_common::AxisBound::new(self.start.coord.row(), self.start.coord.row_abs());
291 let sc =
292 formualizer_common::AxisBound::new(self.start.coord.col(), self.start.coord.col_abs());
293 let er = formualizer_common::AxisBound::new(self.end.coord.row(), self.end.coord.row_abs());
294 let ec = formualizer_common::AxisBound::new(self.end.coord.col(), self.end.coord.col_abs());
295 SharedRangeRef::from_parts(sheet, Some(sr), Some(sc), Some(er), Some(ec))
296 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))
297 }
298
299 pub fn try_from_shared(range: SharedRangeRef<'_>) -> Result<Self, ExcelError> {
300 let owned = range.into_owned();
301 let sheet_id = match owned.sheet {
302 SharedSheetLocator::Id(id) => id,
303 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
304 };
305 let (sr, sc, er, ec) = match (
306 owned.start_row,
307 owned.start_col,
308 owned.end_row,
309 owned.end_col,
310 ) {
311 (Some(sr), Some(sc), Some(er), Some(ec)) => (sr, sc, er, ec),
312 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
313 };
314 let start = CellRef::new(sheet_id, Coord::new(sr.index, sc.index, sr.abs, sc.abs));
315 let end = CellRef::new(sheet_id, Coord::new(er.index, ec.index, er.abs, ec.abs));
316 Ok(Self::new(start, end))
317 }
318}
319
320impl fmt::Display for RangeRef {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 if self.start.sheet_id == self.end.sheet_id {
323 write!(f, "{}:{}", self.start, self.end.coord)
325 } else {
326 write!(f, "{}:{}", self.start, self.end)
328 }
329 }
330}
331
332#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_display_coord() {
342 let c = Coord::new(0, 0, false, false);
343 assert_eq!(c.to_string(), "A1");
344 let c = Coord::new(7, 27, true, true); assert_eq!(c.to_string(), "$AB$8");
346 }
347
348 #[test]
349 fn test_rebase() {
350 let origin = Coord::new(0, 0, false, false);
351 let target = Coord::new(1, 1, false, false);
352 let formula_coord = Coord::new(2, 0, false, true); let rebased = formula_coord.rebase(origin, target);
354 assert_eq!(rebased, Coord::new(3, 0, false, true));
356 }
357
358 #[test]
359 fn test_range_display() {
360 let a1 = CellRef::new(0, Coord::new(0, 0, false, false));
361 let b2 = CellRef::new(0, Coord::new(1, 1, false, false));
362 let r = RangeRef::new(a1, b2);
363 assert_eq!(r.to_string(), "Sheet0!A1:B2");
364 }
365}