Skip to main content

sz_rust_mvc_facade/
view.rs

1//! 视图渲染器 — 对齐 PHP `think\View`
2//!
3//! ## PHP 对齐说明
4//! 对齐 PHP `think\View`(外观模式)+ `think\contract\TemplateHandlerInterface`(驱动接口)+
5//! `think\Template`(模板引擎核心)。PHP 使用 `ob_start` + `include`/`eval` 机制,
6//! Rust 改为直接返回 `String`,避免 I/O 副作用。
7//!
8//! ## 核心类型
9//! - [`View`]:视图入口(对齐 PHP `think\View`)
10//! - [`TemplateEngine`] trait:模板引擎接口(对齐 PHP `think\contract\TemplateHandlerInterface`)
11//! - [`SimpleTemplateEngine`]:默认模板引擎(对齐 PHP `think\Template` 的基本标签)
12//! - [`ViewConfig`]:视图配置(对齐 PHP `config/view.php`)
13//!
14//! ## 支持的标签
15//! - `{$var}` — 变量插值(对齐 PHP `parseVar`)
16//! - `{$var.attr}` — 嵌套属性(对齐 PHP `.` 语法,默认 array 模式)
17//! - `{$var|filter}` — 过滤器(对齐 PHP `parseVarFunction`)
18//! - `{$var|default=x}` — 默认值
19//! - `{$var?='x'}` / `{$var?:'x'}` / `{$var??'x'}` — 三元表达式
20//! - `{:func(args)}` — 函数调用(对齐 PHP `{:fun()}`)
21//! - `{//comment}` / `{/*comment*/}` — 注释(对齐 PHP `parseTag` 注释分支)
22//! - `{literal}...{/literal}` — 原文保留(对齐 PHP `parseLiteral`)
23//! - `{if}/{elseif}/{else}` — 条件判断(对齐 PHP Cx `tagIf`)
24//! - `{foreach}` — foreach 循环(对齐 PHP Cx `tagForeach`)
25//! - `{volist}` — volist 循环(对齐 PHP Cx `tagVolist`)
26//! - `{switch}/{case}/{default}` — switch 分支(对齐 PHP Cx `tagSwitch`)
27//! - `{for}` — for 循环(对齐 PHP Cx `tagFor`)
28//! - 配置模式布局(`layout_on=true`,对齐 PHP `compiler()`)
29//! - `{layout name="..." replace="..."}` — 标签模式布局(对齐 PHP `parseLayout()`)
30//! - `{__NOLAYOUT__}` — 单独禁用布局
31//! - `{extend name="..."}` — 模板继承(对齐 PHP `parseExtend()`)
32//! - `{block name="..."}...{/block}` — block 定义(对齐 PHP `parseBlock()`)
33//! - `{__BLOCK__}` / `{__block__}` — block 合并标记(对齐 PHP `str_replace`)
34//!
35//! ## PHP 源码参考
36//! - `think\framework\src\think\View.php`(195 行)
37//! - `think\framework\src\think\contract\TemplateHandlerInterface.php`
38//! - `think\framework\src\think\view\driver\Php.php`
39//! - `think-template\src\Template.php`(~1800 行)
40//! - `think-view\src\Think.php`
41//! - `config\view.php`
42
43use std::collections::HashMap;
44use std::path::PathBuf;
45use std::sync::Arc;
46
47use parking_lot::RwLock;
48use regex::Regex;
49use serde_json::Value;
50
51// Cx 标签库控制流标签(对齐 PHP `think\template\taglib\Cx`)
52pub mod template;
53
54// 模板布局(对齐 PHP `Template::compiler()` + `parseLayout()`)
55pub mod layout;
56
57// 模板继承(对齐 PHP `Template::parseExtend()` + `parseBlock()`)
58pub mod inheritance;
59
60// ============================================================================
61// 错误类型
62// ============================================================================
63
64/// 视图错误
65#[derive(Debug, thiserror::Error)]
66pub enum ViewError {
67    /// 模板文件未找到(对齐 PHP `TemplateNotFoundException`)
68    #[error("模板文件未找到: {0}")]
69    TemplateNotFound(String),
70
71    /// 模板语法错误
72    #[error("模板语法错误: {0}")]
73    SyntaxError(String),
74
75    /// 模板渲染错误
76    #[error("模板渲染错误: {0}")]
77    RenderError(String),
78
79    /// IO 错误
80    #[error("IO 错误: {0}")]
81    IoError(#[from] std::io::Error),
82}
83
84// ============================================================================
85// 类型别名
86// ============================================================================
87
88/// 模板变量数据(对齐 PHP `$data` 数组)
89pub type ViewData = HashMap<String, Value>;
90
91/// 内容过滤器(对齐 PHP `View::filter`,单值回调)
92pub type ContentFilter = Arc<dyn Fn(&str) -> String + Send + Sync>;
93
94/// 模板函数(对齐 PHP `{:func()}` 中的函数)
95pub type TemplateFn = Arc<dyn Fn(&[Value]) -> Result<Value, ViewError> + Send + Sync>;
96
97// ============================================================================
98// 视图配置
99// ============================================================================
100
101/// 视图配置(对齐 PHP `config/view.php` + `think\Template` 配置)
102#[derive(Debug, Clone)]
103pub struct ViewConfig {
104    /// 视图路径(对齐 PHP `view_path`)
105    pub view_path: PathBuf,
106
107    /// 视图后缀(对齐 PHP `view_suffix`,默认 'html')
108    pub view_suffix: String,
109
110    /// 视图分隔符(对齐 PHP `view_depr`,默认 '/')
111    pub view_depr: String,
112
113    /// 模板标签开始(对齐 PHP `tpl_begin`,默认 '{')
114    pub tpl_begin: String,
115
116    /// 模板标签结束(对齐 PHP `tpl_end`,默认 '}')
117    pub tpl_end: String,
118
119    /// 标签库开始(对齐 PHP `taglib_begin`,默认 '{')
120    pub taglib_begin: String,
121
122    /// 标签库结束(对齐 PHP `taglib_end`,默认 '}')
123    pub taglib_end: String,
124
125    /// 默认过滤器(对齐 PHP `default_filter`,默认 'htmlentities')
126    pub default_filter: String,
127
128    /// 布局开关(对齐 PHP `layout_on`,默认 false)
129    pub layout_on: bool,
130
131    /// 布局名称(对齐 PHP `layout_name`,默认 'layout')
132    pub layout_name: String,
133
134    /// 布局替换项(对齐 PHP `layout_item`,默认 '{__CONTENT__}')
135    pub layout_item: String,
136
137    /// 变量识别方式(对齐 PHP `tpl_var_identify`,默认 'array')
138    pub tpl_var_identify: String,
139}
140
141impl Default for ViewConfig {
142    fn default() -> Self {
143        Self {
144            view_path: PathBuf::from("view"),
145            view_suffix: "html".to_string(),
146            view_depr: "/".to_string(),
147            tpl_begin: "{".to_string(),
148            tpl_end: "}".to_string(),
149            taglib_begin: "{".to_string(),
150            taglib_end: "}".to_string(),
151            default_filter: "htmlentities".to_string(),
152            layout_on: false,
153            layout_name: "layout".to_string(),
154            layout_item: "{__CONTENT__}".to_string(),
155            tpl_var_identify: "array".to_string(),
156        }
157    }
158}
159
160// ============================================================================
161// 模板引擎接口
162// ============================================================================
163
164/// 模板引擎接口(对齐 PHP `think\contract\TemplateHandlerInterface`)
165///
166/// PHP 接口方法:
167/// - `exists(string $template): bool`
168/// - `fetch(string $template, array $data = []): void`(PHP 用 echo,Rust 返回 String)
169/// - `display(string $content, array $data = []): void`(PHP 用 echo,Rust 返回 String)
170/// - `config(array $config): void`
171/// - `getConfig(string $name): mixed`
172pub trait TemplateEngine: Send + Sync {
173    /// 模板是否存在(对齐 PHP `exists`)
174    fn exists(&self, template: &str) -> bool;
175
176    /// 渲染模板文件(对齐 PHP `fetch`)
177    fn fetch(&self, template: &str, data: &ViewData) -> Result<String, ViewError>;
178
179    /// 渲染字符串内容(对齐 PHP `display`)
180    fn display(&self, content: &str, data: &ViewData) -> Result<String, ViewError>;
181
182    /// 设置配置(对齐 PHP `config`)
183    fn set_config(&mut self, config: ViewConfig);
184
185    /// 读取配置(对齐 PHP `getConfig`)
186    fn get_config(&self, name: &str) -> Option<Value>;
187
188    /// 返回 `&dyn Any` 以支持 downcast(Rust 特有,无 PHP 对应)
189    fn as_any(&self) -> &dyn std::any::Any;
190}
191
192// ============================================================================
193// 简易模板引擎
194// ============================================================================
195
196/// 简易模板引擎(对齐 PHP `think\Template` 的基本标签解析)
197///
198/// ## 支持的标签
199///
200/// | 标签 | 示例 | 说明 |
201/// |------|------|------|
202/// | `{$var}` | `{$name}` | 变量插值 |
203/// | `{$var.attr}` | `{$user.name}` | 嵌套属性(对齐 PHP `.` 语法,array 模式) |
204/// | `{$var\|filter}` | `{$name\|upper}` | 过滤器 |
205/// | `{$var\|default=x}` | `{$name\|default='N/A'}` | 默认值 |
206/// | `{$var?='x'}` | | 三元:真则输出 |
207/// | `{$var?:'x'}` | | 三元:假则输出 x |
208/// | `{$var??'x'}` | | null 合并 |
209/// | `{:func(args)}` | `{:date('Y')}` | 函数调用 |
210/// | `{//comment}` | | 单行注释 |
211/// | `{/*comment*/}` | | 块注释 |
212/// | `{literal}...{/literal}` | | 原文保留 |
213pub struct SimpleTemplateEngine {
214    config: RwLock<ViewConfig>,
215    functions: RwLock<HashMap<String, TemplateFn>>,
216}
217
218impl SimpleTemplateEngine {
219    /// 创建新引擎
220    pub fn new(config: ViewConfig) -> Self {
221        let mut functions = HashMap::new();
222        register_builtin_functions(&mut functions);
223        Self {
224            config: RwLock::new(config),
225            functions: RwLock::new(functions),
226        }
227    }
228
229    /// 注册自定义函数(对齐 PHP `Template::extend` 扩展机制)
230    pub fn register_function(&self, name: &str, func: TemplateFn) {
231        self.functions.write().insert(name.to_string(), func);
232    }
233
234    /// 解析模板路径(对齐 PHP `parseTemplateFile`)
235    ///
236    /// PHP 规则:
237    /// 1. 含 `@` → 跨应用调用 `app@template`
238    /// 2. 首字符 `/` → 绝对路径
239    /// 3. 否则 → `view_path/template.view_suffix`
240    pub fn parse_template_path(&self, template: &str) -> PathBuf {
241        let config = self.config.read();
242        let view_path = &config.view_path;
243        let suffix = &config.view_suffix;
244
245        if template.is_empty() {
246            return view_path.join(format!("index.{}", suffix));
247        }
248
249        // 绝对路径
250        if let Some(stripped) = template.strip_prefix('/') {
251            let mut path = PathBuf::from(stripped);
252            if path.extension().is_none() {
253                path = path.with_extension(suffix);
254            }
255            return path;
256        }
257
258        // 跨应用调用(对齐 PHP `app@template`)
259        if let Some(at_pos) = template.find('@') {
260            let app = &template[..at_pos];
261            let tpl = &template[at_pos + 1..];
262            let mut path = PathBuf::from(app);
263            path.push("view");
264            path.push(tpl);
265            if path.extension().is_none() {
266                path = path.with_extension(suffix);
267            }
268            return path;
269        }
270
271        // 相对路径(默认)
272        let mut path = view_path.join(template);
273        if path.extension().is_none() {
274            path = path.with_extension(suffix);
275        }
276        path
277    }
278
279    /// 渲染内容字符串(核心解析逻辑)
280    ///
281    /// 对齐 PHP `Template::parse()` 的解析顺序:
282    /// 1. parseLiteral(暂存 literal 内容)
283    /// 2. parseTagLib(控制流标签:if/foreach/volist/switch/for)
284    /// 3. parseTag(变量/函数/注释)
285    /// 4. 还原 literal
286    fn render_content(&self, content: &str, data: &ViewData) -> Result<String, ViewError> {
287        // 1. 暂存 {literal}...{/literal} 内容
288        let (content, literals) = self.extract_literals(content);
289
290        // 2. 解析控制流标签(对齐 PHP `TagLib::parseTag`)
291        let config = self.config.read().clone();
292        let content = template::render_control_flow(&content, data, &config, |c, d| {
293            self.render_content(c, d)
294        })?;
295
296        // 3. 解析标签(变量/函数/注释)
297        let content = self.parse_tags(&content, data)?;
298
299        // 4. 还原 literal
300        let content = self.restore_literals(&content, &literals);
301
302        Ok(content)
303    }
304
305    /// 暂存 {literal}...{/literal} 内容(对齐 PHP `parseLiteral`)
306    fn extract_literals(&self, content: &str) -> (String, Vec<String>) {
307        let config = self.config.read();
308        let begin = &config.tpl_begin;
309        let end = &config.tpl_end;
310        let literal_open = format!("{}literal{}", begin, end);
311        let literal_close = format!("{}/literal{}", begin, end);
312
313        let mut result = String::with_capacity(content.len());
314        let mut literals = Vec::new();
315        let mut remaining = content;
316
317        loop {
318            if let Some(open_pos) = remaining.find(&literal_open) {
319                result.push_str(&remaining[..open_pos]);
320                let after_open = &remaining[open_pos + literal_open.len()..];
321                if let Some(close_pos) = after_open.find(&literal_close) {
322                    let literal_content = &after_open[..close_pos];
323                    let placeholder = format!("<!--###LITERAL{}###-->", literals.len());
324                    literals.push(literal_content.to_string());
325                    result.push_str(&placeholder);
326                    remaining = &after_open[close_pos + literal_close.len()..];
327                } else {
328                    // 未闭合的 literal,原样输出
329                    result.push_str(&remaining[open_pos..]);
330                    break;
331                }
332            } else {
333                result.push_str(remaining);
334                break;
335            }
336        }
337
338        (result, literals)
339    }
340
341    /// 还原 literal 内容
342    fn restore_literals(&self, content: &str, literals: &[String]) -> String {
343        let mut result = content.to_string();
344        for (i, literal) in literals.iter().enumerate() {
345            let placeholder = format!("<!--###LITERAL{}###-->", i);
346            result = result.replace(&placeholder, literal);
347        }
348        result
349    }
350
351    /// 解析标签(对齐 PHP `parseTag`)
352    ///
353    /// PHP 按首字符分支:
354    /// - `$` → 变量 `{$var}`
355    /// - `:` → 函数输出 `{:fun()}`
356    /// - `~` → 函数执行 `{~fun()}`
357    /// - `+` / `-` → 表达式
358    /// - `/` → 注释 `{//...}` `{/*...*/}`
359    fn parse_tags(&self, content: &str, data: &ViewData) -> Result<String, ViewError> {
360        let config = self.config.read();
361        let begin = regex::escape(&config.tpl_begin);
362        let end = regex::escape(&config.tpl_end);
363
364        // 匹配 {tag_content}(非贪婪,允许跨行)
365        // begin/end 已经过 regex::escape,直接拼接即可
366        let pattern = format!("{}(.*?){}", begin, end);
367        let re = Regex::new(&pattern).map_err(|e| ViewError::SyntaxError(e.to_string()))?;
368
369        let mut result = String::with_capacity(content.len());
370        let mut last_end = 0;
371
372        for caps in re.captures_iter(content) {
373            let full_match = caps.get(0).expect("正则捕获组 0 必定存在");
374            let tag_content = caps.get(1).expect("正则捕获组 1 必定存在").as_str();
375
376            result.push_str(&content[last_end..full_match.start()]);
377
378            let rendered = self.render_tag(tag_content, data)?;
379            result.push_str(&rendered);
380
381            last_end = full_match.end();
382        }
383        result.push_str(&content[last_end..]);
384
385        Ok(result)
386    }
387
388    /// 渲染单个标签(对齐 PHP `parseTag` 首字符分支)
389    fn render_tag(&self, tag: &str, data: &ViewData) -> Result<String, ViewError> {
390        let tag = tag.trim();
391
392        if tag.is_empty() {
393            return Ok(String::new());
394        }
395
396        // 按首字符分支(对齐 PHP parseTag)
397        let first_char = tag.chars().next().expect("已检查 tag 非空");
398
399        match first_char {
400            '$' => self.render_var_tag(tag, data),
401            ':' => self.render_func_tag(tag, data, false),
402            '~' => self.render_func_tag(tag, data, true),
403            '/' => {
404                // 注释 {//...} 或 {/*...*/}
405                Ok(String::new())
406            }
407            _ => {
408                // 未识别标签,原样输出(对齐 PHP parseTag "其他" 分支)
409                let config = self.config.read();
410                Ok(format!("{}{}{}", config.tpl_begin, tag, config.tpl_end))
411            }
412        }
413    }
414
415    /// 渲染变量标签 `{$var}`(对齐 PHP `parseVar` + `parseVarFunction`)
416    ///
417    /// PHP 语法:
418    /// - `{$var}` → 输出变量值
419    /// - `{$var.attr}` → 嵌套属性(array 模式:`$var['attr']`)
420    /// - `{$var|filter}` → 过滤器
421    /// - `{$var|default=x}` → 默认值
422    /// - `{$var?='x'}` → 真则输出
423    /// - `{$var?:'x'}` → 假则输出 x
424    /// - `{$var??'x'}` → null 合并
425    fn render_var_tag(&self, tag: &str, data: &ViewData) -> Result<String, ViewError> {
426        // 去掉前导 $
427        let expr = &tag[1..];
428
429        // 分离变量名和过滤器/三元表达式
430        // PHP 用 `|` 分隔过滤器,`?` 用于三元
431        let (var_expr, filters, ternary) = self.split_var_expr(expr);
432
433        // 解析变量值
434        let value = self.resolve_var(&var_expr, data);
435
436        // 应用三元表达式(对齐 PHP parseVar 的 ? 处理)
437        let value = if let Some(ternary_expr) = &ternary {
438            self.apply_ternary(&value, ternary_expr)?
439        } else {
440            value
441        };
442
443        // 应用过滤器(对齐 PHP parseVarFunction)
444        let value = self.apply_filters(value, &filters)?;
445
446        // 转为字符串输出(对齐 PHP echo)
447        Ok(value_to_string(&value))
448    }
449
450    /// 分离变量表达式、过滤器、三元表达式
451    ///
452    /// PHP 规则:
453    /// - `|` 分隔过滤器(如 `name|upper|default='N/A'`)
454    /// - `?` 用于三元(如 `var?='x'`、`var?:'x'`、`var??'x'`)
455    fn split_var_expr(&self, expr: &str) -> (String, Vec<String>, Option<String>) {
456        // 检查三元表达式(?? / ?: / ?= / ?)
457        // PHP 中 `??` 优先于 `?:` 和 `?=`
458        if let Some(pos) = expr.find("??") {
459            let var = expr[..pos].trim().to_string();
460            let ternary = expr[pos..].trim().to_string();
461            return (var, Vec::new(), Some(ternary));
462        }
463
464        // 分离 `|` 过滤器
465        // 注意:`?` 可能在过滤器参数中,所以先找 `?`(但不在引号内)
466        // 简化处理:先找 `|` 分割,再在每个部分中找 `?`
467        let parts: Vec<&str> = expr.split('|').collect();
468        let var_expr = parts[0].trim().to_string();
469
470        // 在 var_expr 中检查三元
471        if let Some(pos) = var_expr.find('?') {
472            let var = var_expr[..pos].trim().to_string();
473            let ternary = var_expr[pos..].trim().to_string();
474            let filters: Vec<String> = parts[1..]
475                .iter()
476                .map(|s| s.trim().to_string())
477                .filter(|s| !s.is_empty())
478                .collect();
479            return (var, filters, Some(ternary));
480        }
481
482        let filters: Vec<String> = parts[1..]
483            .iter()
484            .map(|s| s.trim().to_string())
485            .filter(|s| !s.is_empty())
486            .collect();
487
488        (var_expr, filters, None)
489    }
490
491    /// 解析变量值(对齐 PHP `parseVar` 的 `.` 语法)
492    ///
493    /// PHP `tpl_var_identify` 配置:
494    /// - `array`(默认):`$a.b.c` → `$a['b']['c']`
495    /// - `obj`:`$a.b.c` → `$a->b->c`
496    /// - `''`(自动):`(is_array($a)?$a['b']:$a->b)`
497    fn resolve_var(&self, expr: &str, data: &ViewData) -> Value {
498        resolve_var_expr(expr, data)
499    }
500
501    /// 应用三元表达式(对齐 PHP `parseVar` 的 `?` 处理)
502    ///
503    /// PHP 语法:
504    /// - `??'x'` → `isset($var) ? $var : 'x'`(null 合并)
505    /// - `?:'x'` → `!empty($var) ? $var : 'x'`
506    /// - `?='x'` → `if ($var) echo 'x'`
507    /// - `? 'a' : 'b'` → `!empty($var) ? 'a' : 'b'`
508    fn apply_ternary(&self, value: &Value, ternary: &str) -> Result<Value, ViewError> {
509        // `??` null 合并
510        if let Some(default) = ternary.strip_prefix("??") {
511            if value.is_null() {
512                return Ok(parse_literal(default.trim()));
513            }
514            return Ok(value.clone());
515        }
516
517        // `?:` 假则输出
518        if let Some(default) = ternary.strip_prefix("?:") {
519            if is_truthy(value) {
520                return Ok(value.clone());
521            }
522            return Ok(parse_literal(default.trim()));
523        }
524
525        // `?=` 真则输出
526        if let Some(output) = ternary.strip_prefix("?=") {
527            if is_truthy(value) {
528                return Ok(parse_literal(output.trim()));
529            }
530            return Ok(Value::Null);
531        }
532
533        // `? a : b` 标准三元
534        if let Some(rest) = ternary.strip_prefix('?') {
535            if let Some(colon_pos) = rest.find(':') {
536                let true_val = rest[..colon_pos].trim();
537                let false_val = rest[colon_pos + 1..].trim();
538                if is_truthy(value) {
539                    return Ok(parse_literal(true_val));
540                }
541                return Ok(parse_literal(false_val));
542            }
543            // `? 'x'`(无冒号,真则输出)
544            if is_truthy(value) {
545                return Ok(parse_literal(rest.trim()));
546            }
547            return Ok(Value::Null);
548        }
549
550        Ok(value.clone())
551    }
552
553    /// 应用过滤器(对齐 PHP `parseVarFunction`)
554    ///
555    /// PHP 内置过滤器:
556    /// - `htmlentities`(默认,自动追加)
557    /// - `raw`(跳过过滤)
558    /// - `upper` / `lower`
559    /// - `default=x`
560    /// - `date=格式`
561    fn apply_filters(&self, mut value: Value, filters: &[String]) -> Result<Value, ViewError> {
562        let config = self.config.read();
563        let default_filter = &config.default_filter;
564
565        // PHP 默认追加 htmlentities(除非显式 |raw)
566        let has_raw = filters.iter().any(|f| f.starts_with("raw"));
567        if !has_raw && !default_filter.is_empty() && default_filter != "raw" {
568            value = apply_builtin_filter(value, default_filter, None)?;
569        }
570
571        // 应用用户指定的过滤器
572        for filter in filters {
573            if filter.starts_with("raw") {
574                continue;
575            }
576
577            // 分离过滤器名和参数(`filter=arg` 或 `filter(arg)`)
578            let (filter_name, filter_arg) = if let Some(eq_pos) = filter.find('=') {
579                (&filter[..eq_pos], Some(filter[eq_pos + 1..].to_string()))
580            } else if let Some(paren_pos) = filter.find('(') {
581                (
582                    &filter[..paren_pos],
583                    Some(filter[paren_pos + 1..].trim_end_matches(')').to_string()),
584                )
585            } else {
586                (filter.as_str(), None)
587            };
588
589            value = apply_builtin_filter(value, filter_name.trim(), filter_arg)?;
590        }
591
592        Ok(value)
593    }
594
595    /// 渲染函数标签 `{:func()}` 或 `{~func()}`(对齐 PHP `parseTag` 的 `:` 和 `~` 分支)
596    ///
597    /// PHP 语法:
598    /// - `{:func(args)}` → `echo func(args)`
599    /// - `{~func(args)}` → `func(args)`(执行,不输出)
600    fn render_func_tag(
601        &self,
602        tag: &str,
603        _data: &ViewData,
604        suppress_output: bool,
605    ) -> Result<String, ViewError> {
606        // 去掉前导 `:` 或 `~`
607        let expr = &tag[1..];
608
609        // 解析函数名和参数
610        let (func_name, args) = parse_func_call(expr)?;
611
612        // 查找函数
613        let functions = self.functions.read();
614        let func = functions
615            .get(&func_name)
616            .ok_or_else(|| ViewError::RenderError(format!("未注册的模板函数: {}", func_name)))?;
617
618        // 调用函数
619        let result = func(&args)?;
620
621        // `~` 前缀:执行但不输出(对齐 PHP `{~fun()}`)
622        if suppress_output {
623            return Ok(String::new());
624        }
625
626        Ok(value_to_string(&result))
627    }
628}
629
630impl TemplateEngine for SimpleTemplateEngine {
631    fn exists(&self, template: &str) -> bool {
632        let path = self.parse_template_path(template);
633        path.is_file()
634    }
635
636    fn fetch(&self, template: &str, data: &ViewData) -> Result<String, ViewError> {
637        let path = self.parse_template_path(template);
638
639        if !path.is_file() {
640            return Err(ViewError::TemplateNotFound(format!(
641                "{} (解析路径: {})",
642                template,
643                path.display()
644            )));
645        }
646
647        let content = std::fs::read_to_string(&path)?;
648        let config = self.config.read().clone();
649        // PHP `parse()` 顺序:parseExtend → parseLayout
650        // 应用继承(对齐 PHP `Template::parseExtend()`)
651        let content = inheritance::apply_inheritance(&content, &config)?;
652        // 应用布局(对齐 PHP `Template::compiler()` 在 `parse()` 之前应用布局)
653        let content = layout::apply_layout(&content, &config)?;
654        self.render_content(&content, data)
655    }
656
657    fn display(&self, content: &str, data: &ViewData) -> Result<String, ViewError> {
658        let config = self.config.read().clone();
659        // PHP `parse()` 顺序:parseExtend → parseLayout
660        // 应用继承(对齐 PHP `Template::parseExtend()`)
661        let content = inheritance::apply_inheritance(content, &config)?;
662        // 应用布局(对齐 PHP `Template::display()` 也通过 `compiler()` 应用布局)
663        let content = layout::apply_layout(&content, &config)?;
664        self.render_content(&content, data)
665    }
666
667    fn set_config(&mut self, config: ViewConfig) {
668        *self.config.write() = config;
669    }
670
671    fn get_config(&self, name: &str) -> Option<Value> {
672        let config = self.config.read();
673        match name {
674            "view_path" => Some(Value::String(config.view_path.to_string_lossy().into())),
675            "view_suffix" => Some(Value::String(config.view_suffix.clone())),
676            "view_depr" => Some(Value::String(config.view_depr.clone())),
677            "tpl_begin" => Some(Value::String(config.tpl_begin.clone())),
678            "tpl_end" => Some(Value::String(config.tpl_end.clone())),
679            "taglib_begin" => Some(Value::String(config.taglib_begin.clone())),
680            "taglib_end" => Some(Value::String(config.taglib_end.clone())),
681            "default_filter" => Some(Value::String(config.default_filter.clone())),
682            "layout_on" => Some(Value::Bool(config.layout_on)),
683            "layout_name" => Some(Value::String(config.layout_name.clone())),
684            "layout_item" => Some(Value::String(config.layout_item.clone())),
685            "tpl_var_identify" => Some(Value::String(config.tpl_var_identify.clone())),
686            _ => None,
687        }
688    }
689
690    fn as_any(&self) -> &dyn std::any::Any {
691        self
692    }
693}
694
695// ============================================================================
696// 视图入口
697// ============================================================================
698
699/// 视图入口(对齐 PHP `think\View`)
700///
701/// PHP `think\View` 继承 `Manager`(多驱动管理),通过 `__call` 转发到默认驱动。
702/// Rust 实现简化为直接持有引擎实例。
703///
704/// ## PHP 对齐方法
705///
706/// | PHP 方法 | Rust 方法 | 说明 |
707/// |----------|-----------|------|
708/// | `assign($name, $value)` | [`View::assign`] | 赋值模板变量 |
709/// | `filter(callable)` | [`View::set_filter`] | 设置内容过滤器 |
710/// | `fetch($template, $vars)` | [`View::fetch`] | 渲染模板文件 |
711/// | `display($content, $vars)` | [`View::display`] | 渲染字符串内容 |
712/// | `engine($type)` | [`View::engine`] / [`View::set_engine`] | 获取/切换引擎 |
713/// | `exists($template)` | [`View::exists`] | 模板是否存在 |
714/// | `__get($name)` | [`View::get_var`] | 读取变量 |
715/// | `__isset($name)` | [`View::has_var`] | 变量是否存在 |
716pub struct View {
717    /// 模板变量池(对齐 PHP `$data`)
718    data: RwLock<ViewData>,
719
720    /// 内容过滤器(对齐 PHP `$filter`,单值回调)
721    filter: RwLock<Option<ContentFilter>>,
722
723    /// 模板引擎(对齐 PHP `$drivers['default']`)
724    engine: RwLock<Box<dyn TemplateEngine>>,
725}
726
727impl View {
728    /// 创建新视图(对齐 PHP `new View()`)
729    pub fn new(engine: Box<dyn TemplateEngine>) -> Self {
730        Self {
731            data: RwLock::new(HashMap::new()),
732            filter: RwLock::new(None),
733            engine: RwLock::new(engine),
734        }
735    }
736
737    /// 创建使用 SimpleTemplateEngine 的默认视图
738    pub fn with_default_engine() -> Self {
739        Self::new(Box::new(SimpleTemplateEngine::new(ViewConfig::default())))
740    }
741
742    /// 创建使用指定配置的默认视图
743    pub fn with_config(config: ViewConfig) -> Self {
744        Self::new(Box::new(SimpleTemplateEngine::new(config)))
745    }
746
747    /// 赋值模板变量(对齐 PHP `View::assign`)
748    ///
749    /// PHP: `$view->assign('name', 'value')` 或 `$view->assign(['k1' => 'v1'])`
750    pub fn assign(&self, name: &str, value: Value) -> &Self {
751        self.data.write().insert(name.to_string(), value);
752        self
753    }
754
755    /// 批量赋值模板变量
756    pub fn assign_many(&self, vars: ViewData) -> &Self {
757        self.data.write().extend(vars);
758        self
759    }
760
761    /// 设置内容过滤器(对齐 PHP `View::filter`)
762    ///
763    /// PHP: `$view->filter(function($content) { return strtoupper($content); })`
764    pub fn set_filter(&self, filter: ContentFilter) -> &Self {
765        *self.filter.write() = Some(filter);
766        self
767    }
768
769    /// 清除过滤器
770    pub fn clear_filter(&self) -> &Self {
771        *self.filter.write() = None;
772        self
773    }
774
775    /// 渲染模板文件(对齐 PHP `View::fetch`)
776    ///
777    /// PHP: `$content = $view->fetch('index', ['name' => 'value'])`
778    ///
779    /// 合并规则:`$vars` 优先于 `$this->data`(对齐 PHP `array_merge`)
780    pub fn fetch(&self, template: &str, vars: Option<ViewData>) -> Result<String, ViewError> {
781        let mut data = self.data.read().clone();
782        if let Some(vars) = vars {
783            data.extend(vars);
784        }
785
786        let content = self.engine.read().fetch(template, &data)?;
787        self.apply_filter(content)
788    }
789
790    /// 渲染字符串内容(对齐 PHP `View::display`)
791    ///
792    /// PHP: `$content = $view->display('Hello {$name}!', ['name' => 'World'])`
793    pub fn display(&self, content: &str, vars: Option<ViewData>) -> Result<String, ViewError> {
794        let mut data = self.data.read().clone();
795        if let Some(vars) = vars {
796            data.extend(vars);
797        }
798
799        let rendered = self.engine.read().display(content, &data)?;
800        self.apply_filter(rendered)
801    }
802
803    /// 模板是否存在(对齐 PHP `View::exists`)
804    pub fn exists(&self, template: &str) -> bool {
805        self.engine.read().exists(template)
806    }
807
808    /// 获取变量(对齐 PHP `View::__get`)
809    pub fn get_var(&self, name: &str) -> Option<Value> {
810        self.data.read().get(name).cloned()
811    }
812
813    /// 检查变量是否存在(对齐 PHP `View::__isset`)
814    pub fn has_var(&self, name: &str) -> bool {
815        self.data.read().contains_key(name)
816    }
817
818    /// 清除所有变量
819    pub fn clear_vars(&self) -> &Self {
820        self.data.write().clear();
821        self
822    }
823
824    /// 获取引擎(对齐 PHP `View::engine`)
825    pub fn engine(&self) -> parking_lot::RwLockReadGuard<'_, Box<dyn TemplateEngine>> {
826        self.engine.read()
827    }
828
829    /// 替换引擎
830    pub fn set_engine(&self, engine: Box<dyn TemplateEngine>) -> &Self {
831        *self.engine.write() = engine;
832        self
833    }
834
835    /// 应用过滤器(对齐 PHP `getContent` 中的 filter 调用)
836    fn apply_filter(&self, content: String) -> Result<String, ViewError> {
837        if let Some(filter) = self.filter.read().as_ref() {
838            Ok(filter(&content))
839        } else {
840            Ok(content)
841        }
842    }
843}
844
845// ============================================================================
846// 辅助函数
847// ============================================================================
848
849/// 解析变量表达式(对齐 PHP `parseVar` 的 `.` 语法)
850///
851/// 从 `resolve_var` 方法提取为自由函数,供 `template` 子模块复用。
852pub(super) fn resolve_var_expr(expr: &str, data: &ViewData) -> Value {
853    let parts: Vec<&str> = expr.split('.').collect();
854    let mut current = data.get(parts[0]).cloned().unwrap_or(Value::Null);
855
856    for part in &parts[1..] {
857        current = match &current {
858            Value::Object(map) => map.get(*part).cloned().unwrap_or(Value::Null),
859            Value::Array(arr) => {
860                // 数组按整数索引或字符串 key 查找
861                if let Ok(idx) = part.parse::<usize>() {
862                    arr.get(idx).cloned().unwrap_or(Value::Null)
863                } else {
864                    Value::Null
865                }
866            }
867            _ => Value::Null,
868        };
869    }
870
871    current
872}
873
874/// HTML 转义(对齐 PHP `htmlentities`)
875fn htmlentities(s: &str) -> String {
876    let mut result = String::with_capacity(s.len());
877    for c in s.chars() {
878        match c {
879            '&' => result.push_str("&amp;"),
880            '<' => result.push_str("&lt;"),
881            '>' => result.push_str("&gt;"),
882            '"' => result.push_str("&quot;"),
883            '\'' => result.push_str("&#039;"),
884            _ => result.push(c),
885        }
886    }
887    result
888}
889
890/// 判断值是否为真(对齐 PHP 真值判断)
891///
892/// PHP 真值规则:
893/// - `false`、`0`、`0.0`、`""`、`"0"`、`[]`、`null` → false
894/// - 其他 → true
895pub(super) fn is_truthy(value: &Value) -> bool {
896    match value {
897        Value::Null => false,
898        Value::Bool(b) => *b,
899        Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
900        Value::String(s) => !s.is_empty() && s != "0",
901        Value::Array(a) => !a.is_empty(),
902        Value::Object(o) => !o.is_empty(),
903    }
904}
905
906/// Value 转字符串(对齐 PHP `echo`)
907pub(super) fn value_to_string(value: &Value) -> String {
908    match value {
909        Value::Null => String::new(),
910        Value::Bool(b) => if *b { "1" } else { "" }.to_string(),
911        Value::Number(n) => {
912            if let Some(i) = n.as_i64() {
913                i.to_string()
914            } else if let Some(f) = n.as_f64() {
915                if f == f.trunc() {
916                    format!("{}", f as i64)
917                } else {
918                    format!("{}", f)
919                }
920            } else {
921                n.to_string()
922            }
923        }
924        Value::String(s) => s.clone(),
925        Value::Array(a) => serde_json::to_string(a).unwrap_or_default(),
926        Value::Object(o) => serde_json::to_string(o).unwrap_or_default(),
927    }
928}
929
930/// 解析字面量(对齐 PHP 模板中的字符串/数字字面量)
931pub(super) fn parse_literal(s: &str) -> Value {
932    let s = s.trim();
933
934    // 去除引号
935    if (s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2)
936        || (s.starts_with('"') && s.ends_with('"') && s.len() >= 2)
937    {
938        return Value::String(s[1..s.len() - 1].to_string());
939    }
940
941    // 数字
942    if let Ok(i) = s.parse::<i64>() {
943        return Value::Number(i.into());
944    }
945    if let Ok(f) = s.parse::<f64>() {
946        if let Some(n) = serde_json::Number::from_f64(f) {
947            return Value::Number(n);
948        }
949    }
950
951    // 布尔
952    match s {
953        "true" => return Value::Bool(true),
954        "false" => return Value::Bool(false),
955        "null" => return Value::Null,
956        _ => {}
957    }
958
959    // 默认作为字符串
960    Value::String(s.to_string())
961}
962
963/// 解析函数调用(对齐 PHP `{:func(args)}`)
964///
965/// 返回 (函数名, 参数列表)
966fn parse_func_call(expr: &str) -> Result<(String, Vec<Value>), ViewError> {
967    let expr = expr.trim();
968
969    if let Some(paren_pos) = expr.find('(') {
970        let func_name = expr[..paren_pos].trim().to_string();
971        let args_str = expr[paren_pos + 1..].trim_end_matches(')');
972
973        let mut args = Vec::new();
974        if !args_str.trim().is_empty() {
975            for arg in split_args(args_str) {
976                args.push(parse_literal(arg.trim()));
977            }
978        }
979
980        Ok((func_name, args))
981    } else {
982        // 无参数调用 `{:func}`
983        Ok((expr.to_string(), Vec::new()))
984    }
985}
986
987/// 分割函数参数(处理引号内的逗号)
988fn split_args(s: &str) -> Vec<String> {
989    let mut args = Vec::new();
990    let mut current = String::new();
991    let mut in_single_quote = false;
992    let mut in_double_quote = false;
993
994    for c in s.chars() {
995        match c {
996            '\'' if !in_double_quote => {
997                in_single_quote = !in_single_quote;
998                current.push(c);
999            }
1000            '"' if !in_single_quote => {
1001                in_double_quote = !in_double_quote;
1002                current.push(c);
1003            }
1004            ',' if !in_single_quote && !in_double_quote => {
1005                args.push(current.trim().to_string());
1006                current.clear();
1007            }
1008            _ => current.push(c),
1009        }
1010    }
1011
1012    if !current.trim().is_empty() {
1013        args.push(current.trim().to_string());
1014    }
1015
1016    args
1017}
1018
1019/// 应用内置过滤器(对齐 PHP `parseVarFunction` 内置函数)
1020fn apply_builtin_filter(
1021    value: Value,
1022    filter_name: &str,
1023    arg: Option<String>,
1024) -> Result<Value, ViewError> {
1025    match filter_name {
1026        "raw" => Ok(value),
1027        "htmlentities" | "htmlspecialchars" => {
1028            Ok(Value::String(htmlentities(&value_to_string(&value))))
1029        }
1030        "upper" | "strtoupper" => Ok(Value::String(value_to_string(&value).to_uppercase())),
1031        "lower" | "strtolower" => Ok(Value::String(value_to_string(&value).to_lowercase())),
1032        "default" => {
1033            if is_truthy(&value) {
1034                Ok(value)
1035            } else {
1036                let default_val = arg.unwrap_or_default();
1037                Ok(parse_literal(&default_val))
1038            }
1039        }
1040        "first" => {
1041            if let Value::Array(arr) = &value {
1042                Ok(arr.first().cloned().unwrap_or(Value::Null))
1043            } else {
1044                Ok(Value::Null)
1045            }
1046        }
1047        "last" => {
1048            if let Value::Array(arr) = &value {
1049                Ok(arr.last().cloned().unwrap_or(Value::Null))
1050            } else {
1051                Ok(Value::Null)
1052            }
1053        }
1054        _ => Err(ViewError::RenderError(format!(
1055            "未知的模板过滤器: {}",
1056            filter_name
1057        ))),
1058    }
1059}
1060
1061/// 注册内置函数(对齐 PHP `Template` 注册的 `$Think` 扩展)
1062fn register_builtin_functions(functions: &mut HashMap<String, TemplateFn>) {
1063    // date 函数(对齐 PHP `date('Y-m-d')`)
1064    functions.insert(
1065        "date".to_string(),
1066        Arc::new(|args: &[Value]| -> Result<Value, ViewError> {
1067            let format = args
1068                .first()
1069                .and_then(|v| v.as_str())
1070                .unwrap_or("Y-m-d H:i:s");
1071            let now = chrono::Local::now();
1072            let php_format = php_date_to_chrono(format);
1073            Ok(Value::String(now.format(&php_format).to_string()))
1074        }),
1075    );
1076
1077    // strtoupper 函数
1078    functions.insert(
1079        "strtoupper".to_string(),
1080        Arc::new(|args: &[Value]| -> Result<Value, ViewError> {
1081            let s = args.first().map(value_to_string).unwrap_or_default();
1082            Ok(Value::String(s.to_uppercase()))
1083        }),
1084    );
1085
1086    // strtolower 函数
1087    functions.insert(
1088        "strtolower".to_string(),
1089        Arc::new(|args: &[Value]| -> Result<Value, ViewError> {
1090            let s = args.first().map(value_to_string).unwrap_or_default();
1091            Ok(Value::String(s.to_lowercase()))
1092        }),
1093    );
1094}
1095
1096/// PHP date 格式转 chrono 格式
1097fn php_date_to_chrono(php_format: &str) -> String {
1098    let mut result = String::with_capacity(php_format.len() * 2);
1099    let chars = php_format.chars();
1100    for c in chars {
1101        match c {
1102            'Y' => result.push_str("%Y"),
1103            'y' => result.push_str("%y"),
1104            'm' => result.push_str("%m"),
1105            'n' => result.push_str("%-m"),
1106            'd' => result.push_str("%d"),
1107            'j' => result.push_str("%-d"),
1108            'H' => result.push_str("%H"),
1109            'G' => result.push_str("%-H"),
1110            'i' => result.push_str("%M"),
1111            's' => result.push_str("%S"),
1112            'D' => result.push_str("%a"),
1113            'l' => result.push_str("%A"),
1114            'M' => result.push_str("%b"),
1115            'F' => result.push_str("%B"),
1116            'a' => result.push_str("%p"),
1117            'A' => result.push_str("%p"),
1118            'U' => result.push_str("%s"),
1119            _ => {
1120                result.push(c);
1121            }
1122        }
1123    }
1124    result
1125}
1126
1127// ============================================================================
1128// 模板渲染兜底场景(对齐 DefaultResponseType::Html)
1129//
1130// 项目主策略为前后端分离(JSON 默认返回),但部分场景需要渲染 HTML 模板:
1131// - PDF 导出:渲染 HTML 模板作为 PDF 输入(对齐 PHP pdf-pdftk 表单填充场景)
1132// - Excel 导出:渲染 HTML 表格作为 Excel 输入(对齐 PHP PhpSpreadsheet 场景)
1133// - 邮件内容:渲染 HTML 模板作为邮件正文
1134// - 报表页面:渲染 HTML 报表
1135//
1136// 本模块提供 View 渲染到 axum Response 的桥接方法,复用
1137// `respond_html` 函数,确保 Content-Type 统一为 text/html; charset=utf-8。
1138//
1139// ## PHP 源码参考
1140//
1141// PHP `Dispatch::autoResponse()` 第 96 行(vendor/topthink/framework/src/think/route/Dispatch.php):
1142// ```php
1143// $type     = $this->request->isJson() ? 'json' : 'html';
1144// $response = Response::create($data, $type);
1145// ```
1146// 当 `$type = 'html'` 时,PHP 创建 HTML Response。
1147// 本模块对应 Rust 的 HTML Response 创建路径。
1148//
1149// ## PHP 项目实际使用情况
1150//
1151// 鲜视达 PHP 项目(e:\vue\test\鲜视达\server)实际不使用 ThinkPHP 模板渲染做 PDF/Excel 导出:
1152// - PDF 导出:使用 mikehaertl/php-pdftk 填充 PDF 表单(addons/finance/model/Payment.php:1357-1514)
1153//   + HTTP 调用 Java 服务(app/job/controller/Pdf.php,http_java_post 到 127.0.0.1:8086)
1154// - Excel 导出:使用 PhpSpreadsheet 直接操作 Spreadsheet 对象(app/common/service/order/ExportService.php)
1155//   + fputcsv CSV 流式输出(app/common.php:924-956)
1156// 但框架层保留了 HTML 兜底分支(Dispatch::autoResponse 第 96 行 isJson() ? 'json' : 'html'),
1157// 本模块对齐此设计,为 Rust 实现提供模板渲染兜底能力。
1158// ============================================================================
1159
1160use axum::response::Response;
1161
1162/// 模板渲染兜底场景 helper(对齐 `DefaultResponseType::Html`)
1163///
1164/// 项目主策略为前后端分离(JSON 默认返回),但 PDF/Excel 导出、邮件内容、
1165/// 报表页面等场景需要渲染 HTML 模板。本结构体封装了 View 渲染到 HTML Response
1166/// 的桥接逻辑,提供便捷的链式调用。
1167///
1168/// # 用法
1169///
1170/// ```ignore
1171/// use sz_rust_core::view::{ViewFallback, ViewConfig};
1172/// use serde_json::json;
1173///
1174/// let fallback = ViewFallback::with_default_engine();
1175/// fallback.assign("name", json!("World"));
1176///
1177/// // 渲染字符串内容为 HTML Response
1178/// let response = fallback.render_display("Hello {$name}!", None).unwrap();
1179///
1180/// // 渲染为字符串(用于 PDF/Excel 生成器输入)
1181/// let html = fallback.display_to_string("Hello {$name}!", None).unwrap();
1182/// ```
1183pub struct ViewFallback {
1184    /// 内部 View 实例
1185    view: View,
1186}
1187
1188impl ViewFallback {
1189    /// 创建新的模板渲染兜底 helper
1190    pub fn new(view: View) -> Self {
1191        Self { view }
1192    }
1193
1194    /// 从默认配置创建模板渲染兜底 helper
1195    pub fn with_default_engine() -> Self {
1196        Self::new(View::with_default_engine())
1197    }
1198
1199    /// 从指定配置创建模板渲染兜底 helper
1200    pub fn with_config(config: ViewConfig) -> Self {
1201        Self::new(View::with_config(config))
1202    }
1203
1204    /// 渲染模板文件为 HTML Response(兜底场景)
1205    ///
1206    /// 对齐 PHP `$this->fetch('template')` + `Response::create($content, 'html')`。
1207    /// 项目主策略为 JSON,但 PDF/Excel 导出等场景需要 HTML 渲染。
1208    ///
1209    /// # 参数
1210    ///
1211    /// - `template`:模板名(对齐 PHP `fetch($template)`)
1212    /// - `vars`:模板变量(可选,对齐 PHP `fetch($template, $vars)`)
1213    ///
1214    /// # 返回
1215    ///
1216    /// `Ok(Response)`:HTTP 200,Content-Type: text/html; charset=utf-8
1217    /// `Err(ViewError)`:模板未找到 / 渲染失败
1218    pub fn render_template(
1219        &self,
1220        template: &str,
1221        vars: Option<ViewData>,
1222    ) -> Result<Response, ViewError> {
1223        let content = self.view.fetch(template, vars)?;
1224        Ok(sz_rust_http_facade::response::respond_html(content))
1225    }
1226
1227    /// 渲染字符串内容为 HTML Response(兜底场景)
1228    ///
1229    /// 对齐 PHP `$this->display($content)` + `Response::create($content, 'html')`。
1230    ///
1231    /// # 参数
1232    ///
1233    /// - `content`:模板字符串内容
1234    /// - `vars`:模板变量(可选)
1235    ///
1236    /// # 返回
1237    ///
1238    /// `Ok(Response)`:HTTP 200,Content-Type: text/html; charset=utf-8
1239    /// `Err(ViewError)`:渲染失败
1240    pub fn render_display(
1241        &self,
1242        content: &str,
1243        vars: Option<ViewData>,
1244    ) -> Result<Response, ViewError> {
1245        let rendered = self.view.display(content, vars)?;
1246        Ok(sz_rust_http_facade::response::respond_html(rendered))
1247    }
1248
1249    /// 渲染模板文件为字符串(用于 PDF/Excel 生成器输入)
1250    ///
1251    /// PDF/Excel 生成器通常需要 HTML 字符串作为输入,而非 HTTP Response。
1252    /// 本方法直接返回渲染后的 HTML 字符串。
1253    ///
1254    /// # 参数
1255    ///
1256    /// - `template`:模板名
1257    /// - `vars`:模板变量(可选)
1258    ///
1259    /// # 返回
1260    ///
1261    /// `Ok(String)`:渲染后的 HTML 字符串
1262    /// `Err(ViewError)`:模板未找到 / 渲染失败
1263    pub fn render_to_string(
1264        &self,
1265        template: &str,
1266        vars: Option<ViewData>,
1267    ) -> Result<String, ViewError> {
1268        self.view.fetch(template, vars)
1269    }
1270
1271    /// 渲染字符串内容为字符串(用于 PDF/Excel 生成器输入)
1272    ///
1273    /// # 参数
1274    ///
1275    /// - `content`:模板字符串内容
1276    /// - `vars`:模板变量(可选)
1277    ///
1278    /// # 返回
1279    ///
1280    /// `Ok(String)`:渲染后的 HTML 字符串
1281    /// `Err(ViewError)`:渲染失败
1282    pub fn display_to_string(
1283        &self,
1284        content: &str,
1285        vars: Option<ViewData>,
1286    ) -> Result<String, ViewError> {
1287        self.view.display(content, vars)
1288    }
1289
1290    /// 获取内部 View 引用(用于直接操作 View)
1291    pub fn view(&self) -> &View {
1292        &self.view
1293    }
1294
1295    /// 赋值模板变量(对齐 PHP `View::assign`)
1296    pub fn assign(&self, name: &str, value: Value) -> &Self {
1297        self.view.assign(name, value);
1298        self
1299    }
1300
1301    /// 批量赋值模板变量
1302    pub fn assign_many(&self, vars: ViewData) -> &Self {
1303        self.view.assign_many(vars);
1304        self
1305    }
1306
1307    /// 清除所有变量
1308    pub fn clear_vars(&self) -> &Self {
1309        self.view.clear_vars();
1310        self
1311    }
1312}
1313
1314/// 渲染模板文件为 HTML Response(兜底场景,自由函数版本)
1315///
1316/// 对齐 PHP `$this->fetch('template')` + `Response::create($content, 'html')`。
1317/// 便捷函数,无需创建 `ViewFallback` 实例。
1318///
1319/// # 参数
1320///
1321/// - `view`:视图实例
1322/// - `template`:模板名
1323/// - `vars`:模板变量(可选)
1324///
1325/// # 返回
1326///
1327/// `Ok(Response)`:HTTP 200,Content-Type: text/html; charset=utf-8
1328/// `Err(ViewError)`:模板未找到 / 渲染失败
1329pub fn render_template_response(
1330    view: &View,
1331    template: &str,
1332    vars: Option<ViewData>,
1333) -> Result<Response, ViewError> {
1334    let content = view.fetch(template, vars)?;
1335    Ok(sz_rust_http_facade::response::respond_html(content))
1336}
1337
1338/// 渲染字符串内容为 HTML Response(兜底场景,自由函数版本)
1339///
1340/// 对齐 PHP `$this->display($content)` + `Response::create($content, 'html')`。
1341///
1342/// # 参数
1343///
1344/// - `view`:视图实例
1345/// - `content`:模板字符串内容
1346/// - `vars`:模板变量(可选)
1347///
1348/// # 返回
1349///
1350/// `Ok(Response)`:HTTP 200,Content-Type: text/html; charset=utf-8
1351/// `Err(ViewError)`:渲染失败
1352pub fn render_display_response(
1353    view: &View,
1354    content: &str,
1355    vars: Option<ViewData>,
1356) -> Result<Response, ViewError> {
1357    let rendered = view.display(content, vars)?;
1358    Ok(sz_rust_http_facade::response::respond_html(rendered))
1359}
1360
1361// ============================================================================
1362// 测试
1363// ============================================================================
1364
1365#[cfg(test)]
1366mod tests {
1367    use super::*;
1368    use serde_json::json;
1369    use std::path::Path;
1370
1371    // =========================================================================
1372    // 辅助函数
1373    // =========================================================================
1374
1375    /// 创建测试用 View(使用临时目录作为 view_path)
1376    fn make_view() -> View {
1377        View::with_default_engine()
1378    }
1379
1380    /// 创建测试用 View(使用指定 view_path)
1381    fn make_view_with_path(path: &Path) -> View {
1382        let config = ViewConfig {
1383            view_path: path.to_path_buf(),
1384            ..Default::default()
1385        };
1386        View::with_config(config)
1387    }
1388
1389    /// 创建临时目录
1390    fn make_temp_dir() -> PathBuf {
1391        let dir = std::env::temp_dir().join(format!(
1392            "sz_rust_view_test_{}",
1393            std::time::SystemTime::now()
1394                .duration_since(std::time::UNIX_EPOCH)
1395                .unwrap_or_default()
1396                .as_nanos()
1397        ));
1398        std::fs::create_dir_all(&dir).unwrap();
1399        dir
1400    }
1401
1402    /// 写入临时模板文件
1403    fn write_template(dir: &Path, name: &str, content: &str) {
1404        let path = dir.join(format!("{}.html", name));
1405        std::fs::write(&path, content).unwrap();
1406    }
1407
1408    /// 清理临时目录
1409    fn cleanup_dir(dir: &Path) {
1410        let _ = std::fs::remove_dir_all(dir);
1411    }
1412
1413    // =========================================================================
1414    // 组 1:View::assign 基本赋值(对齐 PHP testAssignData)
1415    // =========================================================================
1416
1417    #[test]
1418    fn test_assign_single_var() {
1419        // 对齐 PHP: $view->assign('foo', 'bar')
1420        let view = make_view();
1421        view.assign("foo", json!("bar"));
1422        assert_eq!(view.get_var("foo"), Some(json!("bar")));
1423    }
1424
1425    #[test]
1426    fn test_assign_multiple_vars() {
1427        let view = make_view();
1428        view.assign("foo", json!("bar"))
1429            .assign("baz", json!("boom"));
1430        assert_eq!(view.get_var("foo"), Some(json!("bar")));
1431        assert_eq!(view.get_var("baz"), Some(json!("boom")));
1432    }
1433
1434    #[test]
1435    fn test_assign_overwrite() {
1436        let view = make_view();
1437        view.assign("foo", json!("bar"));
1438        view.assign("foo", json!("new"));
1439        assert_eq!(view.get_var("foo"), Some(json!("new")));
1440    }
1441
1442    #[test]
1443    fn test_has_var() {
1444        let view = make_view();
1445        assert!(!view.has_var("foo"));
1446        view.assign("foo", json!("bar"));
1447        assert!(view.has_var("foo"));
1448    }
1449
1450    #[test]
1451    fn test_clear_vars() {
1452        let view = make_view();
1453        view.assign("foo", json!("bar"));
1454        view.clear_vars();
1455        assert!(!view.has_var("foo"));
1456    }
1457
1458    #[test]
1459    fn test_assign_many() {
1460        let view = make_view();
1461        let mut vars = ViewData::new();
1462        vars.insert("a".to_string(), json!(1));
1463        vars.insert("b".to_string(), json!(2));
1464        view.assign_many(vars);
1465        assert_eq!(view.get_var("a"), Some(json!(1)));
1466        assert_eq!(view.get_var("b"), Some(json!(2)));
1467    }
1468
1469    // =========================================================================
1470    // 组 2:View::display 基本渲染(对齐 PHP testRender)
1471    // =========================================================================
1472
1473    #[test]
1474    fn test_display_string_var() {
1475        // 对齐 PHP: $view->display('Hello {$name}!', ['name' => 'World'])
1476        let view = make_view();
1477        let result = view
1478            .display(
1479                "Hello {$name}!",
1480                Some(ViewData::from([("name".to_string(), json!("World"))])),
1481            )
1482            .unwrap();
1483        assert_eq!(result, "Hello World!");
1484    }
1485
1486    #[test]
1487    fn test_display_with_assign() {
1488        // 对齐 PHP: $view->assign('name', 'World'); $view->display('Hello {$name}!')
1489        let view = make_view();
1490        view.assign("name", json!("World"));
1491        let result = view.display("Hello {$name}!", None).unwrap();
1492        assert_eq!(result, "Hello World!");
1493    }
1494
1495    #[test]
1496    fn test_display_vars_override_assign() {
1497        // 对齐 PHP: vars 优先于 $this->data(array_merge 后者覆盖)
1498        let view = make_view();
1499        view.assign("name", json!("Default"));
1500        let result = view
1501            .display(
1502                "Hello {$name}!",
1503                Some(ViewData::from([("name".to_string(), json!("Override"))])),
1504            )
1505            .unwrap();
1506        assert_eq!(result, "Hello Override!");
1507    }
1508
1509    #[test]
1510    fn test_display_no_vars() {
1511        let view = make_view();
1512        let result = view.display("Hello World!", None).unwrap();
1513        assert_eq!(result, "Hello World!");
1514    }
1515
1516    #[test]
1517    fn test_display_missing_var() {
1518        // 未定义变量输出空字符串(对齐 PHP echo null)
1519        let view = make_view();
1520        let result = view.display("Hello {$name}!", None).unwrap();
1521        assert_eq!(result, "Hello !");
1522    }
1523
1524    #[test]
1525    fn test_display_multiple_vars() {
1526        let view = make_view();
1527        let result = view
1528            .display(
1529                "{$greeting}, {$name}!",
1530                Some(ViewData::from([
1531                    ("greeting".to_string(), json!("Hello")),
1532                    ("name".to_string(), json!("World")),
1533                ])),
1534            )
1535            .unwrap();
1536        assert_eq!(result, "Hello, World!");
1537    }
1538
1539    // =========================================================================
1540    // 组 3:变量嵌套属性(对齐 PHP `.` 语法)
1541    // =========================================================================
1542
1543    #[test]
1544    fn test_display_nested_object() {
1545        // 对齐 PHP: {$user.name} → $user['name'](array 模式)
1546        let view = make_view();
1547        let result = view
1548            .display(
1549                "Name: {$user.name}",
1550                Some(ViewData::from([(
1551                    "user".to_string(),
1552                    json!({"name": "Alice", "age": 30}),
1553                )])),
1554            )
1555            .unwrap();
1556        assert_eq!(result, "Name: Alice");
1557    }
1558
1559    #[test]
1560    fn test_display_deep_nested() {
1561        let view = make_view();
1562        let result = view
1563            .display(
1564                "{$a.b.c}",
1565                Some(ViewData::from([(
1566                    "a".to_string(),
1567                    json!({"b": {"c": "deep"}}),
1568                )])),
1569            )
1570            .unwrap();
1571        assert_eq!(result, "deep");
1572    }
1573
1574    #[test]
1575    fn test_display_array_index() {
1576        // 对齐 PHP: {$arr.0} → $arr[0]
1577        let view = make_view();
1578        let result = view
1579            .display(
1580                "{$arr.0}",
1581                Some(ViewData::from([(
1582                    "arr".to_string(),
1583                    json!(["first", "second"]),
1584                )])),
1585            )
1586            .unwrap();
1587        assert_eq!(result, "first");
1588    }
1589
1590    #[test]
1591    fn test_display_nested_missing() {
1592        let view = make_view();
1593        let result = view
1594            .display(
1595                "{$user.name}",
1596                Some(ViewData::from([("user".to_string(), json!({}))])),
1597            )
1598            .unwrap();
1599        assert_eq!(result, "");
1600    }
1601
1602    // =========================================================================
1603    // 组 4:过滤器(对齐 PHP parseVarFunction)
1604    // =========================================================================
1605
1606    #[test]
1607    fn test_filter_upper() {
1608        // 对齐 PHP: {$name|upper}
1609        let view = make_view();
1610        let result = view
1611            .display(
1612                "{$name|upper}",
1613                Some(ViewData::from([("name".to_string(), json!("hello"))])),
1614            )
1615            .unwrap();
1616        assert_eq!(result, "HELLO");
1617    }
1618
1619    #[test]
1620    fn test_filter_lower() {
1621        let view = make_view();
1622        let result = view
1623            .display(
1624                "{$name|lower}",
1625                Some(ViewData::from([("name".to_string(), json!("HELLO"))])),
1626            )
1627            .unwrap();
1628        assert_eq!(result, "hello");
1629    }
1630
1631    #[test]
1632    fn test_filter_default_with_value() {
1633        // 对齐 PHP: {$name|default='N/A'} — 有值时返回原值
1634        let view = make_view();
1635        let result = view
1636            .display(
1637                "{$name|default='N/A'}",
1638                Some(ViewData::from([("name".to_string(), json!("Alice"))])),
1639            )
1640            .unwrap();
1641        assert_eq!(result, "Alice");
1642    }
1643
1644    #[test]
1645    fn test_filter_default_without_value() {
1646        // 对齐 PHP: {$name|default='N/A'} — 无值时返回默认值
1647        let view = make_view();
1648        let result = view.display("{$name|default='N/A'}", None).unwrap();
1649        assert_eq!(result, "N/A");
1650    }
1651
1652    #[test]
1653    fn test_filter_raw() {
1654        // 对齐 PHP: {$name|raw} — 跳过默认 htmlentities
1655        let view = make_view();
1656        let result = view
1657            .display(
1658                "{$name|raw}",
1659                Some(ViewData::from([("name".to_string(), json!("<b>bold</b>"))])),
1660            )
1661            .unwrap();
1662        assert_eq!(result, "<b>bold</b>");
1663    }
1664
1665    #[test]
1666    fn test_filter_default_htmlentities() {
1667        // 对齐 PHP: 默认追加 htmlentities(default_filter='htmlentities')
1668        let view = make_view();
1669        let result = view
1670            .display(
1671                "{$name}",
1672                Some(ViewData::from([("name".to_string(), json!("<b>bold</b>"))])),
1673            )
1674            .unwrap();
1675        assert_eq!(result, "&lt;b&gt;bold&lt;/b&gt;");
1676    }
1677
1678    #[test]
1679    fn test_filter_chained() {
1680        // 对齐 PHP: {$name|upper|lower} — 链式过滤器
1681        let view = make_view();
1682        let result = view
1683            .display(
1684                "{$name|upper|lower}",
1685                Some(ViewData::from([("name".to_string(), json!("Hello"))])),
1686            )
1687            .unwrap();
1688        assert_eq!(result, "hello");
1689    }
1690
1691    // =========================================================================
1692    // 组 5:三元表达式(对齐 PHP parseVar `?` 处理)
1693    // =========================================================================
1694
1695    #[test]
1696    fn test_ternary_null_coalescing() {
1697        // 对齐 PHP: {$name??'default'} — null 合并
1698        let view = make_view();
1699        let result = view.display("{$name??'default'}", None).unwrap();
1700        assert_eq!(result, "default");
1701    }
1702
1703    #[test]
1704    fn test_ternary_null_coalescing_with_value() {
1705        let view = make_view();
1706        let result = view
1707            .display(
1708                "{$name??'default'}",
1709                Some(ViewData::from([("name".to_string(), json!("Alice"))])),
1710            )
1711            .unwrap();
1712        assert_eq!(result, "Alice");
1713    }
1714
1715    #[test]
1716    fn test_ternary_falsy_default() {
1717        // 对齐 PHP: {$name?:'default'} — 假则输出 default
1718        let view = make_view();
1719        let result = view.display("{$name?:'default'}", None).unwrap();
1720        assert_eq!(result, "default");
1721    }
1722
1723    #[test]
1724    fn test_ternary_truthy_output() {
1725        // 对齐 PHP: {$name?='yes'} — 真则输出 yes
1726        let view = make_view();
1727        let result = view
1728            .display(
1729                "{$name?='yes'}",
1730                Some(ViewData::from([("name".to_string(), json!("Alice"))])),
1731            )
1732            .unwrap();
1733        assert_eq!(result, "yes");
1734    }
1735
1736    // =========================================================================
1737    // 组 6:函数调用(对齐 PHP {:func()} 和 {~func()})
1738    // =========================================================================
1739
1740    #[test]
1741    fn test_func_date() {
1742        // 对齐 PHP: {:date('Y')} — 函数调用
1743        let view = make_view();
1744        let result = view.display("{:date('Y')}", None).unwrap();
1745        let year: u32 = result.parse().unwrap();
1746        assert!((2000..=2100).contains(&year));
1747    }
1748
1749    #[test]
1750    fn test_func_strtoupper() {
1751        let view = make_view();
1752        let result = view.display("{:strtoupper('hello')}", None).unwrap();
1753        assert_eq!(result, "HELLO");
1754    }
1755
1756    #[test]
1757    fn test_func_no_args() {
1758        // 无参数函数调用
1759        let view = make_view();
1760        let result = view.display("{:date()}", None).unwrap();
1761        // date() 默认返回当前日期时间
1762        assert!(!result.is_empty());
1763    }
1764
1765    #[test]
1766    fn test_func_suppress_output() {
1767        // 对齐 PHP: {~func()} — 执行但不输出
1768        let view = make_view();
1769        let result = view.display("{~strtoupper('hello')}", None).unwrap();
1770        assert_eq!(result, "");
1771    }
1772
1773    #[test]
1774    fn test_func_unknown() {
1775        let view = make_view();
1776        let result = view.display("{:unknown_func()}", None);
1777        assert!(result.is_err());
1778    }
1779
1780    // =========================================================================
1781    // 组 7:注释(对齐 PHP parseTag `/` 分支)
1782    // =========================================================================
1783
1784    #[test]
1785    fn test_single_line_comment() {
1786        // 对齐 PHP: {//这是注释}
1787        let view = make_view();
1788        let result = view.display("Hello{//这是注释}World", None).unwrap();
1789        assert_eq!(result, "HelloWorld");
1790    }
1791
1792    #[test]
1793    fn test_block_comment() {
1794        // 对齐 PHP: {/*块注释*/}
1795        let view = make_view();
1796        let result = view.display("Hello{/*块注释*/}World", None).unwrap();
1797        assert_eq!(result, "HelloWorld");
1798    }
1799
1800    // =========================================================================
1801    // 组 8:literal 原文保留(对齐 PHP parseLiteral)
1802    // =========================================================================
1803
1804    #[test]
1805    fn test_literal_preserves_tags() {
1806        // 对齐 PHP: {literal}{$var}{/literal} — literal 内不解析
1807        let view = make_view();
1808        let result = view
1809            .display(
1810                "{literal}{$name}{/literal}",
1811                Some(ViewData::from([("name".to_string(), json!("World"))])),
1812            )
1813            .unwrap();
1814        assert_eq!(result, "{$name}");
1815    }
1816
1817    #[test]
1818    fn test_literal_mixed() {
1819        let view = make_view();
1820        let result = view
1821            .display(
1822                "Hello {$name}! {literal}{$raw}{/literal} Bye",
1823                Some(ViewData::from([("name".to_string(), json!("World"))])),
1824            )
1825            .unwrap();
1826        assert_eq!(result, "Hello World! {$raw} Bye");
1827    }
1828
1829    #[test]
1830    fn test_literal_multiple() {
1831        let view = make_view();
1832        let result = view
1833            .display(
1834                "{literal}A{/literal} {$name} {literal}B{/literal}",
1835                Some(ViewData::from([("name".to_string(), json!("X"))])),
1836            )
1837            .unwrap();
1838        assert_eq!(result, "A X B");
1839    }
1840
1841    // =========================================================================
1842    // 组 9:View::fetch 文件渲染(对齐 PHP fetch)
1843    // =========================================================================
1844
1845    #[test]
1846    fn test_fetch_template_file() {
1847        let dir = make_temp_dir();
1848        write_template(&dir, "index", "<h1>{$title}</h1>");
1849        let view = make_view_with_path(&dir);
1850        let result = view
1851            .fetch(
1852                "index",
1853                Some(ViewData::from([("title".to_string(), json!("Hello"))])),
1854            )
1855            .unwrap();
1856        assert_eq!(result, "<h1>Hello</h1>");
1857        cleanup_dir(&dir);
1858    }
1859
1860    #[test]
1861    fn test_fetch_not_found() {
1862        let dir = make_temp_dir();
1863        let view = make_view_with_path(&dir);
1864        let result = view.fetch("nonexistent", None);
1865        assert!(matches!(result, Err(ViewError::TemplateNotFound(_))));
1866        cleanup_dir(&dir);
1867    }
1868
1869    #[test]
1870    fn test_exists() {
1871        let dir = make_temp_dir();
1872        write_template(&dir, "index", "content");
1873        let view = make_view_with_path(&dir);
1874        assert!(view.exists("index"));
1875        assert!(!view.exists("nonexistent"));
1876        cleanup_dir(&dir);
1877    }
1878
1879    #[test]
1880    fn test_fetch_with_assign() {
1881        let dir = make_temp_dir();
1882        write_template(&dir, "index", "Name: {$name}");
1883        let view = make_view_with_path(&dir);
1884        view.assign("name", json!("Alice"));
1885        let result = view.fetch("index", None).unwrap();
1886        assert_eq!(result, "Name: Alice");
1887        cleanup_dir(&dir);
1888    }
1889
1890    // =========================================================================
1891    // 组 10:内容过滤器(对齐 PHP View::filter)
1892    // =========================================================================
1893
1894    #[test]
1895    fn test_content_filter() {
1896        // 对齐 PHP: $view->filter(function($c) { return strtoupper($c); })
1897        let view = make_view();
1898        view.set_filter(Arc::new(|content: &str| content.to_uppercase()));
1899        let result = view.display("hello world", None).unwrap();
1900        assert_eq!(result, "HELLO WORLD");
1901    }
1902
1903    #[test]
1904    fn test_clear_filter() {
1905        let view = make_view();
1906        view.set_filter(Arc::new(|content: &str| content.to_uppercase()));
1907        view.clear_filter();
1908        let result = view.display("hello world", None).unwrap();
1909        assert_eq!(result, "hello world");
1910    }
1911
1912    // =========================================================================
1913    // 组 11:数值/布尔/数组变量(对齐 PHP echo 类型转换)
1914    // =========================================================================
1915
1916    #[test]
1917    fn test_integer_var() {
1918        let view = make_view();
1919        let result = view
1920            .display(
1921                "Count: {$count}",
1922                Some(ViewData::from([("count".to_string(), json!(42))])),
1923            )
1924            .unwrap();
1925        assert_eq!(result, "Count: 42");
1926    }
1927
1928    #[test]
1929    fn test_boolean_true() {
1930        // PHP: echo true → "1"
1931        let view = make_view();
1932        let result = view
1933            .display(
1934                "Flag: {$flag}",
1935                Some(ViewData::from([("flag".to_string(), json!(true))])),
1936            )
1937            .unwrap();
1938        assert_eq!(result, "Flag: 1");
1939    }
1940
1941    #[test]
1942    fn test_boolean_false() {
1943        // PHP: echo false → ""
1944        let view = make_view();
1945        let result = view
1946            .display(
1947                "Flag: {$flag}",
1948                Some(ViewData::from([("flag".to_string(), json!(false))])),
1949            )
1950            .unwrap();
1951        assert_eq!(result, "Flag: ");
1952    }
1953
1954    #[test]
1955    fn test_float_var() {
1956        let view = make_view();
1957        let result = view
1958            .display(
1959                "Float: {$f}",
1960                Some(ViewData::from([("f".to_string(), json!(2.5))])),
1961            )
1962            .unwrap();
1963        assert_eq!(result, "Float: 2.5");
1964    }
1965
1966    #[test]
1967    fn test_float_integer_value() {
1968        // PHP: echo 3.0 → "3"
1969        let view = make_view();
1970        let result = view
1971            .display(
1972                "Num: {$num}",
1973                Some(ViewData::from([("num".to_string(), json!(3.0))])),
1974            )
1975            .unwrap();
1976        assert_eq!(result, "Num: 3");
1977    }
1978
1979    // =========================================================================
1980    // 组 12:ViewConfig 配置(对齐 PHP config/view.php)
1981    // =========================================================================
1982
1983    #[test]
1984    fn test_config_default() {
1985        let config = ViewConfig::default();
1986        assert_eq!(config.view_suffix, "html");
1987        assert_eq!(config.tpl_begin, "{");
1988        assert_eq!(config.tpl_end, "}");
1989        assert_eq!(config.default_filter, "htmlentities");
1990        assert_eq!(config.tpl_var_identify, "array");
1991        assert!(!config.layout_on);
1992    }
1993
1994    #[test]
1995    fn test_config_get_config() {
1996        let engine = SimpleTemplateEngine::new(ViewConfig::default());
1997        assert_eq!(
1998            engine.get_config("view_suffix"),
1999            Some(Value::String("html".to_string()))
2000        );
2001        assert_eq!(
2002            engine.get_config("tpl_begin"),
2003            Some(Value::String("{".to_string()))
2004        );
2005        assert_eq!(engine.get_config("nonexistent"), None);
2006    }
2007
2008    // =========================================================================
2009    // 组 13:自定义函数注册(对齐 PHP Template::extend)
2010    // =========================================================================
2011
2012    #[test]
2013    fn test_register_custom_function() {
2014        let view = make_view();
2015        // 注册自定义函数
2016        if let Some(engine) = view
2017            .engine()
2018            .as_any()
2019            .downcast_ref::<SimpleTemplateEngine>()
2020        {
2021            engine.register_function(
2022                "greet",
2023                Arc::new(|args: &[Value]| {
2024                    let name = args.first().and_then(|v| v.as_str()).unwrap_or("World");
2025                    Ok(Value::String(format!("Hello, {}!", name)))
2026                }),
2027            );
2028        }
2029        let result = view.display("{:greet('Alice')}", None).unwrap();
2030        assert_eq!(result, "Hello, Alice!");
2031    }
2032
2033    // =========================================================================
2034    // 组 14:模板路径解析(对齐 PHP parseTemplateFile)
2035    // =========================================================================
2036
2037    #[test]
2038    fn test_parse_template_path_relative() {
2039        let engine = SimpleTemplateEngine::new(ViewConfig::default());
2040        let path = engine.parse_template_path("index");
2041        assert_eq!(path, PathBuf::from("view/index.html"));
2042    }
2043
2044    #[test]
2045    fn test_parse_template_path_with_extension() {
2046        let engine = SimpleTemplateEngine::new(ViewConfig::default());
2047        let path = engine.parse_template_path("index.html");
2048        assert_eq!(path, PathBuf::from("view/index.html"));
2049    }
2050
2051    #[test]
2052    fn test_parse_template_path_absolute() {
2053        let engine = SimpleTemplateEngine::new(ViewConfig::default());
2054        let path = engine.parse_template_path("/absolute/path");
2055        assert_eq!(path, PathBuf::from("absolute/path.html"));
2056    }
2057
2058    #[test]
2059    fn test_parse_template_path_cross_app() {
2060        // 对齐 PHP: app@template
2061        let engine = SimpleTemplateEngine::new(ViewConfig::default());
2062        let path = engine.parse_template_path("admin@dashboard");
2063        assert_eq!(path, PathBuf::from("admin/view/dashboard.html"));
2064    }
2065
2066    #[test]
2067    fn test_parse_template_path_empty() {
2068        let engine = SimpleTemplateEngine::new(ViewConfig::default());
2069        let path = engine.parse_template_path("");
2070        assert_eq!(path, PathBuf::from("view/index.html"));
2071    }
2072
2073    // =========================================================================
2074    // 组 15:辅助函数测试
2075    // =========================================================================
2076
2077    #[test]
2078    fn test_htmlentities_basic() {
2079        assert_eq!(htmlentities("<b>"), "&lt;b&gt;");
2080        assert_eq!(htmlentities("\"quote\""), "&quot;quote&quot;");
2081        assert_eq!(htmlentities("'apos'"), "&#039;apos&#039;");
2082        assert_eq!(htmlentities("&amp;"), "&amp;amp;");
2083    }
2084
2085    #[test]
2086    fn test_is_truthy() {
2087        assert!(!is_truthy(&Value::Null));
2088        assert!(!is_truthy(&Value::Bool(false)));
2089        assert!(is_truthy(&Value::Bool(true)));
2090        assert!(!is_truthy(&json!(0)));
2091        assert!(is_truthy(&json!(1)));
2092        assert!(!is_truthy(&json!("")));
2093        assert!(!is_truthy(&json!("0")));
2094        assert!(is_truthy(&json!("hello")));
2095        assert!(!is_truthy(&json!([])));
2096        assert!(is_truthy(&json!([1, 2])));
2097        assert!(!is_truthy(&json!({})));
2098        assert!(is_truthy(&json!({"a": 1})));
2099    }
2100
2101    #[test]
2102    fn test_value_to_string() {
2103        assert_eq!(value_to_string(&Value::Null), "");
2104        assert_eq!(value_to_string(&Value::Bool(true)), "1");
2105        assert_eq!(value_to_string(&Value::Bool(false)), "");
2106        assert_eq!(value_to_string(&json!(42)), "42");
2107        assert_eq!(value_to_string(&json!(2.5)), "2.5");
2108        assert_eq!(value_to_string(&json!(3.0)), "3");
2109        assert_eq!(value_to_string(&json!("hello")), "hello");
2110    }
2111
2112    #[test]
2113    fn test_parse_literal() {
2114        assert_eq!(parse_literal("'string'"), Value::String("string".into()));
2115        assert_eq!(parse_literal("\"double\""), Value::String("double".into()));
2116        assert_eq!(parse_literal("42"), json!(42));
2117        assert_eq!(parse_literal("2.5"), json!(2.5));
2118        assert_eq!(parse_literal("true"), Value::Bool(true));
2119        assert_eq!(parse_literal("false"), Value::Bool(false));
2120        assert_eq!(parse_literal("null"), Value::Null);
2121    }
2122
2123    #[test]
2124    fn test_split_args() {
2125        assert_eq!(split_args("a, b, c"), vec!["a", "b", "c"]);
2126        assert_eq!(split_args("'a,b', c"), vec!["'a,b'", "c"]);
2127        assert_eq!(split_args("\"a,b\", c"), vec!["\"a,b\"", "c"]);
2128        assert_eq!(split_args(""), Vec::<String>::new());
2129    }
2130
2131    #[test]
2132    fn test_parse_func_call() {
2133        let (name, args) = parse_func_call("date('Y')").unwrap();
2134        assert_eq!(name, "date");
2135        assert_eq!(args, vec![Value::String("Y".into())]);
2136
2137        let (name, args) = parse_func_call("now()").unwrap();
2138        assert_eq!(name, "now");
2139        assert!(args.is_empty());
2140
2141        let (name, _args) = parse_func_call("noargs").unwrap();
2142        assert_eq!(name, "noargs");
2143    }
2144
2145    // =========================================================================
2146    // 组 16:模板渲染兜底场景(ViewFallback + render_*_response)
2147    // =========================================================================
2148
2149    /// 辅助函数:提取 Response 的 body 为 String(异步)
2150    async fn extract_body_string(resp: axum::response::Response) -> String {
2151        use http_body_util::BodyExt;
2152        let bytes = resp.into_body().collect().await.unwrap().to_bytes();
2153        String::from_utf8(bytes.to_vec()).unwrap()
2154    }
2155
2156    #[test]
2157    fn test_view_fallback_new() {
2158        // ViewFallback::new 创建测试
2159        let view = View::with_default_engine();
2160        let fallback = ViewFallback::new(view);
2161        assert!(!fallback.view().has_var("any"));
2162    }
2163
2164    #[test]
2165    fn test_view_fallback_with_default_engine() {
2166        // ViewFallback::with_default_engine 创建测试
2167        let fallback = ViewFallback::with_default_engine();
2168        assert!(!fallback.view().has_var("any"));
2169    }
2170
2171    #[test]
2172    fn test_view_fallback_with_config() {
2173        // ViewFallback::with_config 创建测试
2174        let config = ViewConfig {
2175            view_suffix: "tpl".to_string(),
2176            ..Default::default()
2177        };
2178        let fallback = ViewFallback::with_config(config);
2179        let view = fallback.view();
2180        // 验证配置传递
2181        let engine = view.engine();
2182        assert_eq!(
2183            engine.get_config("view_suffix"),
2184            Some(Value::String("tpl".to_string()))
2185        );
2186    }
2187
2188    #[test]
2189    fn test_view_fallback_assign() {
2190        // ViewFallback::assign 赋值测试
2191        let fallback = ViewFallback::with_default_engine();
2192        fallback.assign("name", json!("Alice"));
2193        assert_eq!(fallback.view().get_var("name"), Some(json!("Alice")));
2194    }
2195
2196    #[test]
2197    fn test_view_fallback_assign_many() {
2198        // ViewFallback::assign_many 批量赋值测试
2199        let fallback = ViewFallback::with_default_engine();
2200        let mut vars = ViewData::new();
2201        vars.insert("a".to_string(), json!(1));
2202        vars.insert("b".to_string(), json!(2));
2203        fallback.assign_many(vars);
2204        assert_eq!(fallback.view().get_var("a"), Some(json!(1)));
2205        assert_eq!(fallback.view().get_var("b"), Some(json!(2)));
2206    }
2207
2208    #[test]
2209    fn test_view_fallback_assign_chain() {
2210        // ViewFallback::assign 链式调用测试
2211        let fallback = ViewFallback::with_default_engine();
2212        fallback
2213            .assign("a", json!(1))
2214            .assign("b", json!(2))
2215            .assign("c", json!(3));
2216        assert_eq!(fallback.view().get_var("a"), Some(json!(1)));
2217        assert_eq!(fallback.view().get_var("b"), Some(json!(2)));
2218        assert_eq!(fallback.view().get_var("c"), Some(json!(3)));
2219    }
2220
2221    #[test]
2222    fn test_view_fallback_clear_vars() {
2223        // ViewFallback::clear_vars 清除变量测试
2224        let fallback = ViewFallback::with_default_engine();
2225        fallback.assign("name", json!("Alice"));
2226        assert!(fallback.view().has_var("name"));
2227        fallback.clear_vars();
2228        assert!(!fallback.view().has_var("name"));
2229    }
2230
2231    #[test]
2232    fn test_view_fallback_display_to_string() {
2233        // ViewFallback::display_to_string 渲染字符串为字符串测试
2234        // 对齐 PHP: $this->display('Hello {$name}!', ['name' => 'World'])
2235        let fallback = ViewFallback::with_default_engine();
2236        let result = fallback
2237            .display_to_string(
2238                "Hello {$name}!",
2239                Some(ViewData::from([("name".to_string(), json!("World"))])),
2240            )
2241            .unwrap();
2242        assert_eq!(result, "Hello World!");
2243    }
2244
2245    #[test]
2246    fn test_view_fallback_display_to_string_with_assign() {
2247        // ViewFallback::display_to_string 配合 assign 测试
2248        let fallback = ViewFallback::with_default_engine();
2249        fallback.assign("name", json!("Alice"));
2250        let result = fallback.display_to_string("Hello {$name}!", None).unwrap();
2251        assert_eq!(result, "Hello Alice!");
2252    }
2253
2254    #[tokio::test]
2255    async fn test_view_fallback_render_display_response() {
2256        // ViewFallback::render_display 渲染字符串为 HTML Response 测试
2257        // 对齐 PHP: $this->display($content) + Response::create($content, 'html')
2258        let fallback = ViewFallback::with_default_engine();
2259        let resp = fallback
2260            .render_display(
2261                "<h1>Hello {$name}!</h1>",
2262                Some(ViewData::from([("name".to_string(), json!("World"))])),
2263            )
2264            .unwrap();
2265
2266        // 验证 HTTP 状态码 200
2267        assert_eq!(resp.status(), axum::http::StatusCode::OK);
2268
2269        // 验证 Content-Type: text/html; charset=utf-8
2270        let content_type = resp
2271            .headers()
2272            .get(axum::http::header::CONTENT_TYPE)
2273            .unwrap()
2274            .to_str()
2275            .unwrap()
2276            .to_string();
2277        assert_eq!(content_type, "text/html; charset=utf-8");
2278
2279        // 验证 body 内容
2280        let body = extract_body_string(resp).await;
2281        assert_eq!(body, "<h1>Hello World!</h1>");
2282    }
2283
2284    #[tokio::test]
2285    async fn test_view_fallback_render_display_with_assign() {
2286        // ViewFallback::render_display 配合 assign 测试
2287        let fallback = ViewFallback::with_default_engine();
2288        fallback.assign("title", json!("Report"));
2289        let resp = fallback
2290            .render_display("<title>{$title}</title>", None)
2291            .unwrap();
2292        let body = extract_body_string(resp).await;
2293        assert_eq!(body, "<title>Report</title>");
2294    }
2295
2296    #[tokio::test]
2297    async fn test_view_fallback_render_template_file() {
2298        // ViewFallback::render_template 渲染模板文件为 HTML Response 测试
2299        // 对齐 PHP: $this->fetch('template') + Response::create($content, 'html')
2300        let dir = make_temp_dir();
2301        write_template(&dir, "pdf_template", "<pdf>{$content}</pdf>");
2302        let config = ViewConfig {
2303            view_path: dir.clone(),
2304            ..Default::default()
2305        };
2306        let fallback = ViewFallback::with_config(config);
2307
2308        let resp = fallback
2309            .render_template(
2310                "pdf_template",
2311                Some(ViewData::from([(
2312                    "content".to_string(),
2313                    json!("Hello PDF"),
2314                )])),
2315            )
2316            .unwrap();
2317
2318        assert_eq!(resp.status(), axum::http::StatusCode::OK);
2319        let content_type = resp
2320            .headers()
2321            .get(axum::http::header::CONTENT_TYPE)
2322            .unwrap()
2323            .to_str()
2324            .unwrap()
2325            .to_string();
2326        assert_eq!(content_type, "text/html; charset=utf-8");
2327        let body = extract_body_string(resp).await;
2328        assert_eq!(body, "<pdf>Hello PDF</pdf>");
2329
2330        cleanup_dir(&dir);
2331    }
2332
2333    #[test]
2334    fn test_view_fallback_render_template_not_found() {
2335        // ViewFallback::render_template 模板未找到错误测试
2336        // 对齐 PHP: TemplateNotFoundException
2337        let dir = make_temp_dir();
2338        let config = ViewConfig {
2339            view_path: dir.clone(),
2340            ..Default::default()
2341        };
2342        let fallback = ViewFallback::with_config(config);
2343
2344        let result = fallback.render_template("nonexistent", None);
2345        assert!(result.is_err());
2346        match result {
2347            Err(ViewError::TemplateNotFound(_)) => {}
2348            Err(e) => panic!("Expected TemplateNotFound, got: {:?}", e),
2349            Ok(_) => panic!("Expected error, got Ok"),
2350        }
2351
2352        cleanup_dir(&dir);
2353    }
2354
2355    #[test]
2356    fn test_view_fallback_render_to_string() {
2357        // ViewFallback::render_to_string 渲染模板文件为字符串测试
2358        // 用于 PDF/Excel 生成器输入
2359        let dir = make_temp_dir();
2360        write_template(
2361            &dir,
2362            "excel_template",
2363            "<table><tr><td>{$value}</td></tr></table>",
2364        );
2365        let config = ViewConfig {
2366            view_path: dir.clone(),
2367            ..Default::default()
2368        };
2369        let fallback = ViewFallback::with_config(config);
2370
2371        let html = fallback
2372            .render_to_string(
2373                "excel_template",
2374                Some(ViewData::from([("value".to_string(), json!(42))])),
2375            )
2376            .unwrap();
2377        assert_eq!(html, "<table><tr><td>42</td></tr></table>");
2378
2379        cleanup_dir(&dir);
2380    }
2381
2382    #[test]
2383    fn test_view_fallback_render_to_string_not_found() {
2384        // ViewFallback::render_to_string 模板未找到错误测试
2385        let dir = make_temp_dir();
2386        let config = ViewConfig {
2387            view_path: dir.clone(),
2388            ..Default::default()
2389        };
2390        let fallback = ViewFallback::with_config(config);
2391
2392        let result = fallback.render_to_string("nonexistent", None);
2393        assert!(result.is_err());
2394
2395        cleanup_dir(&dir);
2396    }
2397
2398    #[tokio::test]
2399    async fn test_render_template_response_free_function() {
2400        // 自由函数 render_template_response 测试
2401        let dir = make_temp_dir();
2402        write_template(&dir, "report", "<report>{$title}</report>");
2403        let config = ViewConfig {
2404            view_path: dir.clone(),
2405            ..Default::default()
2406        };
2407        let view = View::with_config(config);
2408
2409        let resp = render_template_response(
2410            &view,
2411            "report",
2412            Some(ViewData::from([("title".to_string(), json!("Monthly"))])),
2413        )
2414        .unwrap();
2415
2416        assert_eq!(resp.status(), axum::http::StatusCode::OK);
2417        let content_type = resp
2418            .headers()
2419            .get(axum::http::header::CONTENT_TYPE)
2420            .unwrap()
2421            .to_str()
2422            .unwrap()
2423            .to_string();
2424        assert_eq!(content_type, "text/html; charset=utf-8");
2425        let body = extract_body_string(resp).await;
2426        assert_eq!(body, "<report>Monthly</report>");
2427
2428        cleanup_dir(&dir);
2429    }
2430
2431    #[tokio::test]
2432    async fn test_render_display_response_free_function() {
2433        // 自由函数 render_display_response 测试
2434        let view = View::with_default_engine();
2435        let resp = render_display_response(
2436            &view,
2437            "<p>{$msg}</p>",
2438            Some(ViewData::from([("msg".to_string(), json!("Hello"))])),
2439        )
2440        .unwrap();
2441
2442        assert_eq!(resp.status(), axum::http::StatusCode::OK);
2443        let content_type = resp
2444            .headers()
2445            .get(axum::http::header::CONTENT_TYPE)
2446            .unwrap()
2447            .to_str()
2448            .unwrap()
2449            .to_string();
2450        assert_eq!(content_type, "text/html; charset=utf-8");
2451        let body = extract_body_string(resp).await;
2452        assert_eq!(body, "<p>Hello</p>");
2453    }
2454
2455    #[test]
2456    fn test_render_template_response_not_found() {
2457        // 自由函数 render_template_response 模板未找到错误测试
2458        let dir = make_temp_dir();
2459        let config = ViewConfig {
2460            view_path: dir.clone(),
2461            ..Default::default()
2462        };
2463        let view = View::with_config(config);
2464
2465        let result = render_template_response(&view, "nonexistent", None);
2466        assert!(result.is_err());
2467
2468        cleanup_dir(&dir);
2469    }
2470
2471    #[test]
2472    fn test_view_fallback_pdf_export_scenario() {
2473        // R5 PHP/Rust 行为对比测试:PDF 导出场景
2474        // 对齐 PHP pdf-pdftk 表单填充场景:渲染 HTML 模板作为 PDF 输入
2475        let dir = make_temp_dir();
2476        write_template(
2477            &dir,
2478            "payment_pdf",
2479            r#"<html><body><h1>付款单 {$payment_id}</h1><p>金额: {$amount}</p></body></html>"#,
2480        );
2481        let config = ViewConfig {
2482            view_path: dir.clone(),
2483            ..Default::default()
2484        };
2485        let fallback = ViewFallback::with_config(config);
2486
2487        // 模拟 PDF 导出场景:渲染模板为字符串,传给 PDF 生成器
2488        let html = fallback
2489            .render_to_string(
2490                "payment_pdf",
2491                Some(ViewData::from([
2492                    ("payment_id".to_string(), json!("PAY-001")),
2493                    ("amount".to_string(), json!("¥1,234.56")),
2494                ])),
2495            )
2496            .unwrap();
2497
2498        assert_eq!(
2499            html,
2500            r#"<html><body><h1>付款单 PAY-001</h1><p>金额: ¥1,234.56</p></body></html>"#
2501        );
2502
2503        cleanup_dir(&dir);
2504    }
2505
2506    #[tokio::test]
2507    async fn test_view_fallback_excel_export_scenario() {
2508        // R5 PHP/Rust 行为对比测试:Excel 导出场景
2509        // 对齐 PHP PhpSpreadsheet 场景:渲染 HTML 表格作为 Excel 输入
2510        let dir = make_temp_dir();
2511        write_template(
2512            &dir,
2513            "order_excel",
2514            r#"<table><tr><th>订单号</th><th>金额</th></tr><tr><td>{$order_no}</td><td>{$amount}</td></tr></table>"#,
2515        );
2516        let config = ViewConfig {
2517            view_path: dir.clone(),
2518            ..Default::default()
2519        };
2520        let fallback = ViewFallback::with_config(config);
2521
2522        // 模拟 Excel 导出场景:渲染模板为 HTML Response
2523        let resp = fallback
2524            .render_template(
2525                "order_excel",
2526                Some(ViewData::from([
2527                    ("order_no".to_string(), json!("ORD-2026-001")),
2528                    ("amount".to_string(), json!(99.50)),
2529                ])),
2530            )
2531            .unwrap();
2532
2533        let body = extract_body_string(resp).await;
2534        assert!(body.contains("<th>订单号</th>"));
2535        assert!(body.contains("<td>ORD-2026-001</td>"));
2536        assert!(body.contains("<td>99.5</td>"));
2537
2538        cleanup_dir(&dir);
2539    }
2540
2541    #[tokio::test]
2542    async fn test_view_fallback_email_scenario() {
2543        // R5 PHP/Rust 行为对比测试:邮件内容渲染场景
2544        let fallback = ViewFallback::with_default_engine();
2545        let resp = fallback
2546            .render_display(
2547                r#"<html><body><h2>Dear {$name}</h2><p>Your order #{$order_id} has been shipped.</p></body></html>"#,
2548                Some(ViewData::from([
2549                    ("name".to_string(), json!("Alice")),
2550                    ("order_id".to_string(), json!(12345)),
2551                ])),
2552            )
2553            .unwrap();
2554
2555        let body = extract_body_string(resp).await;
2556        assert!(body.contains("Dear Alice"));
2557        assert!(body.contains("#12345"));
2558        assert!(body.contains("has been shipped"));
2559    }
2560
2561    #[test]
2562    fn test_view_fallback_content_type_header() {
2563        // 验证 HTML Response 的 Content-Type 头正确设置
2564        // 对齐 PHP Response::create($content, 'html') 的 Content-Type: text/html
2565        let fallback = ViewFallback::with_default_engine();
2566        let resp = fallback.render_display("<p>test</p>", None).unwrap();
2567
2568        let content_type = resp
2569            .headers()
2570            .get(axum::http::header::CONTENT_TYPE)
2571            .unwrap()
2572            .to_str()
2573            .unwrap();
2574        // Rust 版本增加 charset=utf-8(PHP 默认不设置 charset)
2575        assert!(content_type.starts_with("text/html"));
2576        assert!(content_type.contains("charset=utf-8"));
2577    }
2578
2579    #[test]
2580    fn test_view_fallback_http_status() {
2581        // 验证 HTML Response 的 HTTP 状态码为 200
2582        // 对齐 PHP Response::create($content, 'html', 200)
2583        let fallback = ViewFallback::with_default_engine();
2584        let resp = fallback.render_display("<html></html>", None).unwrap();
2585        assert_eq!(resp.status(), axum::http::StatusCode::OK);
2586    }
2587
2588    #[test]
2589    fn test_view_fallback_with_layout() {
2590        // ViewFallback + 布局集成测试
2591        // 对齐 PHP layout_on=true 场景下的模板渲染
2592        let dir = make_temp_dir();
2593        write_template(&dir, "layout", "<html><body>{__CONTENT__}</body></html>");
2594        write_template(&dir, "page", "<p>{$content}</p>");
2595        let config = ViewConfig {
2596            view_path: dir.clone(),
2597            layout_on: true,
2598            layout_name: "layout".to_string(),
2599            ..Default::default()
2600        };
2601        let fallback = ViewFallback::with_config(config);
2602
2603        let html = fallback
2604            .render_to_string(
2605                "page",
2606                Some(ViewData::from([("content".to_string(), json!("Hello"))])),
2607            )
2608            .unwrap();
2609        assert_eq!(html, "<html><body><p>Hello</p></body></html>");
2610
2611        cleanup_dir(&dir);
2612    }
2613
2614    #[test]
2615    fn test_view_fallback_with_inheritance() {
2616        // ViewFallback + 模板继承集成测试
2617        // 对齐 PHP {extend name="..."} 场景下的模板渲染
2618        let dir = make_temp_dir();
2619        write_template(
2620            &dir,
2621            "base",
2622            "<html>{block name='content'}default{/block}</html>",
2623        );
2624        write_template(
2625            &dir,
2626            "child",
2627            "{extend name='base'}{block name='content'}{$msg}{/block}",
2628        );
2629        let config = ViewConfig {
2630            view_path: dir.clone(),
2631            ..Default::default()
2632        };
2633        let fallback = ViewFallback::with_config(config);
2634
2635        let html = fallback
2636            .render_to_string(
2637                "child",
2638                Some(ViewData::from([(
2639                    "msg".to_string(),
2640                    json!("Hello Inheritance"),
2641                )])),
2642            )
2643            .unwrap();
2644        assert_eq!(html, "<html>Hello Inheritance</html>");
2645
2646        cleanup_dir(&dir);
2647    }
2648
2649    #[tokio::test]
2650    async fn test_view_fallback_complex_template() {
2651        // ViewFallback 复杂模板渲染测试(变量 + 过滤器 + 三元表达式)
2652        let fallback = ViewFallback::with_default_engine();
2653        let template = r#"<div class="user">
2654  <span>{$name|upper}</span>
2655  <span>{$email|default='N/A'}</span>
2656  <span>{$active?='启用':'禁用'}</span>
2657</div>"#;
2658        let resp = fallback
2659            .render_display(
2660                template,
2661                Some(ViewData::from([
2662                    ("name".to_string(), json!("alice")),
2663                    ("active".to_string(), json!(true)),
2664                ])),
2665            )
2666            .unwrap();
2667        let body = extract_body_string(resp).await;
2668        assert!(body.contains("ALICE"));
2669        assert!(body.contains("N/A"));
2670        assert!(body.contains("启用"));
2671    }
2672}