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
9pub struct TemplateEngine {
11 handlebars: Handlebars<'static>,
12}
13
14impl TemplateEngine {
15 pub fn new() -> Self {
17 let mut handlebars = Handlebars::new();
18
19 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 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 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 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 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 fn extract_handlebars_variables(&self, text: &str) -> Vec<String> {
90 let mut variables = HashSet::new();
91
92 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 if !var_str.starts_with('#') && !var_str.starts_with('/') {
101 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 pub fn render_content(&self, xml_content: &str, data: &Value) -> Result<String> {
115 let full_text = self.extract_text_from_xml(xml_content)?;
117
118 if self.has_complex_cross_paragraph_templates(&full_text) {
119 self.render_document_level_template(xml_content, data)
121 } else {
122 self.render_xml_text_per_node_improved(xml_content, data)
124 }
125 }
126
127 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 fn render_document_level_template(&self, xml_content: &str, data: &Value) -> Result<String> {
136 let full_text = self.extract_text_from_xml(xml_content)?;
138
139 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 self.redistribute_rendered_text(xml_content, &rendered_text)
150 }
151
152 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 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 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 for line in text_lines {
171 if line.trim().is_empty() {
172 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 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 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 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 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 let paragraph_text = paragraph_texts.join("");
254
255 if paragraph_text.contains("{{") && paragraph_text.contains("}}") {
256 let rendered_paragraph = match self.handlebars.render_template(¶graph_text, data) {
258 Ok(rendered) => rendered,
259 Err(_) => paragraph_text.clone(),
260 };
261
262 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 let mut text_written = false;
271 let mut in_text_element = false;
272 for event in ¤t_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 let mut text_written = false;
297 let mut in_text_element = false;
298 for event in ¤t_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 let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
308 writer.write_event(Event::Text(text_event))?;
309 text_written = true;
310 }
311 }
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 for event in ¤t_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
362fn 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 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}