ironcalc_base/expressions/utils/
mod.rs1use super::types::*;
2use crate::constants::{LAST_COLUMN, LAST_ROW};
3
4#[cfg(test)]
5mod test;
6
7pub fn column_to_number(column: &str) -> Result<i32, String> {
9 if column.is_empty() {
10 return Err("Column identifier cannot be empty.".to_string());
11 }
12
13 if !column.is_ascii() {
14 return Err("Column identifier must be ASCII.".to_string());
15 }
16
17 let mut column_number = 0;
18 for character in column.chars() {
19 if !character.is_ascii_uppercase() {
20 return Err("Column identifier can use only A-Z characters".to_string());
21 }
22 column_number = column_number * 26 + ((character as i32) - 64);
23 }
24
25 match is_valid_column_number(column_number) {
26 true => Ok(column_number),
27 false => Err("Column is not valid.".to_string()),
28 }
29}
30
31pub fn number_to_column(mut i: i32) -> Option<String> {
33 if !is_valid_column_number(i) {
34 return None;
35 }
36 let mut column = "".to_string();
37 while i > 0 {
38 let r = ((i - 1) % 26) as u8;
39 column.insert(0, (65 + r) as char);
40 i = (i - 1) / 26;
41 }
42 Some(column)
43}
44
45pub fn is_valid_column_number(column: i32) -> bool {
47 (1..=LAST_COLUMN).contains(&column)
48}
49
50pub fn is_valid_column(column: &str) -> bool {
51 if column.len() > 3 {
53 return false;
54 }
55
56 let column_number = column_to_number(column);
57
58 match column_number {
59 Ok(column_number) => is_valid_column_number(column_number),
60 Err(_) => false,
61 }
62}
63
64pub fn is_valid_row(row: i32) -> bool {
65 (1..=LAST_ROW).contains(&row)
66}
67
68fn is_valid_row_str(row: &str) -> bool {
69 match row.parse::<i32>() {
70 Ok(r) => is_valid_row(r),
71 Err(_r) => false,
72 }
73}
74
75pub fn parse_reference_r1c1(r: &str) -> Option<ParsedReference> {
76 let chars = r.as_bytes();
77 let len = chars.len();
78 let absolute_column;
79 let absolute_row;
80 let mut row = "".to_string();
81 let mut column = "".to_string();
82 if len < 4 {
83 return None;
84 }
85 if chars[0] != b'R' {
86 return None;
87 }
88 let mut i = 1;
89 if chars[i] == b'[' {
90 i += 1;
91 absolute_row = false;
92 if chars[i] == b'-' {
93 i += 1;
94 row.push('-');
95 }
96 } else {
97 absolute_row = true;
98 }
99 while i < len {
100 let ch = chars[i];
101 if ch.is_ascii_digit() {
102 row.push(ch as char);
103 } else {
104 break;
105 }
106 i += 1;
107 }
108 if !absolute_row {
109 if i >= len || chars[i] != b']' {
110 return None;
111 };
112 i += 1;
113 }
114 if i >= len || chars[i] != b'C' {
115 return None;
116 };
117 i += 1;
118 if i < len && chars[i] == b'[' {
119 absolute_column = false;
120 i += 1;
121 if i < len && chars[i] == b'-' {
122 i += 1;
123 column.push('-');
124 }
125 } else {
126 absolute_column = true;
127 }
128 while i < len {
129 let ch = chars[i];
130 if ch.is_ascii_digit() {
131 column.push(ch as char);
132 } else {
133 break;
134 }
135 i += 1;
136 }
137 if !absolute_column {
138 if i >= len || chars[i] != b']' {
139 return None;
140 };
141 i += 1;
142 }
143 if i != len {
144 return None;
145 }
146 Some(ParsedReference {
147 row: row.parse::<i32>().unwrap_or(0),
148 column: column.parse::<i32>().unwrap_or(0),
149 absolute_column,
150 absolute_row,
151 })
152}
153
154pub fn parse_reference_a1(r: &str) -> Option<ParsedReference> {
155 let chars = r.chars();
156 let mut absolute_column = false;
157 let mut absolute_row = false;
158 let mut row = "".to_string();
159 let mut column = "".to_string();
160 let mut state = 1; for ch in chars {
163 match ch {
164 'A'..='Z' if state == 1 => {
165 column.push(ch);
166 }
167 '0'..='9' => {
168 if state == 1 {
169 state = 2
170 }
171 row.push(ch);
172 }
173 '$' => {
174 if column == *"" {
175 absolute_column = true;
176 } else if state == 1 {
177 absolute_row = true;
178 state = 2;
179 } else {
180 return None;
181 }
182 }
183 _ => {
184 return None;
185 }
186 }
187 }
188 if !is_valid_column(&column) {
189 return None;
190 }
191 if !is_valid_row_str(&row) {
192 return None;
193 }
194 let row = match row.parse::<i32>() {
195 Ok(r) => r,
196 Err(_) => return None,
197 };
198
199 Some(ParsedReference {
200 row,
201 column: column_to_number(&column).ok()?,
202 absolute_column,
203 absolute_row,
204 })
205}
206
207pub fn is_valid_a1_identifier(name: &str) -> bool {
212 let upper = name.to_uppercase();
215 let len = upper.chars().count();
216
217 let mut chars = upper.chars();
218
219 if len > 255 || len == 0 {
220 return false;
221 }
222 let first = match chars.next() {
223 Some(ch) => ch,
224 None => return false,
225 };
226
227 if !(first.is_ascii_alphabetic() || first == '_' || first == '\\') {
229 return false;
230 }
231 if upper == *"TRUE" || upper == *"FALSE" {
232 return false;
233 }
234 if parse_reference_a1(name).is_some() {
235 return false;
236 }
237 if parse_reference_r1c1(name).is_some() {
238 return false;
239 }
240 for ch in chars {
241 if !(ch.is_alphanumeric() || ch == '_' || ch == '.') {
242 return false;
243 }
244 }
245
246 true
247}
248
249pub fn is_valid_identifier(name: &str) -> bool {
250 let upper = name.to_uppercase();
252 if upper == "R" || upper == "C" {
253 return false;
254 }
255 is_valid_a1_identifier(name)
256}
257
258fn name_needs_quoting(name: &str) -> bool {
259 let chars = name.chars();
260 for (i, char) in chars.enumerate() {
262 if [' ', '(', ')', '\'', '$', ',', ';', '-', '+', '{', '}'].contains(&char) {
263 return true;
264 }
265 if i == 0 && char.is_ascii_digit() {
267 return true;
268 }
269 }
270 if parse_reference_a1(name).is_some() {
271 return true;
273 }
274 if parse_reference_r1c1(name).is_some() {
275 return true;
277 }
278 false
279}
280
281pub fn quote_name(name: &str) -> String {
284 if name_needs_quoting(name) {
285 return format!("'{}'", name.replace('\'', "''"));
286 };
287 name.to_string()
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_quote_name() {
296 assert_eq!(quote_name("Sheet1"), "Sheet1");
297 assert_eq!(quote_name("Sheet 1"), "'Sheet 1'");
298 assert_eq!(quote_name("Sheet1'"), "'Sheet1'''");
300 assert_eq!(quote_name("Data(2024)"), "'Data(2024)'");
301 assert_eq!(quote_name("Data$2024"), "'Data$2024'");
302 assert_eq!(quote_name("Data-2024"), "'Data-2024'");
303 assert_eq!(quote_name("Data+2024"), "'Data+2024'");
304 assert_eq!(quote_name("Data,2024"), "'Data,2024'");
305 assert_eq!(quote_name("Data;2024"), "'Data;2024'");
306 assert_eq!(quote_name("Data{2024}"), "'Data{2024}'");
307
308 assert_eq!(quote_name("2024"), "'2024'");
309 assert_eq!(quote_name("1Data"), "'1Data'");
310 assert_eq!(quote_name("A1"), "'A1'");
311 assert_eq!(quote_name("R1C1"), "'R1C1'");
312 assert_eq!(quote_name("MySheet"), "MySheet");
313
314 assert_eq!(quote_name("B1048576"), "'B1048576'");
315 assert_eq!(quote_name("B1048577"), "B1048577");
316 }
317}