a1_notation/
lib.rs

1//! # a1_notation
2//!
3//! A library for parsing to and from A1 spreadsheet notation. A1 notation uses letters A-Z for
4//! columns and a one-based number for the row.  So for example at position `(0, 0)` of a spreadsheet
5//! (the top left corner) is "A1".  `(1, 1)` is "B2", `(1, 0)` is "B1", etc.  
6//!
7//! ## Instantiating `A1`s
8//!
9//! The most common need is to parse a string:
10//!
11//! ```
12//! use a1_notation::{A1, Address, Column, RangeOrCell, Row};
13//! use std::str::FromStr;
14//!
15//! let a1 = A1::from_str("A1").unwrap();
16//! // it parses it into an instance:
17//! assert_eq!(a1,
18//!     A1 {
19//!         sheet_name: None,
20//!         reference: RangeOrCell::Cell(Address {
21//!             column: Column { absolute: false, x: 0 },
22//!             row: Row { absolute: false, y: 0 },
23//!         }),
24//!     });
25//! // and can display it back:
26//! assert_eq!(&a1.to_string(), "A1");
27//!
28//! // you can also just call `a1_notation::new`:
29//! let from_col_a_to_d = a1_notation::new("Foo!A:D").unwrap();
30//! assert_eq!(from_col_a_to_d,
31//!     A1 {
32//!         sheet_name: Some("Foo".to_string()),
33//!         reference: RangeOrCell::ColumnRange {
34//!             from: Column { absolute: false, x: 0 },
35//!             to: Column { absolute: false, x: 3 },
36//!         },
37//!     });
38//!
39//! assert_eq!(&from_col_a_to_d.to_string(), "Foo!A:D");
40//! ```
41//!
42//! If you have zero-based coordinates and want to represent them as A1, there are several `fn`s
43//! for instantiating:
44//!
45//! ```
46//! # use a1_notation::*;
47//! // to create a reference to a specific cell:
48//! assert_eq!(&a1_notation::cell(2, 2).to_string(), "C3");
49//!
50//! // a reference to an entire column
51//! assert_eq!(&a1_notation::column(5).to_string(), "F:F");
52//!
53//! // or an entire row
54//! assert_eq!(&a1_notation::row(5).to_string(), "6:6");
55//!
56//! // and finally a range between two cells:
57//! assert_eq!(&a1_notation::range((0, 0), (4, 4)).to_string(), "A1:E5");
58//! ```
59//!
60//! ## Contains
61//!
62//! Given all the various combinations or cells, ranges, row ranges, column ranges and
63//! non-contiguous ranges you can calculate if one reference contains another.
64//!
65//! ```
66//! # use a1_notation::*;
67//! // a column contains any cell in that column:
68//! let col_a = a1_notation::new("A:A").unwrap();
69//! let a1 = a1_notation::new("A1").unwrap();
70//! assert!(col_a.contains(&a1));
71//!
72//! // likewise, a row range contains anything between it:
73//! let top_5_rows = a1_notation::new("1:5").unwrap();
74//! let b2 = a1_notation::new("B2").unwrap();
75//! assert!(top_5_rows.contains(&b2));
76//!
77//! // and a range between two points works as you'd expect (it forms a rectangle)
78//! let c3_to_j20 = a1_notation::new("C3:J20").unwrap();
79//! let d5 = a1_notation::new("D5").unwrap();
80//! assert!(c3_to_j20.contains(&d5));
81//! ```
82//!
83//! ## Into/From/AsRef impls
84//!
85//! As much as possible it implements `Into`/`From` and `AsRef` to convert between the various
86//! structs.  Generally you can go from more specific to less specific but not the other way
87//! around.  You typically should work with `A1` structs but you can also use these traits to work
88//! with these lower level ones and cast them upwards.
89//!
90//! ```
91//! # use a1_notation::*;
92//! // an address can act as a column or row using AsRef:
93//! let a1 = Address::new(0, 0);
94//! assert_eq!(&Column::new(0), a1.as_ref());
95//! assert_eq!(&Row::new(0), a1.as_ref());
96//!
97//! // addresses, columns and rows can `into()` "upwards" to an A1 or RangeOrCell
98//! let col_b = Column::new(1);
99//! assert_eq!(
100//!     RangeOrCell::ColumnRange {
101//!         from: Column::new(1),
102//!         to: Column::new(1),
103//!     },
104//!     col_b.into());
105//!
106//! assert_eq!(
107//!     A1 {
108//!         sheet_name: None,
109//!         reference: RangeOrCell::ColumnRange {
110//!             from: Column::new(1),
111//!             to: Column::new(1),
112//!         },
113//!     },
114//!     col_b.into());
115//! ```
116//!
117//! ## Shifting
118//!
119//! You can move references (and ranges) around:
120//!
121//! ```
122//! # use a1_notation::*;
123//! // A1 -> D1 -> D3 -> C3
124//! assert_eq!(
125//!     &a1_notation::cell(0, 0)
126//!         .shift_right(3)
127//!         .shift_down(2)
128//!         .shift_left(1)
129//!         .to_string(),
130//!     "C3");
131//! ```
132//!
133//! ## Iterators
134//!
135//! You can iterate through the various types of ranges.
136//!
137//! ```
138//! # use crate::*;
139//! // a cell just emits itself (once)
140//! assert_eq!(
141//!     a1_notation::cell(0, 0)
142//!         .iter().map(|r| r.to_string()).collect::<Vec<_>>(),
143//!     vec!["A1"]);
144//!
145//! // a column range iterates column-wise
146//! assert_eq!(
147//!     a1_notation::new("D:G").unwrap()
148//!         .iter().map(|r| r.to_string()).collect::<Vec<_>>(),
149//!     vec!["D:D", "E:E", "F:F", "G:G"]);
150//!
151//! // and a row range goes row-wise
152//! assert_eq!(
153//!     a1_notation::new("3:6").unwrap()
154//!         .iter().map(|r| r.to_string()).collect::<Vec<_>>(),
155//!     vec!["3:3", "4:4", "5:5", "6:6"]);
156//!
157//! // a grid-based range goes row-by-row
158//! assert_eq!(
159//!     a1_notation::new("A1:C3").unwrap()
160//!         .iter().map(|r| r.to_string()).collect::<Vec<_>>(),
161//!     vec![
162//!         "A1", "B1", "C1",
163//!         "A2", "B2", "C2",
164//!         "A3", "B3", "C3",
165//!     ]);
166//! ```
167//!
168//! ### A1 Reference Examples
169//!
170//! Here is a table illustrating A1 references:
171//!
172//! | **Reference**   | **Meaning**               |
173//! |:----------------|:--------------------------|
174//! | `"A1"`          | Cell A1                   |
175//! | `"A1:B5"`       | Cells A1 through B5       |
176//! | `"C5:D9,G9:H16"`| A multiple-area selection |
177//! | `"A:A"`         | Column A                  |
178//! | `"1:1"`         | Row 1                     |
179//! | `"A:C"`         | Columns A through C       |
180//! | `"1:5"`         | Rows 1 through 5          |
181//! | `"1:1,3:3,8:8"` | Rows 1, 3, and 8          |
182//! | `"A:A,C:C,F:F"` | Columns A, C, and F       |
183//!
184//
185// TODO:
186//
187// * implement `IntoIterator` for `RangeOrCell`
188//
189use std::str::FromStr;
190
191mod a1;
192mod address;
193mod column;
194mod error;
195mod range_or_cell;
196mod row;
197
198pub use a1::A1;
199pub use address::Address;
200pub use column::Column;
201pub use error::Error;
202pub use range_or_cell::RangeOrCell;
203pub use row::Row;
204
205pub type Result<T> = std::result::Result<T, Error>;
206
207pub type Index = usize;
208
209pub(crate) static ALPHA: [char; 26] = [
210    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
211    'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
212];
213
214/// Create an `A1` referencing a cell at a given address (x/y)
215pub fn cell(x: Index, y: Index) -> A1 {
216    A1 {
217        sheet_name: None,
218        reference: RangeOrCell::Cell(Address::new(x, y)),
219    }
220}
221
222/// Parse an A1-style string into an `A1`
223pub fn new(s: &str) -> Result<A1> {
224    A1::from_str(s)
225}
226
227/// Create a range between two cells
228pub fn range<A: Into<Address>>(from: A, to: A) -> A1 {
229    A1 {
230        sheet_name: None,
231        reference: RangeOrCell::Range {
232            from: from.into(),
233            to: to.into(),
234        },
235    }
236}
237
238/// An entire column
239pub fn column<C: Into<Column>>(x: C) -> A1 {
240    A1 {
241        sheet_name: None,
242        reference: RangeOrCell::column(x),
243    }
244}
245
246/// A range between two columns
247pub fn column_range<R: Into<Column>>(xa: R, xb: R) -> A1 {
248    A1 {
249        sheet_name: None,
250        reference: RangeOrCell::column_range(xa, xb),
251    }
252}
253
254/// An entire row
255pub fn row<R: Into<Row>>(y: R) -> A1 {
256    A1 {
257        sheet_name: None,
258        reference: RangeOrCell::row(y),
259    }
260}
261
262/// A range between two rows
263pub fn row_range<R: Into<Row>>(ya: R, yb: R) -> A1 {
264    A1 {
265        sheet_name: None,
266        reference: RangeOrCell::row_range(ya, yb),
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn cell() {
276        let a1 = crate::cell(2, 3);
277
278        assert_eq!(a1.sheet_name, None);
279        assert_eq!(a1.reference, RangeOrCell::Cell(Address::new(2, 3)));
280    }
281
282    #[test]
283    fn new() {
284        let a1 = crate::new("Foo!A1").unwrap();
285
286        assert_eq!(a1.sheet_name, Some("Foo".to_string()));
287        assert_eq!(a1.reference, RangeOrCell::Cell(Address::new(0, 0)));
288    }
289
290    #[test]
291    fn range() {
292        let a1 = crate::range((0, 0), (22, 33));
293
294        assert_eq!(a1.sheet_name, None);
295        assert_eq!(
296            a1.reference,
297            RangeOrCell::Range {
298                from: Address::new(0, 0),
299                to: Address::new(22, 33),
300            }
301        );
302    }
303
304    #[test]
305    fn column() {
306        let a1 = crate::column(22);
307
308        assert_eq!(a1.sheet_name, None);
309        assert_eq!(
310            a1.reference,
311            RangeOrCell::ColumnRange {
312                from: Column::new(22),
313                to: Column::new(22),
314            }
315        );
316    }
317
318    #[test]
319    fn column_range() {
320        let a1 = crate::column_range(1, 420);
321
322        assert_eq!(a1.sheet_name, None);
323        assert_eq!(
324            a1.reference,
325            RangeOrCell::ColumnRange {
326                from: Column::new(1),
327                to: Column::new(420),
328            }
329        );
330    }
331
332    #[test]
333    fn row() {
334        let a1 = crate::row(11);
335
336        assert_eq!(a1.sheet_name, None);
337        assert_eq!(
338            a1.reference,
339            RangeOrCell::RowRange {
340                from: Row::new(11),
341                to: Row::new(11),
342            }
343        );
344    }
345
346    #[test]
347    fn row_range() {
348        let a1 = crate::row_range(0, 42);
349
350        assert_eq!(a1.sheet_name, None);
351        assert_eq!(
352            a1.reference,
353            RangeOrCell::RowRange {
354                from: Row::new(0),
355                to: Row::new(42),
356            }
357        );
358    }
359}