Skip to main content

ironcalc_base/
utils.rs

1use crate::constants::{LAST_COLUMN, LAST_ROW};
2use crate::expressions::token::get_error_by_name;
3use crate::expressions::types::CellReferenceIndex;
4use crate::language::Language;
5use crate::number_format::parse_finite_number;
6
7use crate::{
8    expressions::{
9        lexer::{Lexer, LexerMode},
10        token::TokenType,
11    },
12    language::get_language,
13    locale::Locale,
14};
15
16#[derive(Debug, Eq, PartialEq)]
17pub enum ParsedReference {
18    CellReference(CellReferenceIndex),
19    Range(CellReferenceIndex, CellReferenceIndex),
20}
21
22impl ParsedReference {
23    /// Parses reference in formula format. For example:  `Sheet1!A1`, `Sheet1!$A$1:$B$9`.
24    /// Absolute references (`$`) do not affect parsing.
25    ///
26    /// # Arguments
27    ///
28    /// * `sheet_index_context` - if available, sheet index can be provided so references
29    ///   without explicit sheet name can be recognized
30    /// * `reference` - text string to parse as reference
31    /// * `locale` - locale that will be used to set-up parser
32    /// * `get_sheet_index_by_name` - function that allows to translate sheet name to index
33    pub(crate) fn parse_reference_formula<F: Fn(&str) -> Option<u32>>(
34        sheet_index_context: Option<u32>,
35        reference: &str,
36        locale: &Locale,
37        get_sheet_index_by_name: F,
38    ) -> Result<ParsedReference, String> {
39        #[allow(clippy::expect_used)]
40        let language = get_language("en").expect("");
41        let mut lexer = Lexer::new(reference, LexerMode::A1, locale, language);
42
43        let reference_token = lexer.next_token();
44        let eof_token = lexer.next_token();
45
46        if TokenType::EOF != eof_token {
47            return Err("Invalid reference. Expected only one token.".to_string());
48        }
49
50        match reference_token {
51            TokenType::Reference {
52                sheet: sheet_name,
53                column: column_id,
54                row: row_id,
55                ..
56            } => {
57                let sheet_index;
58                if let Some(name) = sheet_name {
59                    match get_sheet_index_by_name(&name) {
60                        Some(i) => sheet_index = i,
61                        None => {
62                            return Err(format!(
63                                "Invalid reference. Sheet \"{}\" could not be found.",
64                                name.as_str(),
65                            ));
66                        }
67                    }
68                } else if let Some(sheet_index_context) = sheet_index_context {
69                    sheet_index = sheet_index_context;
70                } else {
71                    return Err(
72                        "Reference doesn't contain sheet name and relative cell is not known."
73                            .to_string(),
74                    );
75                }
76
77                Ok(ParsedReference::CellReference(CellReferenceIndex {
78                    sheet: sheet_index,
79                    row: row_id,
80                    column: column_id,
81                }))
82            }
83            TokenType::Range {
84                sheet: sheet_name,
85                left,
86                right,
87            } => {
88                let sheet_index;
89                if let Some(name) = sheet_name {
90                    match get_sheet_index_by_name(&name) {
91                        Some(i) => sheet_index = i,
92                        None => {
93                            return Err(format!(
94                                "Invalid reference. Sheet \"{}\" could not be found.",
95                                name.as_str(),
96                            ));
97                        }
98                    }
99                } else if let Some(sheet_index_context) = sheet_index_context {
100                    sheet_index = sheet_index_context;
101                } else {
102                    return Err(
103                        "Reference doesn't contain sheet name and relative cell is not known."
104                            .to_string(),
105                    );
106                }
107
108                Ok(ParsedReference::Range(
109                    CellReferenceIndex {
110                        sheet: sheet_index,
111                        row: left.row,
112                        column: left.column,
113                    },
114                    CellReferenceIndex {
115                        sheet: sheet_index,
116                        row: right.row,
117                        column: right.column,
118                    },
119                ))
120            }
121            _ => Err("Invalid reference. First token is not a reference.".to_string()),
122        }
123    }
124
125    /// Parses a reference in R1C1 notation, the way a user writes it. For
126    /// example: `R1C1`, `Sheet1!R2C3:R5C7`, `R[-1]C[2]`, `RC[-1]`, `R3`, `C[1]:C[2]`.
127    ///
128    /// A number after `R` or `C` is the row or column itself, a number in
129    /// brackets is an offset from `cell`, and no number at all means the row
130    /// or column of `cell`. `R` on its own is a whole row and `C` a whole
131    /// column. Without a sheet name the reference is in the sheet of `cell`.
132    ///
133    /// An offset that leaves the sheet comes back in by the opposite side, as
134    /// in Excel: one row above the first row is the last row.
135    ///
136    /// # Arguments
137    ///
138    /// * `cell` - the cell the reference is relative to
139    /// * `reference` - text string to parse as reference
140    /// * `get_sheet_index_by_name` - function that allows to translate sheet name to index
141    pub(crate) fn parse_r1c1_reference<F: Fn(&str) -> Option<u32>>(
142        cell: CellReferenceIndex,
143        reference: &str,
144        get_sheet_index_by_name: F,
145    ) -> Result<ParsedReference, String> {
146        let (sheet_name, reference) = split_sheet_name(reference)?;
147        let sheet = match sheet_name {
148            Some(name) => get_sheet_index_by_name(&name).ok_or_else(|| {
149                format!("Invalid reference. Sheet \"{name}\" could not be found.")
150            })?,
151            None => cell.sheet,
152        };
153
154        let text = reference.as_bytes();
155        let mut position = 0;
156        let first = parse_r1c1_part(text, &mut position, &cell)?;
157        let second = if text.get(position) == Some(&b':') {
158            position += 1;
159            Some(parse_r1c1_part(text, &mut position, &cell)?)
160        } else {
161            None
162        };
163        if position != text.len() {
164            return Err("Invalid reference. Expected end of reference.".to_string());
165        }
166
167        let (row1, column1, row2, column2) = match (first, second) {
168            (R1C1Part::Cell(row, column), None) => {
169                return Ok(ParsedReference::CellReference(CellReferenceIndex {
170                    sheet,
171                    row,
172                    column,
173                }));
174            }
175            (R1C1Part::Cell(row1, column1), Some(R1C1Part::Cell(row2, column2))) => {
176                (row1, column1, row2, column2)
177            }
178            (R1C1Part::Row(row), None) => (row, 1, row, LAST_COLUMN),
179            (R1C1Part::Row(row1), Some(R1C1Part::Row(row2))) => (row1, 1, row2, LAST_COLUMN),
180            (R1C1Part::Column(column), None) => (1, column, LAST_ROW, column),
181            (R1C1Part::Column(column1), Some(R1C1Part::Column(column2))) => {
182                (1, column1, LAST_ROW, column2)
183            }
184            _ => {
185                return Err(
186                    "Invalid reference. The two ends of the range are of different kind."
187                        .to_string(),
188                );
189            }
190        };
191        Ok(ParsedReference::Range(
192            CellReferenceIndex {
193                sheet,
194                row: row1.min(row2),
195                column: column1.min(column2),
196            },
197            CellReferenceIndex {
198                sheet,
199                row: row1.max(row2),
200                column: column1.max(column2),
201            },
202        ))
203    }
204}
205
206/// One end of a reference in R1C1 notation.
207enum R1C1Part {
208    /// `R2C3`: row and column
209    Cell(i32, i32),
210    /// `R2`: a whole row
211    Row(i32),
212    /// `C3`: a whole column
213    Column(i32),
214}
215
216/// Splits `Sheet1!R1C1` or `'My sheet'!R1C1` into the name of the sheet and
217/// the rest. A quote in a quoted name is written twice.
218fn split_sheet_name(reference: &str) -> Result<(Option<String>, &str), String> {
219    let Some(quoted) = reference.strip_prefix('\'') else {
220        return Ok(match reference.split_once('!') {
221            Some((name, rest)) => (Some(name.to_string()), rest),
222            None => (None, reference),
223        });
224    };
225    let mut name = String::new();
226    let mut chars = quoted.char_indices().peekable();
227    while let Some((index, c)) = chars.next() {
228        if c != '\'' {
229            name.push(c);
230        } else if matches!(chars.peek(), Some((_, '\''))) {
231            name.push('\'');
232            chars.next();
233        } else {
234            return match quoted[index + 1..].strip_prefix('!') {
235                Some(rest) => Ok((Some(name), rest)),
236                None => Err("Invalid reference. Expected '!' after the sheet name.".to_string()),
237            };
238        }
239    }
240    Err("Invalid reference. The name of the sheet is not closed.".to_string())
241}
242
243/// Reads one end of a reference in R1C1 notation starting at `position`,
244/// which is left just after it.
245fn parse_r1c1_part(
246    text: &[u8],
247    position: &mut usize,
248    cell: &CellReferenceIndex,
249) -> Result<R1C1Part, String> {
250    let is_letter = |position: usize, letter: u8| {
251        text.get(position)
252            .is_some_and(|c| c.eq_ignore_ascii_case(&letter))
253    };
254    let part = if is_letter(*position, b'R') {
255        *position += 1;
256        let row = parse_r1c1_number(text, position, cell.row, LAST_ROW)?;
257        if is_letter(*position, b'C') {
258            *position += 1;
259            let column = parse_r1c1_number(text, position, cell.column, LAST_COLUMN)?;
260            R1C1Part::Cell(row, column)
261        } else {
262            R1C1Part::Row(row)
263        }
264    } else if is_letter(*position, b'C') {
265        *position += 1;
266        R1C1Part::Column(parse_r1c1_number(text, position, cell.column, LAST_COLUMN)?)
267    } else {
268        return Err("Invalid reference. Expected 'R' or 'C'.".to_string());
269    };
270    let (row, column) = match part {
271        R1C1Part::Cell(row, column) => (row, column),
272        R1C1Part::Row(row) => (row, 1),
273        R1C1Part::Column(column) => (1, column),
274    };
275    if !(1..=LAST_ROW).contains(&row) || !(1..=LAST_COLUMN).contains(&column) {
276        return Err("Invalid reference. It is outside of the sheet.".to_string());
277    }
278    Ok(part)
279}
280
281/// Reads the number that follows an `R` or a `C`: `12` is row or column 12,
282/// `[-2]` is two before `current` and nothing at all is `current`.
283///
284/// The rows and the columns go round: an offset that goes past the `last` one
285/// carries on from the first, and one that goes before the first carries on
286/// from the last. An offset has to be shorter than the whole way round.
287fn parse_r1c1_number(
288    text: &[u8],
289    position: &mut usize,
290    current: i32,
291    last: i32,
292) -> Result<i32, String> {
293    let invalid = || "Invalid reference. Expected a number.".to_string();
294    let is_relative = text.get(*position) == Some(&b'[');
295    if is_relative {
296        *position += 1;
297    }
298    let start = *position;
299    if is_relative && text.get(*position) == Some(&b'-') {
300        *position += 1;
301    }
302    while text.get(*position).is_some_and(u8::is_ascii_digit) {
303        *position += 1;
304    }
305    let digits = std::str::from_utf8(&text[start..*position]).map_err(|_| invalid())?;
306    if !is_relative {
307        if digits.is_empty() {
308            return Ok(current);
309        }
310        return digits.parse::<i32>().map_err(|_| invalid());
311    }
312    if text.get(*position) != Some(&b']') {
313        return Err("Invalid reference. Expected ']'.".to_string());
314    }
315    *position += 1;
316    let offset = digits.parse::<i32>().map_err(|_| invalid())?;
317    // Not `offset.abs()`: the lowest number has no positive counterpart.
318    if offset <= -last || offset >= last {
319        return Err("Invalid reference. The offset is larger than the sheet.".to_string());
320    }
321    Ok((current - 1 + offset).rem_euclid(last) + 1)
322}
323
324/// Returns true if the string value could be interpreted as:
325///  * a formula
326///  * a number
327///  * a boolean
328///  * an error (i.e "#VALUE!")
329pub(crate) fn value_needs_quoting(value: &str, language: &Language) -> bool {
330    value.starts_with(['=', '+', '-'])
331        || parse_finite_number(value).is_some()
332        || value.to_lowercase().parse::<bool>().is_ok()
333        || get_error_by_name(&value.to_uppercase(), language).is_some()
334}
335
336/// Gets all timezones
337pub fn get_all_timezones() -> Vec<String> {
338    crate::tz::get_all_timezone_names()
339}
340
341#[cfg(test)]
342mod tests {
343    #![allow(clippy::expect_used)]
344
345    use super::*;
346    use crate::language::get_language;
347    use crate::locale::{get_locale, Locale};
348
349    fn get_test_locale() -> &'static Locale {
350        #![allow(clippy::unwrap_used)]
351        get_locale("en").unwrap()
352    }
353
354    fn get_sheet_index_by_name(sheet_names: &[&str], name: &str) -> Option<u32> {
355        sheet_names
356            .iter()
357            .position(|&sheet_name| sheet_name == name)
358            .map(|index| index as u32)
359    }
360
361    #[test]
362    fn test_parse_cell_references() {
363        let locale = get_test_locale();
364        let sheet_names = vec!["Sheet1", "Sheet2", "Sheet3"];
365
366        assert_eq!(
367            ParsedReference::parse_reference_formula(Some(7), "A1", locale, |name| {
368                get_sheet_index_by_name(&sheet_names, name)
369            },),
370            Ok(ParsedReference::CellReference(CellReferenceIndex {
371                sheet: 7,
372                row: 1,
373                column: 1,
374            })),
375        );
376
377        assert_eq!(
378            ParsedReference::parse_reference_formula(None, "Sheet1!A1", locale, |name| {
379                get_sheet_index_by_name(&sheet_names, name)
380            },),
381            Ok(ParsedReference::CellReference(CellReferenceIndex {
382                sheet: 0,
383                row: 1,
384                column: 1,
385            })),
386        );
387
388        assert_eq!(
389            ParsedReference::parse_reference_formula(None, "Sheet1!$A$1", locale, |name| {
390                get_sheet_index_by_name(&sheet_names, name)
391            },),
392            Ok(ParsedReference::CellReference(CellReferenceIndex {
393                sheet: 0,
394                row: 1,
395                column: 1,
396            })),
397        );
398
399        assert_eq!(
400            ParsedReference::parse_reference_formula(None, "Sheet2!$A$1", locale, |name| {
401                get_sheet_index_by_name(&sheet_names, name)
402            },),
403            Ok(ParsedReference::CellReference(CellReferenceIndex {
404                sheet: 1,
405                row: 1,
406                column: 1,
407            })),
408        );
409    }
410
411    #[test]
412    fn test_parse_range_references() {
413        let locale = get_test_locale();
414        let sheet_names = vec!["Sheet1", "Sheet2", "Sheet3"];
415
416        assert_eq!(
417            ParsedReference::parse_reference_formula(Some(5), "A1:A2", locale, |name| {
418                get_sheet_index_by_name(&sheet_names, name)
419            },),
420            Ok(ParsedReference::Range(
421                CellReferenceIndex {
422                    sheet: 5,
423                    column: 1,
424                    row: 1,
425                },
426                CellReferenceIndex {
427                    sheet: 5,
428                    column: 1,
429                    row: 2,
430                },
431            )),
432        );
433
434        assert_eq!(
435            ParsedReference::parse_reference_formula(None, "Sheet1!$A$1:$B$10", locale, |name| {
436                get_sheet_index_by_name(&sheet_names, name)
437            },),
438            Ok(ParsedReference::Range(
439                CellReferenceIndex {
440                    sheet: 0,
441                    row: 1,
442                    column: 1,
443                },
444                CellReferenceIndex {
445                    sheet: 0,
446                    row: 10,
447                    column: 2,
448                },
449            )),
450        );
451
452        assert_eq!(
453            ParsedReference::parse_reference_formula(None, "Sheet2!AA1:E$11", locale, |name| {
454                get_sheet_index_by_name(&sheet_names, name)
455            },),
456            Ok(ParsedReference::Range(
457                CellReferenceIndex {
458                    sheet: 1,
459                    row: 1,
460                    column: 27,
461                },
462                CellReferenceIndex {
463                    sheet: 1,
464                    row: 11,
465                    column: 5,
466                },
467            )),
468        );
469    }
470
471    #[test]
472    fn test_error_reject_assignments() {
473        let locale = get_test_locale();
474        let sheet_index = Some(1);
475        assert_eq!(
476            ParsedReference::parse_reference_formula(sheet_index, "=A1", locale, |_| Some(1)),
477            Err("Invalid reference. Expected only one token.".to_string()),
478        );
479        assert_eq!(
480            ParsedReference::parse_reference_formula(sheet_index, "=$A$1", locale, |_| { Some(1) }),
481            Err("Invalid reference. Expected only one token.".to_string()),
482        );
483        assert_eq!(
484            ParsedReference::parse_reference_formula(None, "=Sheet1!A1", locale, |_| Some(1)),
485            Err("Invalid reference. Expected only one token.".to_string()),
486        );
487    }
488
489    #[test]
490    fn test_error_reject_formulas_without_equal_sign() {
491        let locale = get_test_locale();
492        assert_eq!(
493            ParsedReference::parse_reference_formula(None, "SUM", locale, |_| Some(1)),
494            Err("Invalid reference. First token is not a reference.".to_string()),
495        );
496        assert_eq!(
497            ParsedReference::parse_reference_formula(None, "SUM(A1:A2)", locale, |_| Some(1)),
498            Err("Invalid reference. Expected only one token.".to_string()),
499        );
500    }
501
502    #[test]
503    fn test_error_reject_without_sheet_and_relative_cell() {
504        let locale = get_test_locale();
505        assert_eq!(
506            ParsedReference::parse_reference_formula(None, "A1", locale, |_| Some(1)),
507            Err("Reference doesn't contain sheet name and relative cell is not known.".to_string()),
508        );
509        assert_eq!(
510            ParsedReference::parse_reference_formula(None, "A1:A2", locale, |_| Some(1)),
511            Err("Reference doesn't contain sheet name and relative cell is not known.".to_string()),
512        );
513    }
514
515    #[test]
516    fn test_error_unrecognized_sheet_name() {
517        let locale = get_test_locale();
518        assert_eq!(
519            ParsedReference::parse_reference_formula(None, "SheetName!A1", locale, |_| None),
520            Err("Invalid reference. Sheet \"SheetName\" could not be found.".to_string()),
521        );
522        assert_eq!(
523            ParsedReference::parse_reference_formula(None, "SheetName2!A1:A4", locale, |_| None),
524            Err("Invalid reference. Sheet \"SheetName2\" could not be found.".to_string()),
525        );
526    }
527
528    fn r1c1(reference: &str) -> Result<ParsedReference, String> {
529        // Relative to Sheet2!C5
530        let cell = CellReferenceIndex {
531            sheet: 1,
532            row: 5,
533            column: 3,
534        };
535        let sheet_names = vec!["Sheet1", "Sheet2", "My sheet", "It's"];
536        ParsedReference::parse_r1c1_reference(cell, reference, |name| {
537            get_sheet_index_by_name(&sheet_names, name)
538        })
539    }
540
541    fn cell(sheet: u32, row: i32, column: i32) -> Result<ParsedReference, String> {
542        Ok(ParsedReference::CellReference(CellReferenceIndex {
543            sheet,
544            row,
545            column,
546        }))
547    }
548
549    fn range(
550        sheet: u32,
551        (row1, column1): (i32, i32),
552        (row2, column2): (i32, i32),
553    ) -> Result<ParsedReference, String> {
554        Ok(ParsedReference::Range(
555            CellReferenceIndex {
556                sheet,
557                row: row1,
558                column: column1,
559            },
560            CellReferenceIndex {
561                sheet,
562                row: row2,
563                column: column2,
564            },
565        ))
566    }
567
568    #[test]
569    fn test_parse_r1c1_cells() {
570        assert_eq!(r1c1("R1C1"), cell(1, 1, 1));
571        assert_eq!(r1c1("R12C34"), cell(1, 12, 34));
572        assert_eq!(r1c1("r2c3"), cell(1, 2, 3));
573        assert_eq!(r1c1("R1048576C16384"), cell(1, LAST_ROW, LAST_COLUMN));
574        // Relative to C5
575        assert_eq!(r1c1("RC"), cell(1, 5, 3));
576        assert_eq!(r1c1("R[0]C[0]"), cell(1, 5, 3));
577        assert_eq!(r1c1("R[2]C[-1]"), cell(1, 7, 2));
578        assert_eq!(r1c1("R[-4]C[1]"), cell(1, 1, 4));
579        assert_eq!(r1c1("RC[1]"), cell(1, 5, 4));
580        assert_eq!(r1c1("R[1]C"), cell(1, 6, 3));
581        assert_eq!(r1c1("R1C"), cell(1, 1, 3));
582        assert_eq!(r1c1("RC1"), cell(1, 5, 1));
583        assert_eq!(r1c1("R7C[2]"), cell(1, 7, 5));
584    }
585
586    #[test]
587    fn test_parse_r1c1_sheets() {
588        assert_eq!(r1c1("Sheet1!R2C3"), cell(0, 2, 3));
589        assert_eq!(r1c1("Sheet2!R2C3"), cell(1, 2, 3));
590        assert_eq!(r1c1("'My sheet'!R2C3"), cell(2, 2, 3));
591        assert_eq!(r1c1("'It''s'!R2C3"), cell(3, 2, 3));
592        assert_eq!(r1c1("'Sheet1'!R1C1:R2C2"), range(0, (1, 1), (2, 2)));
593        assert_eq!(
594            r1c1("Sheet3!R2C3"),
595            Err("Invalid reference. Sheet \"Sheet3\" could not be found.".to_string())
596        );
597        assert!(r1c1("'My sheet!R2C3").is_err());
598        assert!(r1c1("'My sheet'R2C3").is_err());
599        assert!(r1c1("Sheet1!").is_err());
600    }
601
602    #[test]
603    fn test_parse_r1c1_ranges() {
604        assert_eq!(r1c1("R1C1:R1C768"), range(1, (1, 1), (1, 768)));
605        assert_eq!(r1c1("R2C3:R5C7"), range(1, (2, 3), (5, 7)));
606        assert_eq!(r1c1("R2C3:R2C3"), range(1, (2, 3), (2, 3)));
607        assert_eq!(r1c1("R[-1]C[-1]:R[1]C[1]"), range(1, (4, 2), (6, 4)));
608        assert_eq!(r1c1("R1C1:RC"), range(1, (1, 1), (5, 3)));
609        // The corners are put in order
610        assert_eq!(r1c1("R5C7:R2C3"), range(1, (2, 3), (5, 7)));
611        assert_eq!(r1c1("R5C3:R2C7"), range(1, (2, 3), (5, 7)));
612    }
613
614    #[test]
615    fn test_parse_r1c1_whole_rows_and_columns() {
616        assert_eq!(r1c1("R2"), range(1, (2, 1), (2, LAST_COLUMN)));
617        assert_eq!(r1c1("R"), range(1, (5, 1), (5, LAST_COLUMN)));
618        assert_eq!(r1c1("R[1]"), range(1, (6, 1), (6, LAST_COLUMN)));
619        assert_eq!(r1c1("R2:R4"), range(1, (2, 1), (4, LAST_COLUMN)));
620        assert_eq!(r1c1("C2"), range(1, (1, 2), (LAST_ROW, 2)));
621        assert_eq!(r1c1("C"), range(1, (1, 3), (LAST_ROW, 3)));
622        assert_eq!(r1c1("C[-2]"), range(1, (1, 1), (LAST_ROW, 1)));
623        assert_eq!(r1c1("C4:C2"), range(1, (1, 2), (LAST_ROW, 4)));
624        assert_eq!(r1c1("Sheet1!C:C[1]"), range(0, (1, 3), (LAST_ROW, 4)));
625    }
626
627    #[test]
628    fn test_parse_r1c1_offsets_go_round_the_sheet() {
629        // Relative to C5: five rows up is the last row, and so on
630        assert_eq!(r1c1("R[-4]C"), cell(1, 1, 3));
631        assert_eq!(r1c1("R[-5]C"), cell(1, LAST_ROW, 3));
632        assert_eq!(r1c1("R[-6]C"), cell(1, LAST_ROW - 1, 3));
633        assert_eq!(r1c1("R[1048571]C"), cell(1, LAST_ROW, 3));
634        assert_eq!(r1c1("R[1048572]C"), cell(1, 1, 3));
635        assert_eq!(r1c1("RC[-2]"), cell(1, 5, 1));
636        assert_eq!(r1c1("RC[-3]"), cell(1, 5, LAST_COLUMN));
637        assert_eq!(r1c1("RC[16381]"), cell(1, 5, LAST_COLUMN));
638        assert_eq!(r1c1("RC[16382]"), cell(1, 5, 1));
639        assert_eq!(r1c1("R[-5]C[-3]"), cell(1, LAST_ROW, LAST_COLUMN));
640        // The longest offsets
641        assert_eq!(r1c1("R[1048575]C"), cell(1, 4, 3));
642        assert_eq!(r1c1("R[-1048575]C"), cell(1, 6, 3));
643        assert_eq!(r1c1("RC[16383]"), cell(1, 5, 2));
644        assert_eq!(r1c1("RC[-16383]"), cell(1, 5, 4));
645        // Whole rows and columns
646        assert_eq!(
647            r1c1("R[-5]"),
648            range(1, (LAST_ROW, 1), (LAST_ROW, LAST_COLUMN))
649        );
650        assert_eq!(
651            r1c1("C[-3]"),
652            range(1, (1, LAST_COLUMN), (LAST_ROW, LAST_COLUMN))
653        );
654        // A range whose first corner goes round: the corners are put in order
655        assert_eq!(r1c1("R[-5]C:R[-3]C"), range(1, (2, 3), (LAST_ROW, 3)));
656        // Once round the sheet, or more, is not an offset
657        for reference in [
658            "R[1048576]C",
659            "R[-1048576]C",
660            "RC[16384]",
661            "RC[-16384]",
662            "R[2000000]C",
663            "C[-20000]",
664        ] {
665            assert!(r1c1(reference).is_err(), "{reference}");
666        }
667    }
668
669    #[test]
670    fn test_parse_r1c1_largest_offsets() {
671        // The largest numbers an offset can be written with. The lowest one
672        // has no positive counterpart, so it cannot be turned into a length.
673        for reference in [
674            "R[-2147483648]C",
675            "RC[-2147483648]",
676            "R[-2147483648]",
677            "C[-2147483648]",
678            "R[-2147483647]C",
679            "R[2147483647]C",
680            "RC[2147483647]",
681            "R[-2147483648]C:R[2147483647]C",
682            // These do not fit in a number
683            "R[-2147483649]C",
684            "R[2147483648]C",
685        ] {
686            assert!(r1c1(reference).is_err(), "{reference}");
687        }
688    }
689
690    #[test]
691    fn test_parse_r1c1_errors() {
692        for reference in [
693            "",
694            "A1",
695            "$A$1",
696            "A1:B2",
697            "R1C1:",
698            ":R1C1",
699            "R1C1:B2",
700            "R1C1:R2",
701            "R1:C1",
702            "C1:R2C2",
703            "R1C1 ",
704            " R1C1",
705            "R1C1x",
706            "R1C1:R2C2:R3C3",
707            "R[1C1",
708            "R[]C1",
709            "R[a]C1",
710            "R1C[1",
711            "R-1C1",
712            "R[+1]C1",
713            // Outside of the sheet
714            "R0C1",
715            "R1C0",
716            "R1048577C1",
717            "R1C16385",
718            "R99999999999C1",
719            "R[99999999999]C1",
720            "R0",
721            "C0",
722        ] {
723            assert!(r1c1(reference).is_err(), "{reference}");
724        }
725    }
726
727    #[test]
728    fn test_value_needs_quoting() {
729        let en_language = get_language("en").expect("en language expected");
730
731        assert!(!value_needs_quoting("", en_language));
732        assert!(!value_needs_quoting("hello", en_language));
733
734        assert!(value_needs_quoting("12", en_language));
735        assert!(value_needs_quoting("true", en_language));
736        assert!(value_needs_quoting("False", en_language));
737
738        assert!(value_needs_quoting("=A1", en_language));
739
740        assert!(value_needs_quoting("#REF!", en_language));
741        assert!(value_needs_quoting("#NAME?", en_language));
742    }
743}