Shared Rust kernels

catalogue/text_format

All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.

read_value · 26 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// Text argument `index` read as VALUE reads it under the call's locale profile
/// ([`crate::value_text`]): #VALUE! for text the grammar does not read and for a value that is
/// not text; an error is the result; refused without a profile.
pub(super) fn read_value(site: &Site<'_>, a: &[CalcValue], index: usize) -> Step<Reading> {
    match a[index].core_ref() {
        CoreRef::Text(text) => {
            let environment = site.environment().ok_or_else(|| site.not_modelled(None))?;
            let profile = environment
                .profile
                .as_ref()
                .ok_or_else(|| site.not_modelled(None))?;
            let context = ReadContext {
                profile,
                date_system: environment.date_system,
                now: environment.now,
            };
            value_text::read(text.utf16_code_units(), &context).map_err(|unread| match unread {
                Unread::Rejected => Exit::Error(WorksheetErrorCode::Value),
                Unread::NotModelled => site.not_modelled(Some(index)),
            })
        }
        CoreRef::Error(code) => fail(code),
        _ if a[index].callable_value().is_some() => Err(site.not_modelled(Some(index))),
        _ => fail(WorksheetErrorCode::Value),
    }
}

Used by 14 functions.

value · 21 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// VALUE: a number as it is, a blank as 0, a logical #VALUE!, text read as number text, a date
/// or a time.
fn value(site: &Site<'_>, a: &[CalcValue]) -> Step<CalcValue> {
    match a[0].core_ref() {
        CoreRef::Number(_) | CoreRef::Empty | CoreRef::Missing => {
            site.number(site.number_argument(0, &a[0])?)
        }
        CoreRef::Logical(_) => fail(WorksheetErrorCode::Value),
        CoreRef::Text(text) => {
            if text.len_utf16_code_units() > MAX_READER_TEXT {
                return fail(WorksheetErrorCode::Value);
            }
            if let Ok(Numeric::Decimal(decimal)) = coerce::number(&a[0]) {
                return site.number(site.argument(0, decimal.nearest())?);
            }
            let reading = read_value(site, a, 0)?;
            site.number(reading.value())
        }
        _ => read_value(site, a, 0).and_then(|reading| site.number(reading.value())),
    }
}

Used by 12 functions.

profile · 9 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// The call's environment and its locale profile, or the refusal of a call without them.
fn profile<'a>(site: &'a Site<'_>) -> Step<(&'a CalcEnvironment, &'a FormatProfile)> {
    let environment = site.environment().ok_or_else(|| site.not_modelled(None))?;
    let profile = environment
        .profile
        .as_ref()
        .ok_or_else(|| site.not_modelled(None))?;
    Ok((environment, profile))
}

Used by 21 functions.

rendered · 7 lines · calc-func/src/catalogue/text_format.rs on GitHub
fn rendered(site: &Site<'_>, result: Result<String, Failure>) -> Step<CalcValue> {
    match result {
        Ok(text) => Ok(crate::catalogue::kit::text_value(&text)),
        Err(Failure::Value) => fail(WorksheetErrorCode::Value),
        Err(Failure::NotModelled) => Err(site.not_modelled(None)),
    }
}

Used by 18 functions.

text · 56 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// TEXT: a number (or text that reads as one) rendered with the code, other text through the
/// code's text section.
fn text(site: &Site<'_>, a: &[CalcValue]) -> Step<CalcValue> {
    if a[0].is_excel_overflow() {
        let code = text_arg(site, a, 1)?.to_string_lossy();
        if code.eq_ignore_ascii_case("general") || code == "@" {
            let default = crate::locale::format_profile(LocaleId::EnZa);
            let profile = site.environment().and_then(|e| e.profile.as_ref()).unwrap_or(&default);
            return Ok(crate::catalogue::kit::text_value(&format::general(
                profile, a[0].as_number().expect("qualified overflow number"),
            )));
        }
        return fail(WorksheetErrorCode::Value);
    }
    if let (Some(environment), CoreRef::Number(_), CoreRef::Text(code)) =
        (site.environment(), a[0].core_ref(), a[1].core_ref())
    {
        if environment.profile.is_none() {
            let number = site.number_argument(0, &a[0])?;
            return rendered(site, format::text_declared_date_locale(
                environment.date_system, number, &code.to_string_lossy(),
            ));
        }
    }
    let (environment, profile) = profile(site)?;
    let code = text_arg(site, a, 1)?.to_string_lossy();
    let number = match a[0].core_ref() {
        CoreRef::Number(_) | CoreRef::Empty | CoreRef::Missing => {
            site.number_argument(0, &a[0])?
        }
        CoreRef::Error(code) => return fail(code),
        CoreRef::Logical(_) | CoreRef::Text(_) => {
            let as_number = if matches!(a[0].core_ref(), CoreRef::Text(_)) {
                match site.number_argument(0, &a[0]) {
                    Ok(n) => Some(n),
                    Err(Exit::Error(WorksheetErrorCode::Value)) => None,
                    Err(other) => return Err(other),
                }
            } else {
                None
            };
            match as_number {
                Some(n) => n,
                None => {
                    let shown = text_arg(site, a, 0)?.to_string_lossy();
                    return rendered(site, format::text_of_text(&shown, &code, profile));
                }
            }
        }
        _ => return Err(site.not_modelled(Some(0))),
    };
    rendered(
        site,
        format::text(profile, environment.date_system, number, &code),
    )
}

Used by 18 functions.

places · 11 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// The places of FIXED and DOLLAR: 2 when the call leaves them out; beyond 127 is #VALUE!.
fn places(site: &Site<'_>, a: &[CalcValue]) -> Step<i32> {
    if a.len() < 2 || a[1].is_missing() {
        return Ok(2);
    }
    let places = num(site, a, 1)?.trunc();
    if places > 127.0 {
        return fail(WorksheetErrorCode::Value);
    }
    Ok(places.max(-400.0) as i32)
}

Used by 2 functions.

given · 4 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// An optional argument: `None` when the call does not reach it or leaves it empty.
fn given(a: &[CalcValue], index: usize) -> bool {
    a.get(index).is_some_and(|value| !value.is_missing())
}

Used by 9 functions.

binary_flag · 15 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// A 0-or-1 flag argument (match modes, formats): another number is #VALUE!.
fn binary_flag(site: &Site<'_>, a: &[CalcValue], index: usize) -> Step<bool> {
    if !given(a, index) {
        return Ok(false);
    }
    let n = num(site, a, index)?;
    if !n.is_finite() {
        return fail(WorksheetErrorCode::Value);
    }
    match n.trunc() {
        0.0 => Ok(false),
        1.0 => Ok(true),
        _ => fail(WorksheetErrorCode::Value),
    }
}

Used by 8 functions.

delimiters · 18 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// The texts a delimiter argument gives: a single text, or each cell of an array or reference.
fn delimiters(
    site: &Site<'_>,
    a: &[CalcValue],
    index: usize,
    sv: &mut EvalServices<'_>,
) -> Step<Vec<ExcelText>> {
    let g = grid(site, a, index, sv)?;
    let mut out = Vec::with_capacity(g.cells.len());
    for value in &g.cells {
        if matches!(value.core_ref(), CoreRef::Array(_) | CoreRef::Reference(_)) {
            return Err(site.not_modelled(Some(index)));
        }
        let units = site.argument(index, coerce::text(value))?;
        out.push(ExcelText::from_utf16_code_units(units.into_owned()));
    }
    Ok(out)
}

Used by 3 functions.

text_delim · 32 lines · calc-func/src/catalogue/text_format.rs on GitHub
fn text_delim(site: &Site<'_>, a: &[CalcValue], sv: &mut EvalServices<'_>, side: Side) -> Step<CalcValue> {
    let text = text_arg(site, a, 0)?;
    let delimiters = delimiters(site, a, 1, sv)?;
    let instance = if given(a, 2) {
        let n = num(site, a, 2)?;
        if !n.is_finite() || n.trunc().abs() > 1e9 {
            return fail(WorksheetErrorCode::Value);
        }
        n.trunc() as isize
    } else {
        1
    };
    let case_insensitive = binary_flag(site, a, 3)?;
    let match_end = binary_flag(site, a, 4)?;
    // fn1 Excel 16.0.20527: an explicitly supplied instance is validated against the empty
    // input before fallback/match_end. An empty delimiter still returns empty text.
    if given(a, 2) && text.len_utf16_code_units() == 0
        && !delimiters.iter().any(|d| d.len_utf16_code_units() == 0)
    {
        return fail(WorksheetErrorCode::Value);
    }
    match k::delimited(side, &text, &delimiters, instance, case_insensitive, match_end) {
        Delimited::Text(found) => Ok(CalcValue::text(found)),
        Delimited::BadInstance => fail(WorksheetErrorCode::Value),
        Delimited::NotFound if a.len() > 5 => Ok(if a[5].is_missing() {
            CalcValue::error(WorksheetErrorCode::NA)
        } else {
            a[5].clone()
        }),
        Delimited::NotFound => fail(WorksheetErrorCode::NA),
    }
}

Used by 2 functions.

value_text · 14 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// A value's text as VALUETOTEXT and ARRAYTOTEXT write it, concise or strict (text quoted).
fn value_text(site: &Site<'_>, index: usize, value: &CalcValue, strict: bool) -> Step<String> {
    Ok(match value.core_ref() {
        CoreRef::Number(_) => {
            String::from_utf16_lossy(&site.argument(index, coerce::text(value))?)
        }
        CoreRef::Text(t) if strict => format!("\"{}\"", t.to_string_lossy().replace('"', "\"\"")),
        CoreRef::Text(t) => t.to_string_lossy(),
        CoreRef::Logical(b) => if b { "TRUE" } else { "FALSE" }.to_owned(),
        CoreRef::Error(code) => k::error_literal(code).to_owned(),
        CoreRef::Empty | CoreRef::Missing => String::new(),
        CoreRef::Array(_) | CoreRef::Reference(_) => return Err(site.not_modelled(Some(index))),
    })
}

Used by 2 functions.

case_sensitive · 4 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// The case sensitivity of the REGEX functions: 0 (the default) case-sensitive, 1 not.
fn case_sensitive(site: &Site<'_>, a: &[CalcValue], index: usize) -> Step<bool> {
    Ok(!binary_flag(site, a, index)?)
}

Used by 3 functions.

regex · 18 lines · calc-func/src/catalogue/text_format.rs on GitHub
fn regex(
    site: &Site<'_>,
    text: &ExcelText,
    pattern: &ExcelText,
    case_sensitive: bool,
    first_only: bool,
) -> Step<(Vec<char>, Vec<(usize, usize)>)> {
    if !tk::is_well_formed(text) || !tk::is_well_formed(pattern) {
        return Err(site.not_modelled(None));
    }
    k::regex_matches(
        &text.to_string_lossy(),
        &pattern.to_string_lossy(),
        case_sensitive,
        first_only,
    )
    .map_err(|_| site.not_modelled(Some(1)))
}

Used by 3 functions.

width_conversion · 14 lines · calc-func/src/catalogue/text_format.rs on GitHub
/// ASC and DBCS change character widths only under a double-byte language; under every other
/// profile Excel gives the text as it is (Excel 16.0.20527 under en-ZA, the catalogue's sheet
/// dczsn3_bytes: full-width and half-width forms, kana, CJK and Hangul are kept). Under the
/// ja-JP, ko-KR and zh-CN profiles they are refused (bead dc-zsn.3).
fn width_conversion(site: &Site<'_>, a: &[CalcValue]) -> Step<CalcValue> {
    let (_, profile) = profile(site)?;
    if matches!(
        profile.id,
        LocaleId::JaJp | LocaleId::KoKr | LocaleId::ZhCn
    ) {
        return Err(site.not_modelled(None));
    }
    Ok(CalcValue::text(text_arg(site, a, 0)?))
}

Used by 2 functions.