Skip to main content

ironcalc_base/
utils.rs

1use crate::expressions::token::get_error_by_name;
2use crate::expressions::types::CellReferenceIndex;
3use crate::language::Language;
4
5use crate::{
6    expressions::{
7        lexer::{Lexer, LexerMode},
8        token::TokenType,
9    },
10    language::get_language,
11    locale::Locale,
12};
13
14#[derive(Debug, Eq, PartialEq)]
15pub enum ParsedReference {
16    CellReference(CellReferenceIndex),
17    Range(CellReferenceIndex, CellReferenceIndex),
18}
19
20impl ParsedReference {
21    /// Parses reference in formula format. For example:  `Sheet1!A1`, `Sheet1!$A$1:$B$9`.
22    /// Absolute references (`$`) do not affect parsing.
23    ///
24    /// # Arguments
25    ///
26    /// * `sheet_index_context` - if available, sheet index can be provided so references
27    ///   without explicit sheet name can be recognized
28    /// * `reference` - text string to parse as reference
29    /// * `locale` - locale that will be used to set-up parser
30    /// * `get_sheet_index_by_name` - function that allows to translate sheet name to index
31    pub(crate) fn parse_reference_formula<F: Fn(&str) -> Option<u32>>(
32        sheet_index_context: Option<u32>,
33        reference: &str,
34        locale: &Locale,
35        get_sheet_index_by_name: F,
36    ) -> Result<ParsedReference, String> {
37        #[allow(clippy::expect_used)]
38        let language = get_language("en").expect("");
39        let mut lexer = Lexer::new(reference, LexerMode::A1, locale, language);
40
41        let reference_token = lexer.next_token();
42        let eof_token = lexer.next_token();
43
44        if TokenType::EOF != eof_token {
45            return Err("Invalid reference. Expected only one token.".to_string());
46        }
47
48        match reference_token {
49            TokenType::Reference {
50                sheet: sheet_name,
51                column: column_id,
52                row: row_id,
53                ..
54            } => {
55                let sheet_index;
56                if let Some(name) = sheet_name {
57                    match get_sheet_index_by_name(&name) {
58                        Some(i) => sheet_index = i,
59                        None => {
60                            return Err(format!(
61                                "Invalid reference. Sheet \"{}\" could not be found.",
62                                name.as_str(),
63                            ));
64                        }
65                    }
66                } else if let Some(sheet_index_context) = sheet_index_context {
67                    sheet_index = sheet_index_context;
68                } else {
69                    return Err(
70                        "Reference doesn't contain sheet name and relative cell is not known."
71                            .to_string(),
72                    );
73                }
74
75                Ok(ParsedReference::CellReference(CellReferenceIndex {
76                    sheet: sheet_index,
77                    row: row_id,
78                    column: column_id,
79                }))
80            }
81            TokenType::Range {
82                sheet: sheet_name,
83                left,
84                right,
85            } => {
86                let sheet_index;
87                if let Some(name) = sheet_name {
88                    match get_sheet_index_by_name(&name) {
89                        Some(i) => sheet_index = i,
90                        None => {
91                            return Err(format!(
92                                "Invalid reference. Sheet \"{}\" could not be found.",
93                                name.as_str(),
94                            ));
95                        }
96                    }
97                } else if let Some(sheet_index_context) = sheet_index_context {
98                    sheet_index = sheet_index_context;
99                } else {
100                    return Err(
101                        "Reference doesn't contain sheet name and relative cell is not known."
102                            .to_string(),
103                    );
104                }
105
106                Ok(ParsedReference::Range(
107                    CellReferenceIndex {
108                        sheet: sheet_index,
109                        row: left.row,
110                        column: left.column,
111                    },
112                    CellReferenceIndex {
113                        sheet: sheet_index,
114                        row: right.row,
115                        column: right.column,
116                    },
117                ))
118            }
119            _ => Err("Invalid reference. First token is not a reference.".to_string()),
120        }
121    }
122}
123
124/// Returns true if the string value could be interpreted as:
125///  * a formula
126///  * a number
127///  * a boolean
128///  * an error (i.e "#VALUE!")
129pub(crate) fn value_needs_quoting(value: &str, language: &Language) -> bool {
130    value.starts_with(['=', '+', '-'])
131        || value.parse::<f64>().is_ok()
132        || value.to_lowercase().parse::<bool>().is_ok()
133        || get_error_by_name(&value.to_uppercase(), language).is_some()
134}
135
136/// Gets all timezones
137pub fn get_all_timezones() -> Vec<String> {
138    crate::tz::get_all_timezone_names()
139}
140
141#[cfg(test)]
142mod tests {
143    #![allow(clippy::expect_used)]
144
145    use super::*;
146    use crate::language::get_language;
147    use crate::locale::{get_locale, Locale};
148
149    fn get_test_locale() -> &'static Locale {
150        #![allow(clippy::unwrap_used)]
151        get_locale("en").unwrap()
152    }
153
154    fn get_sheet_index_by_name(sheet_names: &[&str], name: &str) -> Option<u32> {
155        sheet_names
156            .iter()
157            .position(|&sheet_name| sheet_name == name)
158            .map(|index| index as u32)
159    }
160
161    #[test]
162    fn test_parse_cell_references() {
163        let locale = get_test_locale();
164        let sheet_names = vec!["Sheet1", "Sheet2", "Sheet3"];
165
166        assert_eq!(
167            ParsedReference::parse_reference_formula(Some(7), "A1", locale, |name| {
168                get_sheet_index_by_name(&sheet_names, name)
169            },),
170            Ok(ParsedReference::CellReference(CellReferenceIndex {
171                sheet: 7,
172                row: 1,
173                column: 1,
174            })),
175        );
176
177        assert_eq!(
178            ParsedReference::parse_reference_formula(None, "Sheet1!A1", locale, |name| {
179                get_sheet_index_by_name(&sheet_names, name)
180            },),
181            Ok(ParsedReference::CellReference(CellReferenceIndex {
182                sheet: 0,
183                row: 1,
184                column: 1,
185            })),
186        );
187
188        assert_eq!(
189            ParsedReference::parse_reference_formula(None, "Sheet1!$A$1", locale, |name| {
190                get_sheet_index_by_name(&sheet_names, name)
191            },),
192            Ok(ParsedReference::CellReference(CellReferenceIndex {
193                sheet: 0,
194                row: 1,
195                column: 1,
196            })),
197        );
198
199        assert_eq!(
200            ParsedReference::parse_reference_formula(None, "Sheet2!$A$1", locale, |name| {
201                get_sheet_index_by_name(&sheet_names, name)
202            },),
203            Ok(ParsedReference::CellReference(CellReferenceIndex {
204                sheet: 1,
205                row: 1,
206                column: 1,
207            })),
208        );
209    }
210
211    #[test]
212    fn test_parse_range_references() {
213        let locale = get_test_locale();
214        let sheet_names = vec!["Sheet1", "Sheet2", "Sheet3"];
215
216        assert_eq!(
217            ParsedReference::parse_reference_formula(Some(5), "A1:A2", locale, |name| {
218                get_sheet_index_by_name(&sheet_names, name)
219            },),
220            Ok(ParsedReference::Range(
221                CellReferenceIndex {
222                    sheet: 5,
223                    column: 1,
224                    row: 1,
225                },
226                CellReferenceIndex {
227                    sheet: 5,
228                    column: 1,
229                    row: 2,
230                },
231            )),
232        );
233
234        assert_eq!(
235            ParsedReference::parse_reference_formula(None, "Sheet1!$A$1:$B$10", locale, |name| {
236                get_sheet_index_by_name(&sheet_names, name)
237            },),
238            Ok(ParsedReference::Range(
239                CellReferenceIndex {
240                    sheet: 0,
241                    row: 1,
242                    column: 1,
243                },
244                CellReferenceIndex {
245                    sheet: 0,
246                    row: 10,
247                    column: 2,
248                },
249            )),
250        );
251
252        assert_eq!(
253            ParsedReference::parse_reference_formula(None, "Sheet2!AA1:E$11", locale, |name| {
254                get_sheet_index_by_name(&sheet_names, name)
255            },),
256            Ok(ParsedReference::Range(
257                CellReferenceIndex {
258                    sheet: 1,
259                    row: 1,
260                    column: 27,
261                },
262                CellReferenceIndex {
263                    sheet: 1,
264                    row: 11,
265                    column: 5,
266                },
267            )),
268        );
269    }
270
271    #[test]
272    fn test_error_reject_assignments() {
273        let locale = get_test_locale();
274        let sheet_index = Some(1);
275        assert_eq!(
276            ParsedReference::parse_reference_formula(sheet_index, "=A1", locale, |_| Some(1)),
277            Err("Invalid reference. Expected only one token.".to_string()),
278        );
279        assert_eq!(
280            ParsedReference::parse_reference_formula(sheet_index, "=$A$1", locale, |_| { Some(1) }),
281            Err("Invalid reference. Expected only one token.".to_string()),
282        );
283        assert_eq!(
284            ParsedReference::parse_reference_formula(None, "=Sheet1!A1", locale, |_| Some(1)),
285            Err("Invalid reference. Expected only one token.".to_string()),
286        );
287    }
288
289    #[test]
290    fn test_error_reject_formulas_without_equal_sign() {
291        let locale = get_test_locale();
292        assert_eq!(
293            ParsedReference::parse_reference_formula(None, "SUM", locale, |_| Some(1)),
294            Err("Invalid reference. First token is not a reference.".to_string()),
295        );
296        assert_eq!(
297            ParsedReference::parse_reference_formula(None, "SUM(A1:A2)", locale, |_| Some(1)),
298            Err("Invalid reference. Expected only one token.".to_string()),
299        );
300    }
301
302    #[test]
303    fn test_error_reject_without_sheet_and_relative_cell() {
304        let locale = get_test_locale();
305        assert_eq!(
306            ParsedReference::parse_reference_formula(None, "A1", locale, |_| Some(1)),
307            Err("Reference doesn't contain sheet name and relative cell is not known.".to_string()),
308        );
309        assert_eq!(
310            ParsedReference::parse_reference_formula(None, "A1:A2", locale, |_| Some(1)),
311            Err("Reference doesn't contain sheet name and relative cell is not known.".to_string()),
312        );
313    }
314
315    #[test]
316    fn test_error_unrecognized_sheet_name() {
317        let locale = get_test_locale();
318        assert_eq!(
319            ParsedReference::parse_reference_formula(None, "SheetName!A1", locale, |_| None),
320            Err("Invalid reference. Sheet \"SheetName\" could not be found.".to_string()),
321        );
322        assert_eq!(
323            ParsedReference::parse_reference_formula(None, "SheetName2!A1:A4", locale, |_| None),
324            Err("Invalid reference. Sheet \"SheetName2\" could not be found.".to_string()),
325        );
326    }
327
328    #[test]
329    fn test_value_needs_quoting() {
330        let en_language = get_language("en").expect("en language expected");
331
332        assert!(!value_needs_quoting("", en_language));
333        assert!(!value_needs_quoting("hello", en_language));
334
335        assert!(value_needs_quoting("12", en_language));
336        assert!(value_needs_quoting("true", en_language));
337        assert!(value_needs_quoting("False", en_language));
338
339        assert!(value_needs_quoting("=A1", en_language));
340
341        assert!(value_needs_quoting("#REF!", en_language));
342        assert!(value_needs_quoting("#NAME?", en_language));
343    }
344}