Skip to main content

xlsbye_formula/
refs.rs

1pub use xlsbye_core::path::col_to_a1;
2
3pub const COL_INDEX_MASK: u16 = 0x3FFF;
4pub const COL_RELATIVE_FLAG: u16 = 0x4000;
5pub const ROW_RELATIVE_FLAG: u16 = 0x8000;
6const SHARED_ROW_BITS: u32 = 20;
7const SHARED_COL_BITS: u16 = 14;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct CellRefFlags {
11    pub row_abs: bool,
12    pub col_abs: bool,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct AreaRefFlags {
17    pub first_row_abs: bool,
18    pub first_col_abs: bool,
19    pub last_row_abs: bool,
20    pub last_col_abs: bool,
21}
22
23pub fn decode_col_field(col_field: u16) -> (u16, CellRefFlags) {
24    let col = col_field & COL_INDEX_MASK;
25    let col_abs = (col_field & COL_RELATIVE_FLAG) == 0;
26    let row_abs = (col_field & ROW_RELATIVE_FLAG) == 0;
27
28    (col, CellRefFlags { row_abs, col_abs })
29}
30
31pub fn format_cell_ref(row: u32, col: u16, row_abs: bool, col_abs: bool) -> String {
32    let mut output = String::new();
33
34    if col_abs {
35        output.push('$');
36    }
37    output.push_str(&col_to_a1(u32::from(col)));
38
39    if row_abs {
40        output.push('$');
41    }
42    output.push_str(&(row + 1).to_string());
43
44    output
45}
46
47pub fn format_cell_ref_from_encoded(row: u32, col_field: u16) -> String {
48    let (col, flags) = decode_col_field(col_field);
49    format_cell_ref(row, col, flags.row_abs, flags.col_abs)
50}
51
52pub fn format_area_ref(
53    first_row: u32,
54    first_col: u16,
55    last_row: u32,
56    last_col: u16,
57    flags: AreaRefFlags,
58) -> String {
59    let left = format_cell_ref(
60        first_row,
61        first_col,
62        flags.first_row_abs,
63        flags.first_col_abs,
64    );
65    let right = format_cell_ref(last_row, last_col, flags.last_row_abs, flags.last_col_abs);
66    format!("{left}:{right}")
67}
68
69pub fn format_area_ref_from_encoded(
70    first_row: u32,
71    first_col_field: u16,
72    last_row: u32,
73    last_col_field: u16,
74) -> String {
75    let (first_col, first_flags) = decode_col_field(first_col_field);
76    let (last_col, last_flags) = decode_col_field(last_col_field);
77
78    format_area_ref(
79        first_row,
80        first_col,
81        last_row,
82        last_col,
83        AreaRefFlags {
84            first_row_abs: first_flags.row_abs,
85            first_col_abs: first_flags.col_abs,
86            last_row_abs: last_flags.row_abs,
87            last_col_abs: last_flags.col_abs,
88        },
89    )
90}
91
92pub fn format_relative_cell_ref(base_row: u32, base_col: u32, row_delta: i32, col_delta: i16) -> String {
93    let row = base_row.checked_add_signed(row_delta).unwrap_or(0);
94    let col = base_col.checked_add_signed(i32::from(col_delta)).unwrap_or(0);
95    format!("{}{}", col_to_a1(col), row + 1)
96}
97
98pub fn format_relative_area_ref(
99    base_row: u32,
100    base_col: u32,
101    first_row_delta: i32,
102    first_col_delta: i16,
103    last_row_delta: i32,
104    last_col_delta: i16,
105) -> String {
106    let left = format_relative_cell_ref(base_row, base_col, first_row_delta, first_col_delta);
107    let right = format_relative_cell_ref(base_row, base_col, last_row_delta, last_col_delta);
108    format!("{left}:{right}")
109}
110
111pub fn format_shared_cell_ref(base_row: u32, base_col: u32, row_field: u32, col_field: u16) -> String {
112    let (col_value, flags) = decode_col_field(col_field);
113    let row = if flags.row_abs {
114        row_field
115    } else {
116        base_row
117            .checked_add_signed(sign_extend(row_field, SHARED_ROW_BITS))
118            .unwrap_or(0)
119    };
120    let col = if flags.col_abs {
121        u32::from(col_value)
122    } else {
123        base_col
124            .checked_add_signed(i32::from(sign_extend_14(col_value)))
125            .unwrap_or(0)
126    };
127
128    format_cell_ref(row, col as u16, flags.row_abs, flags.col_abs)
129}
130
131pub fn format_shared_area_ref(
132    base_row: u32,
133    base_col: u32,
134    first_row_field: u32,
135    first_col_field: u16,
136    last_row_field: u32,
137    last_col_field: u16,
138) -> String {
139    let left = format_shared_cell_ref(base_row, base_col, first_row_field, first_col_field);
140    let right = format_shared_cell_ref(base_row, base_col, last_row_field, last_col_field);
141    format!("{left}:{right}")
142}
143
144fn sign_extend(value: u32, bits: u32) -> i32 {
145    let shift = 32 - bits;
146    ((value << shift) as i32) >> shift
147}
148
149fn sign_extend_14(value: u16) -> i16 {
150    let shift = 16 - SHARED_COL_BITS;
151    ((value << shift) as i16) >> shift
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn decode_inverted_relative_bits() {
160        let (col, flags) = decode_col_field(0x0000);
161        assert_eq!(col, 0);
162        assert!(flags.col_abs);
163        assert!(flags.row_abs);
164
165        let (col, flags) = decode_col_field(0x4000);
166        assert_eq!(col, 0);
167        assert!(!flags.col_abs);
168        assert!(flags.row_abs);
169
170        let (col, flags) = decode_col_field(0x8000);
171        assert_eq!(col, 0);
172        assert!(flags.col_abs);
173        assert!(!flags.row_abs);
174
175        let (col, flags) = decode_col_field(0xC000);
176        assert_eq!(col, 0);
177        assert!(!flags.col_abs);
178        assert!(!flags.row_abs);
179    }
180
181    #[test]
182    fn format_mixed_absolute_relative_refs() {
183        assert_eq!(format_cell_ref(0, 0, true, true), "$A$1");
184        assert_eq!(format_cell_ref(0, 0, true, false), "A$1");
185        assert_eq!(format_cell_ref(0, 0, false, true), "$A1");
186        assert_eq!(format_cell_ref(0, 0, false, false), "A1");
187    }
188
189    #[test]
190    fn format_area_from_encoded() {
191        assert_eq!(
192            format_area_ref_from_encoded(0, 0xC000, 9, 0xC001),
193            "A1:B10"
194        );
195    }
196
197    #[test]
198    fn format_relative_refs_from_anchor() {
199        assert_eq!(format_relative_cell_ref(5, 3, 0, -2), "B6");
200        assert_eq!(format_relative_area_ref(5, 3, 0, -2, 1, 0), "B6:D7");
201    }
202
203    #[test]
204    fn format_shared_refs_with_signed_offsets_and_mixed_abs() {
205        assert_eq!(format_shared_cell_ref(17, 1, 0x000F_FFFF, 0xC000), "B17");
206        assert_eq!(format_shared_cell_ref(17, 1, 12, 0x4000), "B$13");
207        assert_eq!(format_shared_cell_ref(17, 1, 0x000F_FFFF, 0x8001), "$B17");
208    }
209}