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        use std::io::Write;
157        
158        // 为每行文本创建段落
159        let text_lines: Vec<&str> = rendered_text.lines().collect();
160        let mut result: Vec<u8> = Vec::new();
161        let mut writer = Writer::new(Cursor::new(&mut result));
162        
163        // 写入文档开始部分
164        let doc_start = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
165<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
166<w:body>"#;
167        writer.get_mut().write_all(doc_start.as_bytes())?;
168        
169        // 为每行文本创建段落
170        for line in text_lines {
171            if line.trim().is_empty() {
172                // 空行创建空段落
173                let empty_para = r#"<w:p>
174  <w:pPr>
175    <w:rPr>
176      <w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
177    </w:rPr>
178  </w:pPr>
179</w:p>"#;
180                writer.get_mut().write_all(empty_para.as_bytes())?;
181            } else {
182                // 有内容的行
183                let para_content = format!(r#"<w:p>
184  <w:pPr>
185    <w:rPr>
186      <w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
187    </w:rPr>
188  </w:pPr>
189  <w:r>
190    <w:rPr>
191      <w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
192    </w:rPr>
193    <w:t>{}</w:t>
194  </w:r>
195</w:p>"#, line);
196                writer.get_mut().write_all(para_content.as_bytes())?;
197            }
198        }
199        
200        // 写入文档结束部分
201        let doc_end = r#"</w:body>
202</w:document>"#;
203        writer.get_mut().write_all(doc_end.as_bytes())?;
204        
205        String::from_utf8(result).map_err(|e| DocxHandlebarsError::Custom(format!("UTF-8 转换错误: {}", e)))
206    }
207    
208    /// 改进的逐节点渲染,更好地处理跨节点的 Handlebars 语法
209    fn render_xml_text_per_node_improved(&self, xml_content: &str, data: &Value) -> Result<String> {
210        use quick_xml::Writer;
211        use std::io::Cursor;
212        
213        let mut result: Vec<u8> = Vec::new();
214        let mut reader = Reader::from_str(xml_content);
215        let mut writer = Writer::new(Cursor::new(&mut result));
216        
217        let mut buf = Vec::new();
218        let mut in_paragraph = false;
219        let mut paragraph_texts = Vec::new();
220        let mut current_paragraph_events: Vec<Event<'static>> = Vec::new();
221        
222        loop {
223            match reader.read_event_into(&mut buf) {
224                Ok(Event::Start(ref e)) => {
225                    if e.name().as_ref() == b"w:p" {
226                        in_paragraph = true;
227                        paragraph_texts.clear();
228                        current_paragraph_events.clear();
229                        current_paragraph_events.push(Event::Start(e.clone().into_owned()));
230                    } else if in_paragraph {
231                        current_paragraph_events.push(Event::Start(e.clone().into_owned()));
232                    } else {
233                        writer.write_event(Event::Start(e.clone()))?;
234                    }
235                }
236                Ok(Event::Text(e)) => {
237                    if in_paragraph {
238                        // 检查是否在 w:t 元素内
239                        let is_in_text_element = current_paragraph_events.iter().rev()
240                            .any(|event| matches!(event, Event::Start(start_event) if start_event.name().as_ref() == b"w:t"));
241                        
242                        if is_in_text_element {
243                            paragraph_texts.push(e.unescape()?.to_string());
244                        }
245                        current_paragraph_events.push(Event::Text(e.into_owned()));
246                    } else {
247                        writer.write_event(Event::Text(e))?;
248                    }
249                }
250                Ok(Event::End(ref e)) => {
251                    if e.name().as_ref() == b"w:p" {
252                        // 段落结束,处理整个段落的文本
253                        let paragraph_text = paragraph_texts.join("");
254                        
255                        if paragraph_text.contains("{{") && paragraph_text.contains("}}") {
256                            // 尝试渲染段落文本
257                            let rendered_paragraph = match self.handlebars.render_template(&paragraph_text, data) {
258                                Ok(rendered) => rendered,
259                                Err(_) => paragraph_text.clone(),
260                            };
261                            
262                            // 检查是否包含复杂的模板语法
263                            let has_complex_syntax = paragraph_text.contains("{{#each") 
264                                || paragraph_text.contains("{{#if") 
265                                || paragraph_text.contains("{{/each}}")
266                                || paragraph_text.contains("{{/if}}");
267                            
268                            if has_complex_syntax {
269                                // 复杂模板,使用简化处理 - 放在第一个文本节点
270                                let mut text_written = false;
271                                let mut in_text_element = false;
272                                for event in &current_paragraph_events {
273                                    match event {
274                                        Event::Start(e) if e.name().as_ref() == b"w:t" => {
275                                            in_text_element = true;
276                                            writer.write_event(Event::Start(e.clone()))?;
277                                        }
278                                        Event::Text(_) if in_text_element => {
279                                            if !text_written {
280                                                let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
281                                                writer.write_event(Event::Text(text_event))?;
282                                                text_written = true;
283                                            }
284                                        }
285                                        Event::End(e) if e.name().as_ref() == b"w:t" => {
286                                            in_text_element = false;
287                                            writer.write_event(Event::End(e.clone()))?;
288                                        }
289                                        _ => {
290                                            writer.write_event(event.clone())?;
291                                        }
292                                    }
293                                }
294                            } else {
295                                // 简单模板,应用渲染结果但保持格式
296                                let mut text_written = false;
297                                let mut in_text_element = false;
298                                for event in &current_paragraph_events {
299                                    match event {
300                                        Event::Start(e) if e.name().as_ref() == b"w:t" => {
301                                            in_text_element = true;
302                                            writer.write_event(Event::Start(e.clone()))?;
303                                        }
304                                        Event::Text(_) if in_text_element => {
305                                            if !text_written {
306                                                // 在第一个文本节点写入渲染后的内容
307                                                let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
308                                                writer.write_event(Event::Text(text_event))?;
309                                                text_written = true;
310                                            }
311                                            // 后续文本节点跳过
312                                        }
313                                        Event::End(e) if e.name().as_ref() == b"w:t" => {
314                                            in_text_element = false;
315                                            writer.write_event(Event::End(e.clone()))?;
316                                        }
317                                        _ => {
318                                            writer.write_event(event.clone())?;
319                                        }
320                                    }
321                                }
322                            }
323                        } else {
324                            // 没有模板语法,直接写入原始段落
325                            for event in &current_paragraph_events {
326                                writer.write_event(event.clone())?;
327                            }
328                        }
329                        
330                        writer.write_event(Event::End(e.clone()))?;
331                        in_paragraph = false;
332                    } else if in_paragraph {
333                        current_paragraph_events.push(Event::End(e.clone().into_owned()));
334                    } else {
335                        writer.write_event(Event::End(e.clone()))?;
336                    }
337                }
338                Ok(Event::Eof) => break,
339                Ok(event) => {
340                    if in_paragraph {
341                        current_paragraph_events.push(event.into_owned());
342                    } else {
343                        writer.write_event(event)?;
344                    }
345                }
346                Err(e) => return Err(DocxHandlebarsError::Xml(e)),
347            }
348            buf.clear();
349        }
350        
351        String::from_utf8(result).map_err(|e| DocxHandlebarsError::Custom(format!("UTF-8 转换错误: {}", e)))
352    }
353    
354}
355
356impl Default for TemplateEngine {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362// 自定义 Handlebars helpers
363
364fn eq_helper(
365    h: &Helper,
366    _: &Handlebars,
367    _: &Context,
368    _: &mut RenderContext,
369    out: &mut dyn Output,
370) -> HelperResult {
371    let param1 = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
372    let param2 = h.param(1).and_then(|v| v.value().as_str()).unwrap_or("");
373    out.write(&(param1 == param2).to_string())?;
374    Ok(())
375}
376
377fn ne_helper(
378    h: &Helper,
379    _: &Handlebars,
380    _: &Context,
381    _: &mut RenderContext,
382    out: &mut dyn Output,
383) -> HelperResult {
384    let param1 = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
385    let param2 = h.param(1).and_then(|v| v.value().as_str()).unwrap_or("");
386    out.write(&(param1 != param2).to_string())?;
387    Ok(())
388}
389
390fn gt_helper(
391    h: &Helper,
392    _: &Handlebars,
393    _: &Context,
394    _: &mut RenderContext,
395    out: &mut dyn Output,
396) -> HelperResult {
397    let param1 = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
398    let param2 = h.param(1).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
399    out.write(&(param1 > param2).to_string())?;
400    Ok(())
401}
402
403fn lt_helper(
404    h: &Helper,
405    _: &Handlebars,
406    _: &Context,
407    _: &mut RenderContext,
408    out: &mut dyn Output,
409) -> HelperResult {
410    let param1 = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
411    let param2 = h.param(1).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
412    out.write(&(param1 < param2).to_string())?;
413    Ok(())
414}
415
416fn and_helper(
417    h: &Helper,
418    _: &Handlebars,
419    _: &Context,
420    _: &mut RenderContext,
421    out: &mut dyn Output,
422) -> HelperResult {
423    let param1 = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
424    let param2 = h.param(1).and_then(|v| v.value().as_bool()).unwrap_or(false);
425    out.write(&(param1 && param2).to_string())?;
426    Ok(())
427}
428
429fn or_helper(
430    h: &Helper,
431    _: &Handlebars,
432    _: &Context,
433    _: &mut RenderContext,
434    out: &mut dyn Output,
435) -> HelperResult {
436    let param1 = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
437    let param2 = h.param(1).and_then(|v| v.value().as_bool()).unwrap_or(false);
438    out.write(&(param1 || param2).to_string())?;
439    Ok(())
440}
441
442fn not_helper(
443    h: &Helper,
444    _: &Handlebars,
445    _: &Context,
446    _: &mut RenderContext,
447    out: &mut dyn Output,
448) -> HelperResult {
449    let param = h.param(0).and_then(|v| v.value().as_bool()).unwrap_or(false);
450    out.write(&(!param).to_string())?;
451    Ok(())
452}
453
454fn format_number_helper(
455    h: &Helper,
456    _: &Handlebars,
457    _: &Context,
458    _: &mut RenderContext,
459    out: &mut dyn Output,
460) -> HelperResult {
461    let number = h.param(0).and_then(|v| v.value().as_f64()).unwrap_or(0.0);
462    let formatted = format!("{:.2}", number);
463    out.write(&formatted)?;
464    Ok(())
465}
466
467fn format_date_helper(
468    h: &Helper,
469    _: &Handlebars,
470    _: &Context,
471    _: &mut RenderContext,
472    out: &mut dyn Output,
473) -> HelperResult {
474    let date_str = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
475    // 这里可以实现更复杂的日期格式化逻辑
476    out.write(date_str)?;
477    Ok(())
478}
479
480fn upper_helper(
481    h: &Helper,
482    _: &Handlebars,
483    _: &Context,
484    _: &mut RenderContext,
485    out: &mut dyn Output,
486) -> HelperResult {
487    let text = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
488    out.write(&text.to_uppercase())?;
489    Ok(())
490}
491
492fn lower_helper(
493    h: &Helper,
494    _: &Handlebars,
495    _: &Context,
496    _: &mut RenderContext,
497    out: &mut dyn Output,
498) -> HelperResult {
499    let text = h.param(0).and_then(|v| v.value().as_str()).unwrap_or("");
500    out.write(&text.to_lowercase())?;
501    Ok(())
502}