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
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 if !paragraph_has_content {
185 if current_line_index < text_lines.len() {
186 let line_text = text_lines[current_line_index];
187
188 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 let text_event = quick_xml::events::BytesText::new("");
203 writer.write_event(Event::Text(text_event))?;
204 paragraph_has_content = true;
205 }
206 }
207 } 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 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 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 let paragraph_text = paragraph_texts.join("");
278
279 if paragraph_text.contains("{{") && paragraph_text.contains("}}") {
280 let rendered_paragraph = match self.handlebars.render_template(¶graph_text, data) {
282 Ok(rendered) => rendered,
283 Err(_) => paragraph_text.clone(),
284 };
285
286 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 let mut text_written = false;
295 let mut in_text_element = false;
296 for event in ¤t_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 let mut text_written = false;
321 let mut in_text_element = false;
322 for event in ¤t_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 let text_event = quick_xml::events::BytesText::new(&rendered_paragraph);
332 writer.write_event(Event::Text(text_event))?;
333 text_written = true;
334 }
335 }
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 for event in ¤t_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
386fn 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 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}