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(
128 a: &ReferenceType,
129 b: &ReferenceType,
130) -> Result<ReferenceType, ExcelError> {
131 fn to_bounds(r: &ReferenceType) -> Option<SheetBounds> {
133 match r {
134 ReferenceType::Cell {
135 sheet, row, col, ..
136 } => Some((sheet.clone(), (*row, *col, *row, *col))),
137 ReferenceType::Range {
138 sheet,
139 start_row,
140 start_col,
141 end_row,
142 end_col,
143 ..
144 } => {
145 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
146 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
147 _ => return None,
148 };
149 Some((sheet.clone(), (sr, sc, er, ec)))
150 }
151 _ => None,
152 }
153 }
154
155 for endpoint in [a, b] {
159 if let ReferenceType::NamedRange(name) = endpoint {
160 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
161 "Defined name '{name}' used as a range endpoint (':') is not supported yet"
162 )));
163 }
164 }
165
166 let (sheet_a, (a_sr, a_sc, a_er, a_ec)) = to_bounds(a).ok_or_else(|| {
167 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
168 })?;
169 let (sheet_b, (b_sr, b_sc, b_er, b_ec)) = to_bounds(b).ok_or_else(|| {
170 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
171 })?;
172
173 if sheet_a != sheet_b {
175 return Err(ExcelError::new(ExcelErrorKind::Ref)
176 .with_message("Cannot combine references across sheets"));
177 }
178
179 let sr = a_sr.min(b_sr);
180 let sc = a_sc.min(b_sc);
181 let er = a_er.max(b_er);
182 let ec = a_ec.max(b_ec);
183
184 Ok(ReferenceType::Range {
185 sheet: sheet_a,
186 start_row: Some(sr),
187 start_col: Some(sc),
188 end_row: Some(er),
189 end_col: Some(ec),
190 start_row_abs: false,
191 start_col_abs: false,
192 end_row_abs: false,
193 end_col_abs: false,
194 })
195}
196
197impl fmt::Display for Coord {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 if self.col_abs() {
200 write!(f, "$")?;
201 }
202 write!(f, "{}", Self::col_to_letters(self.col()))?;
203 if self.row_abs() {
204 write!(f, "$")?;
205 }
206 write!(f, "{}", self.row() + 1)
208 }
209}
210
211pub type SheetId = u16; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
221pub struct CellRef {
222 pub sheet_id: SheetId,
223 pub coord: Coord,
224}
225
226impl CellRef {
227 #[inline]
228 pub const fn new(sheet_id: SheetId, coord: Coord) -> Self {
229 Self { sheet_id, coord }
230 }
231
232 #[inline]
233 pub fn new_absolute(sheet_id: SheetId, row: u32, col: u32) -> Self {
234 Self {
235 sheet_id,
236 coord: Coord::new(row, col, true, true),
237 }
238 }
239
240 #[inline]
242 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
243 Self {
244 sheet_id: self.sheet_id,
245 coord: self.coord.rebase(origin, target),
246 }
247 }
248
249 #[inline]
250 pub fn sheet_name<'a>(&self, sheet_reg: &'a SheetRegistry) -> &'a str {
251 sheet_reg.name(self.sheet_id)
252 }
253
254 #[inline]
255 pub fn to_shared(self) -> SharedCellRef<'static> {
256 SharedCellRef::new(
257 SharedSheetLocator::Id(self.sheet_id),
258 self.coord.into_inner(),
259 )
260 }
261
262 pub fn try_from_shared(cell: SharedCellRef<'_>) -> Result<Self, ExcelError> {
266 let owned = cell.into_owned();
267 let sheet_id = match owned.sheet {
268 SharedSheetLocator::Id(id) => id,
269 SharedSheetLocator::Current | SharedSheetLocator::Name(_) => {
270 return Err(ExcelError::new(ExcelErrorKind::Ref));
271 }
272 };
273 Ok(Self::new(sheet_id, Coord(owned.coord)))
274 }
275}
276
277impl fmt::Display for CellRef {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 write!(f, "Sheet{}!", self.sheet_id)?;
281 write!(f, "{}", self.coord)
282 }
283}
284
285#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
290pub struct RangeRef {
291 pub start: CellRef,
292 pub end: CellRef, }
294
295impl RangeRef {
296 #[inline]
297 pub const fn new(start: CellRef, end: CellRef) -> Self {
298 Self { start, end }
299 }
300
301 pub fn try_to_shared(self) -> Result<SharedRangeRef<'static>, ExcelError> {
302 if self.start.sheet_id != self.end.sheet_id {
303 return Err(ExcelError::new(ExcelErrorKind::Ref));
304 }
305 let sheet = SharedSheetLocator::Id(self.start.sheet_id);
306 let sr =
307 formualizer_common::AxisBound::new(self.start.coord.row(), self.start.coord.row_abs());
308 let sc =
309 formualizer_common::AxisBound::new(self.start.coord.col(), self.start.coord.col_abs());
310 let er = formualizer_common::AxisBound::new(self.end.coord.row(), self.end.coord.row_abs());
311 let ec = formualizer_common::AxisBound::new(self.end.coord.col(), self.end.coord.col_abs());
312 SharedRangeRef::from_parts(sheet, Some(sr), Some(sc), Some(er), Some(ec))
313 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))
314 }
315
316 pub fn try_from_shared(range: SharedRangeRef<'_>) -> Result<Self, ExcelError> {
318 let owned = range.into_owned();
319 let sheet_id = match owned.sheet {
320 SharedSheetLocator::Id(id) => id,
321 SharedSheetLocator::Current | SharedSheetLocator::Name(_) => {
322 return Err(ExcelError::new(ExcelErrorKind::Ref));
323 }
324 };
325 let (sr, sc, er, ec) = match (
326 owned.start_row,
327 owned.start_col,
328 owned.end_row,
329 owned.end_col,
330 ) {
331 (Some(sr), Some(sc), Some(er), Some(ec)) => (sr, sc, er, ec),
332 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
333 };
334 let start = CellRef::new(sheet_id, Coord::new(sr.index, sc.index, sr.abs, sc.abs));
335 let end = CellRef::new(sheet_id, Coord::new(er.index, ec.index, er.abs, ec.abs));
336 Ok(Self::new(start, end))
337 }
338}
339
340impl fmt::Display for RangeRef {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 if self.start.sheet_id == self.end.sheet_id {
343 write!(f, "{}:{}", self.start, self.end.coord)
345 } else {
346 write!(f, "{}:{}", self.start, self.end)
348 }
349 }
350}
351
352#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_display_coord() {
362 let c = Coord::new(0, 0, false, false);
363 assert_eq!(c.to_string(), "A1");
364 let c = Coord::new(7, 27, true, true); assert_eq!(c.to_string(), "$AB$8");
366 }
367
368 #[test]
369 fn test_rebase() {
370 let origin = Coord::new(0, 0, false, false);
371 let target = Coord::new(1, 1, false, false);
372 let formula_coord = Coord::new(2, 0, false, true); let rebased = formula_coord.rebase(origin, target);
374 assert_eq!(rebased, Coord::new(3, 0, false, true));
376 }
377
378 #[test]
379 fn test_range_display() {
380 let a1 = CellRef::new(0, Coord::new(0, 0, false, false));
381 let b2 = CellRef::new(0, Coord::new(1, 1, false, false));
382 let r = RangeRef::new(a1, b2);
383 assert_eq!(r.to_string(), "Sheet0!A1:B2");
384 }
385}