freemarker 0.1.1

Apache FreeMarker 语义兼容的 Rust 模板引擎(freemarker-core 迁移)
Documentation
//! 模型值构造与下沉、输出字符串化与格式化分派(docs/09 §2)
//! (对应 Java EvalUtil.coerceModelToStringOrMarkup / Environment.getTemplateDateFormat 家族)。

use super::environment_loop::LocalEntry;
use super::environment_macros::{LambdaValue, MacroValue, Namespace};
use super::Environment;
use crate::core::Expr;
use crate::error::{Result, TemplateError};
use crate::template::{TModel, TemplateHashModel, TemplateHashModelEx};
use indexmap::IndexMap;
use std::rc::Rc;

/// 命名空间值模型(Java Namespace 作为 TemplateHashModel 值;FTL 类型 extended_hash)
pub fn namespace_model(ns: Rc<Namespace>) -> TModel {
    let h: Rc<dyn TemplateHashModel> = ns.clone();
    let ex: Rc<dyn TemplateHashModelEx> = ns.clone();
    TModel {
        hash: Some(h),
        hash_ex: Some(ex),
        internal: Some(ns),
        type_name: "extended_hash",
        kind: crate::template::ModelKind::Hash,
        ..TModel::nothing()
    }
}

/// `.vars` 快照 —— 对应 Java `BuiltinVariable.VarsHash`(BuiltinVariable.java:330-337:
/// get(key) = env.getVariable 完整解析链)。v1 以快照近似活视图:按
/// 局部上下文(循环变量)> 宏帧局部变量 > 当前命名空间 > 全局命名空间 > 根模型 >
/// 共享变量的优先级合并(低优先级先入、高优先级覆盖)。
pub fn vars_snapshot(env: &Environment) -> IndexMap<String, TModel> {
    let mut map = IndexMap::new();
    // 根数据模型
    if let Some(ex) = &env.root.hash_ex {
        let keys = ex.keys().unwrap_or_default();
        for k in keys {
            if let Ok(Some(v)) = ex.get(&k) {
                map.insert(k, v);
            }
        }
    }
    // 共享变量
    for (k, v) in &env.template.configuration.shared_vars {
        map.insert(k.clone(), v.clone());
    }
    // 全局命名空间
    for k in env.get_global_namespace().var_names() {
        if let Some(v) = env.get_global_namespace().get_member(&k) {
            map.insert(k, v);
        }
    }
    // 当前命名空间
    for k in env.get_current_namespace().var_names() {
        if let Some(v) = env.get_current_namespace().get_member(&k) {
            map.insert(k, v);
        }
    }
    // 宏帧局部变量
    if let Some(frame) = env.macro_frames.last() {
        for (k, v) in frame.locals.borrow().iter() {
            map.insert(k.clone(), v.clone());
        }
    }
    // 局部上下文(循环变量/body 参数)
    for entry in env.local_stack.iter().rev() {
        if let LocalEntry::Loop(lc) = entry {
            let c = lc.borrow();
            if !c.var_name.is_empty() {
                if let Some(v) = c.get(&c.var_name, env.settings.fallback_on_null_loop_variable) {
                    map.insert(c.var_name.clone(), v);
                }
                if let Some(v2) = &c.var2_name {
                    if let Some(v) = c.get(v2, env.settings.fallback_on_null_loop_variable) {
                        map.insert(v2.clone(), v);
                    }
                }
            }
        } else if let LocalEntry::Body(bc) = entry {
            for (k, v) in bc.vars.iter() {
                map.insert(k.clone(), v.clone());
            }
        }
    }
    map
}

/// 宏/函数值模型(Java Macro 对象;`?is_macro` 依据 kind 判定)
pub fn macro_model(mv: Rc<MacroValue>) -> TModel {
    TModel {
        internal: Some(mv),
        type_name: "macro",
        kind: crate::template::ModelKind::Macro,
        ..TModel::nothing()
    }
}

/// lambda 值模型(Java LocalLambdaExpression 求值结果;v1 仅存槽位)
pub fn lambda_model(params: Vec<String>, body: Rc<Expr>) -> TModel {
    TModel {
        internal: Some(Rc::new(LambdaValue { params, body })),
        type_name: "lambda",
        kind: crate::template::ModelKind::Lambda,
        ..TModel::nothing()
    }
}

/// 布尔格式的 true/false 字符串(Java `getTrueStringValue`/`getFalseStringValue`;
/// "true,false" 遗留默认 → None——视为未设置)
pub(crate) fn boolean_format_strings(env: &Environment) -> Option<(String, String)> {
    let format = env.settings.boolean_format.as_str();
    if format == "true,false" {
        // Java parseBooleanFormat(Configurable.java:1087-1090):BOOLEAN_FORMAT_LEGACY_DEFAULT
        // → null(视为未设置,即使显式设置)→ getTrueStringValue null → 报错
        None
    } else if format == "c" {
        Some(("true".to_string(), "false".to_string()))
    } else {
        format
            .split_once(',')
            .map(|(t, f)| (t.to_string(), f.to_string()))
    }
}

/// 布尔格式化 —— 对应 Java `Environment.formatBoolean`(Environment.java:1795):
/// - "true,false"(BOOLEAN_FORMAT_LEGACY_DEFAULT)→ parseBooleanFormat 返回 null
///   (Configurable.java:1087-1090,显式设置亦然)→ 视为未设置:
///   fallback=false(插值/字符串拼接路径)报 legacy 错误(jar 实测 `${false}` 默认配置报错);
///   fallback=true(?string 路径)返回 true/false;
/// - "c" → C 格式 true/false(Java parseBooleanFormat 空数组 → CFormat.getTrue/FalseString);
/// - 其余 → 按首个逗号切分(Java indexOf(',');不做 trim)。
pub(crate) fn boolean_format(env: &Environment, b: bool, fallback: bool) -> Result<String> {
    let format = env.settings.boolean_format.as_str();
    if format == "true,false" {
        if fallback {
            return Ok(if b {
                "true".to_string()
            } else {
                "false".to_string()
            });
        }
        return Err(TemplateError::misc(
            "Can't convert boolean to string automatically, because the \"boolean_format\" setting was \"true,false\", which is the legacy deprecated default, and we treat it as if no format was set. This is the default configuration; you should provide the format explicitly for each place where you print a boolean.\n\n----\nTip: Write something like myBool?string('yes', 'no') to specify boolean formatting in place.\n----\nTip: If you want \"true\"/\"false\" result as you are generating computer-language output (not for direct human consumption), then use \"?c\", like ${myBool?c}. (If you always generate computer-language output, then it's might be reasonable to set the \"boolean_format\" setting to \"c\" instead.)\n----\nTip: If you need the same two values on most places, the programmers can set the \"boolean_format\" setting to something like \"yes,no\". However, then it will be easy to unwillingly format booleans like that.\n----",
        ));
    }
    if format == "c" {
        return Ok(if b {
            "true".to_string()
        } else {
            "false".to_string()
        });
    }
    match format.split_once(',') {
        Some((t, f)) => Ok(if b { t.to_string() } else { f.to_string() }),
        None => Err(TemplateError::misc(format!(
            "Setting value must be a string that contains two comma-separated values for true and false, or it must be \"c\", but it was {format:?}."
        ))),
    }
}

/// 模型 → 输出字符串 —— 对应 Java 插值/字符串拼接的模型转字符串规则
/// (Java EvalUtil.coerceModelToStringOrMarkup):
/// - 标量原样;数字按 number_format("number" → canonical plain、"c" → C 格式、
///   其余 → DecimalFormat 子集,见 builtins/format.rs);
/// - 布尔:classic-compatible 模式(Java EvalUtil.coerceModelToTextualCommon :486-518:
///   true → "true"、false → "";双角色标量模型优先返回字符串),否则 boolean_format;
/// - 日期按 date_format/time_format/date_time_format 设置(Java formatDateToPlainText)。
pub(crate) fn model_to_string(env: &mut Environment, m: &TModel) -> Result<String> {
    if m.is_nothing() {
        // Java EvalUtil.coerceModelToTextualCommon :482-494:tm == null → classic 兼容
        // 模式回退空串;strict 模式为 InvalidReferenceException(本引擎 strict 的
        // 缺失变量在解析层已抛 Err,此处仅显式 null 值会到达)
        if env.settings.classic_compatible {
            return Ok(String::new());
        }
    }
    if let Some(s) = &m.scalar {
        return s.as_string();
    }
    if let Some(n) = &m.number {
        return n
            .as_number()
            .and_then(|n| crate::builtins::format::format_number(env, &n));
    }
    if let Some(b) = &m.boolean {
        let bv = b.as_boolean()?;
        // Java coerceModelToTextualCommon:classic 模式布尔 → "true"/""(先于 formatBoolean)
        if env.settings.classic_compatible {
            return Ok(if bv {
                "true".to_string()
            } else {
                String::new()
            });
        }
        // Java EvalUtil.coerceModelToStringOrMarkup:formatBoolean(value, false)
        return boolean_format(env, bv, false);
    }
    if let Some(d) = &m.date {
        let d = d.as_date()?;
        let format = match d.kind {
            crate::value::DateType::Date => env.settings.date_format.clone(),
            crate::value::DateType::Time => env.settings.time_format.clone(),
            crate::value::DateType::DateTime => env.settings.date_time_format.clone(),
            // Java newCantFormatUnknownTypeDateException(_MessageUtil.java:38-45):
            // 未知类型须先 ?date/?time/?datetime;消息含 UNKNOWN_DATE_TO_STRING_TIPS
            crate::value::DateType::Unknown => {
                return Err(TemplateError::misc(
                    "Can't convert the date-like value to string because it isn't known if it's a date (no time part), time or date-time value.\n\n----\nTip: Use ?date, ?time, or ?datetime to tell FreeMarker the exact type.\n----\nTip: If you need a particular format only once, use ?string(pattern), like ?string('dd.MM.yyyy HH:mm:ss'), to specify which fields to display. \n----",
                ))
            }
        };
        return format_date_value(env, &d, &format);
    }
    Err(TemplateError::type_mismatch(
        "string-like value",
        m.type_name,
    ))
}

/// 日期格式化分派 —— 对应 Java `Environment.getTemplateDateFormat` 的格式串解析
/// (getTemplateDateFormatWithoutCache :2304-2333):
/// `xs...` → XML Schema 格式、`iso...` → ISO 8601 格式、其余 → Java 模式
/// (命名模式 short/medium/long/full 或 SimpleDateFormat 子集)
pub(crate) fn format_date_value(
    env: &Environment,
    d: &crate::value::DateValue,
    format_string: &str,
) -> Result<String> {
    use crate::builtins::iso_date_format::{format_iso_like, is_iso_like, parse_iso_params};
    // Java Environment.java:2336-2346(getTemplateDateFormatWithoutCache):`@name`
    // → 自定义日期格式查找;v1 无注册机制 → 一律 UndefinedCustomFormatException
    // (date/time/datetime 三种格式串共用此检查,消息统一 "No custom date format...")
    if let Some(name) = crate::builtins::format::custom_format_name(format_string) {
        return Err(TemplateError::misc(format!(
            "No custom date format was defined with name {}",
            crate::builtins::format::j_quote(&name)
        )));
    }
    // Java Environment.java:2184:格式串无效 → "Can't create ... based on format string" 包装
    // (dateformat-iso-like 用例断言消息含 "format string")
    let r: Result<String> = (|| match is_iso_like(format_string) {
        Some((prefix_len, xs_mode)) => {
            let spec = parse_iso_params(format_string, prefix_len, xs_mode)?;
            format_iso_like(d, &spec, xs_mode, &env.settings.time_zone)
        }
        None => {
            let locale = env.settings.locale.as_str();
            let pattern = crate::builtins::java_date_format::resolve_named_style(
                format_string,
                d.kind,
                locale,
            )
            .unwrap_or_else(|| format_string.to_string());
            crate::builtins::java_date_format::format_java(
                &pattern,
                d,
                locale,
                &env.settings.time_zone,
            )
        }
    })();
    r.map_err(|e| {
        TemplateError::misc(format!(
            "Can't create date/time/datetime format based on format string \"{format_string}\". Reason given: {e}"
        ))
    })
}

/// 日期解析分派 —— 对应 Java `TemplateDateFormat.parse`(ISO/XS/Java 三种格式)
pub(crate) fn parse_date_value(
    env: &Environment,
    s: &str,
    kind: crate::value::DateType,
    format_string: &str,
) -> Result<crate::value::DateValue> {
    use crate::builtins::iso_date_format::{is_iso_like, parse_iso_like, parse_iso_params};
    match is_iso_like(format_string) {
        Some((prefix_len, xs_mode)) => {
            let spec = parse_iso_params(format_string, prefix_len, xs_mode)?;
            parse_iso_like(s, kind, &spec, &env.settings.time_zone, xs_mode)
        }
        None => {
            let locale = env.settings.locale.as_str();
            let pattern =
                crate::builtins::java_date_format::resolve_named_style(format_string, kind, locale)
                    .unwrap_or_else(|| format_string.to_string());
            crate::builtins::java_date_format::parse_java(
                &pattern,
                s,
                kind,
                locale,
                &env.settings.time_zone,
            )
        }
    }
}

/// 表达式求值后转输出字符串(`<#include path>`、`<#stop msg>` 等;缺失 → InvalidReference)
pub(crate) fn eval_to_string(env: &mut Environment, e: &Expr) -> Result<String> {
    let m = crate::core::eval::eval(env, e)?;
    if m.is_nothing() {
        return Err(TemplateError::invalid_reference(expr_desc(e)));
    }
    model_to_string(env, &m)
}

/// 表达式描述(Java `getCanonicalForm()` 的 v1 简化子集;错误消息用)
pub(crate) fn expr_desc(e: &Expr) -> String {
    use crate::core::ExprKind as K;
    match &e.kind {
        K::Str(s) => format!("\"{}\"", s),
        K::Num(n) => n.to_plain_string(),
        K::Bool(b) => b.to_string(),
        K::Ident(n) => n.clone(),
        K::Dot { target, name } => format!("{}.{}", expr_desc(target), name),
        K::DynKey { target, key } => format!("{}[{}]", expr_desc(target), expr_desc(key)),
        K::Call { callee, args } => format!(
            "{}({})",
            expr_desc(callee),
            args.iter().map(expr_desc).collect::<Vec<_>>().join(", ")
        ),
        K::Paren(i) => format!("({})", expr_desc(i)),
        K::Not(i) => format!("!{}", expr_desc(i)),
        K::UnaryMinus(i) => format!("-{}", expr_desc(i)),
        K::Add(a, b) => format!("{} + {}", expr_desc(a), expr_desc(b)),
        K::Sub(a, b) => format!("{} - {}", expr_desc(a), expr_desc(b)),
        K::Mul(a, b) => format!("{} * {}", expr_desc(a), expr_desc(b)),
        K::Div(a, b) => format!("{} / {}", expr_desc(a), expr_desc(b)),
        K::Mod(a, b) => format!("{} % {}", expr_desc(a), expr_desc(b)),
        K::Eq(a, b) => format!("{} == {}", expr_desc(a), expr_desc(b)),
        K::NotEq(a, b) => format!("{} != {}", expr_desc(a), expr_desc(b)),
        K::Gt(a, b) => format!("{} > {}", expr_desc(a), expr_desc(b)),
        K::Gte(a, b) => format!("{} >= {}", expr_desc(a), expr_desc(b)),
        K::Lt(a, b) => format!("{} < {}", expr_desc(a), expr_desc(b)),
        K::Lte(a, b) => format!("{} <= {}", expr_desc(a), expr_desc(b)),
        K::And(a, b) => format!("{} && {}", expr_desc(a), expr_desc(b)),
        K::Or(a, b) => format!("{} || {}", expr_desc(a), expr_desc(b)),
        K::Default { target, .. } => format!("{}!", expr_desc(target)),
        K::Exists(t) => format!("{}??", expr_desc(t)),
        K::ListLit(items) => format!(
            "[{}]",
            items.iter().map(expr_desc).collect::<Vec<_>>().join(", ")
        ),
        K::BuiltIn { target, name, args } => match args {
            Some(args) => format!(
                "{}?{}({})",
                expr_desc(target),
                name,
                args.iter().map(expr_desc).collect::<Vec<_>>().join(", ")
            ),
            None => format!("{}?{}", expr_desc(target), name),
        },
        _ => "...".to_string(),
    }
}