Skip to main content

sheets_diff/core/
utils.rs

1/// Returns sheets whose names appear in both lists, preserving old workbook order.
2pub fn filter_same_name_sheets(old_sheets: &[String], new_sheets: &[String]) -> Vec<String> {
3    old_sheets
4        .iter()
5        .filter(|s| new_sheets.contains(s))
6        .cloned()
7        .collect()
8}
9
10/// Returns the bounding rectangle that covers both ranges.
11///
12/// Returns `(start_row, start_col, end_row_exclusive, end_col_exclusive)`.
13pub fn diff_range(
14    old_start: Option<(u32, u32)>,
15    new_start: Option<(u32, u32)>,
16    old_end: Option<(u32, u32)>,
17    new_end: Option<(u32, u32)>,
18) -> (u32, u32, u32, u32) {
19    let (old_start_row, old_start_col) = old_start.unwrap_or((u32::MAX, u32::MAX));
20    let (new_start_row, new_start_col) = new_start.unwrap_or((u32::MAX, u32::MAX));
21    let (old_end_row, old_end_col) = old_end.unwrap_or((u32::MIN, u32::MIN));
22    let (new_end_row, new_end_col) = new_end.unwrap_or((u32::MIN, u32::MIN));
23
24    let start_row = old_start_row.min(new_start_row);
25    let start_col = old_start_col.min(new_start_col);
26    let end_row = old_end_row.max(new_end_row);
27    let end_col = old_end_col.max(new_end_col);
28
29    (start_row, start_col, end_row + 1, end_col + 1)
30}
31
32/// Converts a 1-based column index to an Excel column label (e.g. 1 → "A", 27 → "AA").
33///
34/// Excel's full column range is `1..=16384`, where column 16384 is `XFD`.
35///
36/// # Panics
37///
38/// Panics in debug builds if `col == 0`. A zero column is an internal programming
39/// error; calamine returns 0-based coordinates and every call site must add 1 before
40/// calling this function.
41pub fn col_to_label(mut col: usize) -> String {
42    assert!(col > 0, "Excel column index is 1-based; col == 0 is invalid");
43
44    let mut bytes = Vec::new();
45    while col > 0 {
46        let rem = (col - 1) % 26;
47        bytes.push(b'A' + rem as u8);
48        col = (col - 1) / 26;
49    }
50    bytes.reverse();
51    // Safety: only ASCII uppercase letters are pushed.
52    String::from_utf8(bytes).expect("only ASCII uppercase letters are generated")
53}
54
55/// Converts a 1-based `(row, col)` pair to an Excel A1 address string.
56///
57/// For example: `(1, 1)` → `"A1"`, `(1, 16384)` → `"XFD1"`.
58pub fn cell_pos_to_address(row: usize, col: usize) -> String {
59    format!("{}{}", col_to_label(col), row)
60}