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 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 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
206enum R1C1Part {
208 Cell(i32, i32),
210 Row(i32),
212 Column(i32),
214}
215
216fn 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
243fn 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
281fn 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 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
324pub(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
336pub 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 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 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 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 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 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 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 assert_eq!(r1c1("R[-5]C:R[-3]C"), range(1, (2, 3), (LAST_ROW, 3)));
656 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 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 "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 "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}