docx_handlebars/
template.rs

1use crate::error::{DocxHandlebarsError, Result};
2use handlebars::{Handlebars, Helper, Context, RenderContext, Output, HelperResult};
3use quick_xml::events::Event;
4use quick_xml::Reader;
5use regex::Regex;
6use serde_json::Value;
7use std::collections::HashSet;
8
9/// Handlebars 模板引擎
10pub struct TemplateEngine {
11    handlebars: Handlebars<'static>,
12}
13
14impl TemplateEngine {
15    /// 创建新的模板引擎
16    pub fn new() -> Self {
17        let mut handlebars = Handlebars::new();
18        
19        // 注册自定义 helper
20        handlebars.register_helper("eq", Box::new(eq_helper));
21        handlebars.register_helper("ne", Box::new(ne_helper));
22        handlebars.register_helper("gt", Box::new(gt_helper));
23        handlebars.register_helper("lt", Box::new(lt_helper));
24        handlebars.register_helper("and", Box::new(and_helper));
25        handlebars.register_helper("or", Box::new(or_helper));
26        handlebars.register_helper("not", Box::new(not_helper));
27        handlebars.register_helper("format_number", Box::new(format_number_helper));
28        handlebars.register_helper("format_date", Box::new(format_date_helper));
29        handlebars.register_helper("upper", Box::new(upper_helper));
30        handlebars.register_helper("lower", Box::new(lower_helper));
31
32        Self { handlebars }
33    }
34
35    /// 从 XML 内容中提取模板变量
36    pub fn extract_variables(&self, xml_content: &str) -> Result<Vec<String>> {
37        let text_content = self.extract_text_from_xml(xml_content)?;
38        let variables = self.extract_handlebars_variables(&text_content);
39        Ok(variables)
40    }
41
42    /// 从 XML 中提取纯文本(保留换行信息)
43    fn extract_text_from_xml(&self, xml_content: &str) -> Result<String> {
44        let mut text_content = String::new();
45        let mut reader = Reader::from_str(xml_content);
46
47        let mut buf = Vec::new();
48        let mut in_text = false;
49
50        loop {
51            match reader.read_event_into(&mut buf) {
52                Ok(Event::Start(ref e)) => {
53                    if e.name().as_ref() == b"w:t" {
54                        in_text = true;
55                    } else if e.name().as_ref() == b"w:p" {
56                        // 段落开始,如果不是第一个段落则添加换行
57                        if !text_content.is_empty() {
58                            text_content.push('\n');
59                        }
60                    }
61                }
62                Ok(Event::Text(e)) => {
63                    if in_text {
64                        text_content.push_str(e.unescape()?.as_ref());
65                    }
66                }
67                Ok(Event::End(ref e)) => {
68                    if e.name().as_ref() == b"w:t" {
69                        in_text = false;
70                    }
71                }
72                Ok(Event::Empty(ref e)) => {
73                    if e.name().as_ref() == b"w:br" {
74                        // 换行标签
75                        text_content.push('\n');
76                    }
77                }
78                Ok(Event::Eof) => break,
79                Err(e) => return Err(DocxHandlebarsError::Xml(e)),
80                _ => {}
81            }
82            buf.clear();
83        }
84
85        Ok(text_content)
86    }
87
88    /// 提取 Handlebars 变量
89    fn extract_handlebars_variables(&self, text: &str) -> Vec<String> {
90        let mut variables = HashSet::new();
91        
92        // 匹配 {{variable}} 和 {{#each items}} 等模式
93        let re = Regex::new(r"\{\{[#/]?([^}]+)\}\}").unwrap();
94        
95        for cap in re.captures_iter(text) {
96            if let Some(var_match) = cap.get(1) {
97                let var_str = var_match.as_str().trim();
98                
99                // 过滤掉 helpers 和特殊关键字
100                if !var_str.starts_with('#') && !var_str.starts_with('/') {
101                    // 提取变量名(去掉 helper 调用)
102                    let var_name = var_str.split_whitespace().next().unwrap_or(var_str);
103                    if !["if", "unless", "each", "with", "else"].contains(&var_name) {
104                        variables.insert(var_name.to_string());
105                    }
106                }
107            }
108        }
109        
110        variables.into_iter().collect()
111    }
112
113    /// 渲染 XML 内容
114    pub fn render_content(&self, xml_content: &str, data: &Value) -> Result<String> {
115        // 首先检查是否包含复杂的跨段落模板
116        let full_text = self.extract_text_from_xml(xml_content)?;
117        
118        if self.has_complex_cross_paragraph_templates(&full_text) {
119            // 使用文档级别渲染
120            self.render_document_level_template(xml_content, data)
121        } else {
122            // 使用段落级别渲染
123            self.render_xml_text_per_node_improved(xml_content, data)
124        }
125    }
126
127    /// 检查是否包含复杂的跨段落模板语法
128    fn has_complex_cross_paragraph_templates(&self, text: &str) -> bool {
129        let has_each = text.contains("{{#each") && text.contains("{{/each}}");
130        let has_if = text.contains("{{#if") && text.contains("{{/if}}");
131        has_each || has_if
132    }
133
134    /// 文档级别的模板渲染
135    fn render_document_level_template(&self, xml_content: &str, data: &Value) -> Result<String> {
136        // 首先提取完整的文本内容
137        let full_text = self.extract_text_from_xml(xml_content)?;
138        
139        // 渲染完整的模板
140        let rendered_text = match self.handlebars.render_template(&full_text, data) {
141            Ok(rendered) => rendered,
142            Err(e) => {
143                eprintln!("模板渲染失败: {}, 回退到段落级别渲染", e);
144                return self.render_xml_text_per_node_improved(xml_content, data);
145            }
146        };
147        
148        // 将渲染后的文本重新分配到文档结构中
149        self.redistribute_rendered_text(xml_content, &rendered_text)
150    }
151
152    /// 将渲染后的文本重新分配到文档的段落结构中
153    fn redistribute_rendered_text(&self, xml_content: &str, rendered_text: &str) -> Result<String> {
154        use quick_xml::Writer;
155        use std::io::Cursor;
156        
157        let mut result: Vec<u8> = Vec::new();
158        let mut reader = Reader::from_str(xml_content);
159        let mut writer = Writer::new(Cursor::new(&mut result));
160        
161        let mut buf = Vec::new();
162        let mut in_paragraph = false;
163        let text_lines: Vec<&str> = rendered_text.lines().collect();
164        let mut current_line_index = 0;
165        let mut in_text_element = false;
166        let mut paragraph_has_content = false;
167        let mut _paragraph_index = 0;
168        
169        loop {
170            match reader.read_event_into(&mut buf) {
171                Ok(Event::Start(ref e)) => {
172                    if e.name().as_ref() == b"w:p" {
173                        in_paragraph = true;
174                        paragraph_has_content = false;
175                        _paragraph_index += 1;
176                    } else if e.name().as_ref() == b"w:t" && in_paragraph {
177                        in_text_element = true;
178                    }
179                    writer.write_event(Event::Start(e.clone()))?;
180                }
181                Ok(Event::Text(ref e)) => {
182                    if in_text_element && in_paragraph {
183                        // 只在第一个文本元素中写入对应行的内容
184                        if !paragraph_has_content {
185                            if current_line_index < text_lines.len() {
186                                let line_text = text_lines[current_line_index];
187                                
188                                // 对于空行,分配一个空字符串
189                                if line_text.trim().is_empty() {
190                                    let text_event = quick_xml::events::BytesText::new("");
191                                    writer.write_event(Event::Text(text_event))?;
192                                    current_line_index += 1;
193                                    paragraph_has_content = true;
194                                } else {
195                                    let text_event = quick_xml::events::BytesText::new(line_text);
196                                    writer.write_event(Event::Text(text_event))?;
197                                    current_line_index += 1;
198                                    paragraph_has_content = true;
199                                }
200                            } else {
201                                // 没有更多文本行,输出空字符串
202                                let text_event = quick_xml::events::BytesText::new("");
203                                writer.write_event(Event::Text(text_event))?;
204                                paragraph_has_content = true;
205                            }
206                        }
207                        // 如果这个段落已经有内容了,跳过后续的文本节点
208                    } else {
209                        writer.write_event(Event::Text(e.clone()))?;
210                    }
211                }
212                Ok(Event::End(ref e)) => {
213                    if e.name().as_ref() == b"w:p" {
214                        in_paragraph = false;
215                    } else if e.name().as_ref() == b"w:t" && in_paragraph {
216                        in_text_element = false;
217                    }
218                    writer.write_event(Event::End(e.clone()))?;
219                }
220                Ok(Event::Eof) => break,
221                Ok(event) => {
222                    writer.write_event(event)?;
223                }
224                Err(e) => return Err(DocxHandlebarsError::Xml(e)),
225            }
226            buf.clear();
227        }
228        
229        String::from_utf8(result).map_err(|e| DocxHandlebarsError::Custom(format!("UTF-8 转换错误: {}", e)))
230    }
231    
232    /// 改进的逐节点渲染,更好地处理跨节点的 Handlebars 语法
233    fn render_xml_text_per_node_improved(&self, xml_content: &str, data: &Value) -> Result<String> {
234        use quick_xml::Writer;
235        use std::io::Cursor;
236        
237        let mut result: Vec<u8> = Vec::new();
238        let mut reader = Reader::from_str(xml_content);
239        let mut writer = Writer::new(Cursor::new(&mut result));
240        
241        let mut buf = Vec::new();
242        let mut in_paragraph = false;
243        let mut paragraph_texts = Vec::new();
244        let mut current_paragraph_events: Vec<Event<'static>> = Vec::new();
245        
246        loop {
247            match reader.read_event_into(&mut buf) {
248                Ok(Event::Start(ref e)) => {
249                    if e.name().as_ref() == b"w:p" {
250                        in_paragraph = true;
251                        paragraph_texts.clear();
252                        current_paragraph_events.clear();
253                        current_paragraph_events.push(Event::Start(e.clone().into_owned()));
254                    } else if in_paragraph {
255                        current_paragraph_events.push(Event::Start(e.clone().into_owned()));
256                    } else {
257                        writer.write_event(Event::Start(e.clone()))?;
258                    }
259                }
260                Ok(Event::Text(e)) => {
261                    if in_paragraph {
262                        // 检查是否在 w:t 元素内
263                        let is_in_text_element = current_paragraph_events.iter().rev()
264                            .any(|event| matches!(event, Event::Start(start_event) if start_event.name().as_ref() == b"w:t"));
265                        
266                        if is_in_text_element {
267                            paragraph_texts.push(e.unescape()?.to_string());
268                        }
269                        current_paragraph_events.push(Event::Text(e.into_owned()));
270                    } else {
271                        writer.write_event(Event::Text(e))?;
272                    }
273                }
274                Ok(Event::End(ref e)) => {
275                    if e.name().as_ref() == b"w:p" {
276                        // 段落结束,处理整个段落的文本
277                        let paragraph_text = paragraph_texts.join("");
278                        
279                        if paragraph_text.contains("{{") && paragraph_text.contains("}}") {
280                            // 尝试渲染段落文本
281                            let rendered_paragraph = match self.handlebars.render_template(&paragraph_text, data) {
282                                Ok(rendered) => rendered,
283                                Err(_) => paragraph_text.clone(),
284                            };
285                            
286                            // 检查是否包含复杂的模板语法
287                            let has_complex_syntax = paragraph_text.contains("{{#each") 
288                                || paragraph_text.contains("{{#if") 
289                                || paragraph_text.contains("{{/each}}")
290                                || paragraph_text.contains("{{/if}}");
291                            
292                            if has_complex_syntax {
293                                // 复杂模板,使用简化处理 - 放在第一个文本节点
294                                let mut text_written = false;
295                                let mut in_text_element = false;
296                                for event in &current_paragraph_events {
297                                    match event {
298                                        Event::Start(e) if e.name().as_ref() == b"w:t" => {
299                                            in_text_element = true;
300                                            writer.write_event(Event::Start(e.clone()))?;
301                                        }
302                                        Event::Text(_) if in_text_element => {
303                                            if !text_written {
304                                                let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
305                                                writer.write_event(Event::Text(text_event))?;
306                                                text_written = true;
307                                            }
308                                        }
309                                        Event::End(e) if e.name().as_ref() == b"w:t" => {
310                                            in_text_element = false;
311                                            writer.write_event(Event::End(e.clone()))?;
312                                        }
313                                        _ => {
314                                            writer.write_event(event.clone())?;
315                                        }
316                                    }
317                                }
318                            } else {
319                                // 简单模板,应用渲染结果但保持格式
320                                let mut text_written = false;
321                                let mut in_text_element = false;
322                                for event in &current_paragraph_events {
323                                    match event {
324                                        Event::Start(e) if e.name().as_ref() == b"w:t" => {
325                                            in_text_element = true;
326                                            writer.write_event(Event::Start(e.clone()))?;
327                                        }
328                                        Event::Text(_) if in_text_element => {
329                                            if !text_written {
330                                                // 在第一个文本节点写入渲染后的内容
331                                                let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
332                                                writer.write_event(Event::Text(text_event))?;
333                                                text_written = true;
334                                            }
335                                            // 后续文本节点跳过
336                                        }
337                                        Event::End(e) if e.name().as_ref() == b"w:t" => {
338                                            in_text_element = false;
339                                            writer.write_event(Event::End(e.clone()))?;
340                                        }
341                                        _ => {
342                                            writer.write_event(event.clone())?;
343                                        }
344                                    }
345                                }
346                            }
347                        } else {
348                            // 没有模板语法,直接写入原始段落
349                            for event in &current_paragraph_events {
350                                writer.write_event(event.clone())?;
351                            }
352                        }
353                        
354                        writer.write_event(Event::End(e.clone()))?;
355                        in_paragraph = false;
356                    } else if in_paragraph {
357                        current_paragraph_events.push(Event::End(e.clone().into_owned()));
358                    } else {
359                        writer.write_event(Event::End(e.clone()))?;
360                    }
361                }
362                Ok(Event::Eof) => break,
363                Ok(event) => {
364                    if in_paragraph {
365                        current_paragraph_events.push(event.into_owned());
366                    } else {
367                        writer.write_event(event)?;
368                    }
369                }
370                Err(e) => return Err(DocxHandlebarsError::Xml(e)),
371            }
372            buf.clear();
373        }
374        
375        String::from_utf8(result).map_err(|e| DocxHandlebarsError::Custom(format!("UTF-8 转换错误: {}", e)))
376    }
377    
378}
379
380impl Default for TemplateEngine {
381    fn default() -> Self {
382        Self::new()
383    }
384}
385
386// 自定义 Handlebars helpers
387
388fn eq_helper(
389    h: &Helper,
390    _: &Handlebars,
391    _: &Context,
392    _: &mut RenderContext,
393    out: &mut dyn Output,
394) -> HelperResult {
395    let param1 = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
396    let param2 = h.param(1).and_then(|v| v.value().as_str()).unwrap_or("");
397    out.write(&(param1 == param2).to_string())?;
398    Ok(())
399}
400
401fn ne_helper(
402    h: &Helper,
403    _: &Handlebars,
404    _: &Context,
405    _: &mut RenderContext,
406    out: &mut dyn Output,
407) -> HelperResult {
408    let param1 = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
409    let param2 = h.param(1).and_then(|v| v.value().as_str()).unwrap_or("");
410    out.write(&(param1 != param2).to_string())?;
411    Ok(())
412}
413
414fn gt_helper(
415    h: &Helper,
416    _: &Handlebars,
417    _: &Context,
418    _: &mut RenderContext,
419    out: &mut dyn Output,
420) -> HelperResult {
421    let param1 = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
422    let param2 = h.param(1).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
423    out.write(&(param1 > param2).to_string())?;
424    Ok(())
425}
426
427fn lt_helper(
428    h: &Helper,
429    _: &Handlebars,
430    _: &Context,
431    _: &mut RenderContext,
432    out: &mut dyn Output,
433) -> HelperResult {
434    let param1 = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
435    let param2 = h.param(1).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
436    out.write(&(param1 < param2).to_string())?;
437    Ok(())
438}
439
440fn and_helper(
441    h: &Helper,
442    _: &Handlebars,
443    _: &Context,
444    _: &mut RenderContext,
445    out: &mut dyn Output,
446) -> HelperResult {
447    let param1 = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
448    let param2 = h.param(1).and_then(|v| v.value().as_bool()).unwrap_or(false);
449    out.write(&(param1 && param2).to_string())?;
450    Ok(())
451}
452
453fn or_helper(
454    h: &Helper,
455    _: &Handlebars,
456    _: &Context,
457    _: &mut RenderContext,
458    out: &mut dyn Output,
459) -> HelperResult {
460    let param1 = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
461    let param2 = h.param(1).and_then(|v| v.value().as_bool()).unwrap_or(false);
462    out.write(&(param1 || param2).to_string())?;
463    Ok(())
464}
465
466fn not_helper(
467    h: &Helper,
468    _: &Handlebars,
469    _: &Context,
470    _: &mut RenderContext,
471    out: &mut dyn Output,
472) -> HelperResult {
473    let param = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
474    out.write(&(!param).to_string())?;
475    Ok(())
476}
477
478fn format_number_helper(
479    h: &Helper,
480    _: &Handlebars,
481    _: &Context,
482    _: &mut RenderContext,
483    out: &mut dyn Output,
484) -> HelperResult {
485    let number = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
486    let formatted = format!("{:.2}", number);
487    out.write(&formatted)?;
488    Ok(())
489}
490
491fn format_date_helper(
492    h: &Helper,
493    _: &Handlebars,
494    _: &Context,
495    _: &mut RenderContext,
496    out: &mut dyn Output,
497) -> HelperResult {
498    let date_str = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
499    // 这里可以实现更复杂的日期格式化逻辑
500    out.write(date_str)?;
501    Ok(())
502}
503
504fn upper_helper(
505    h: &Helper,
506    _: &Handlebars,
507    _: &Context,
508    _: &mut RenderContext,
509    out: &mut dyn Output,
510) -> HelperResult {
511    let text = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
512    out.write(&text.to_uppercase())?;
513    Ok(())
514}
515
516fn lower_helper(
517    h: &Helper,
518    _: &Handlebars,
519    _: &Context,
520    _: &mut RenderContext,
521    out: &mut dyn Output,
522) -> HelperResult {
523    let text = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
524    out.write(&text.to_lowercase())?;
525    Ok(())
526}