Skip to main content

ravenrs_sheet/calc/eval/
format.rs

1use super::{CellError, Value};
2use crate::base::{CellRef, MAX_COLS, MAX_ROWS};
3
4/// CELL("format") code for a number format.
5pub(super) fn cell_format_code(code: &str) -> String {
6    let c = code.to_ascii_lowercase();
7    if c == "general" {
8        return "G".into();
9    }
10    if c.contains('%') {
11        return format!("P{}", decimals(&c));
12    }
13    if c.contains('e') && c.contains('0') {
14        return format!("S{}", decimals(&c));
15    }
16    if c.contains('$') {
17        return format!("C{}", decimals(&c));
18    }
19    if c.contains('y') || c.contains('d') {
20        return "D1".into();
21    }
22    if c.contains('h') {
23        return "D9".into();
24    }
25    if c.contains(',') {
26        return format!(",{}", decimals(&c));
27    }
28    format!("F{}", decimals(&c))
29}
30
31fn decimals(c: &str) -> usize {
32    c.split_once('.').map_or(0, |(_, d)| d.chars().take_while(|ch| *ch == '0').count())
33}
34
35pub(super) fn same_kind(a: &Value, b: &Value) -> bool {
36    std::mem::discriminant(a) == std::mem::discriminant(b) || (a.is_empty() || b.is_empty())
37}
38
39/// Converts an R1C1 reference text to A1 (for INDIRECT(…, FALSE)). `Ok(None)` means the text is
40/// not R1C1; `Err` means it is R1C1 but points past the sheet.
41pub fn r1c1_to_a1(s: &str, at: CellRef) -> Result<Option<String>, CellError> {
42    let (sheet, body) = match s.rsplit_once('!') {
43        Some((sh, b)) => (format!("{sh}!"), b),
44        None => (String::new(), s),
45    };
46    let conv = |part: &str| -> Result<Option<String>, CellError> {
47        let u = part.to_ascii_uppercase();
48        let Some((rpart, cpart)) = u.strip_prefix('R').and_then(|rest| rest.split_once('C')) else { return Ok(None) };
49        // `None`: not R1C1. `Some(None)`: R1C1, but past the sheet.
50        let num = |p: &str, base: u32, max: u32| -> Option<Option<(u32, bool)>> {
51            let (v, absolute) = if p.is_empty() {
52                (Some(i64::from(base)), false)
53            } else if let Some(inner) = p.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
54                (i64::from(base).checked_add(inner.parse().ok()?), false)
55            } else {
56                (p.parse::<u32>().ok()?.checked_sub(1).map(i64::from), true)
57            };
58            Some(v.filter(|v| (0..i64::from(max)).contains(v)).map(|v| (v as u32, absolute)))
59        };
60        let (Some(r), Some(c)) = (num(rpart, at.row, MAX_ROWS), num(cpart, at.col, MAX_COLS)) else { return Ok(None) };
61        let (Some((r, ra)), Some((c, ca))) = (r, c) else { return Err(CellError::Ref) };
62        Ok(Some(format!("{}{}{}{}", if ca { "$" } else { "" }, crate::base::col_to_letters(c), if ra { "$" } else { "" }, r + 1)))
63    };
64    let out = match body.split_once(':') {
65        Some((a, b)) => match (conv(a)?, conv(b)?) {
66            (Some(a), Some(b)) => format!("{a}:{b}"),
67            _ => return Ok(None),
68        },
69        None => match conv(body)? {
70            Some(x) => x,
71            None => return Ok(None),
72        },
73    };
74    Ok(Some(format!("{sheet}{out}")))
75}