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}