1use crate::error::DocxError;
2use crate::image::{DOCX_EMU, DocxImage};
3use crate::request::request_image_data;
4use quick_xml::Writer;
5use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
6use reqwest::Client;
7use std::collections::HashMap;
8use std::fs::File;
9use std::io::{Cursor, Read, Write};
10use std::time::Duration;
11use zip::write::SimpleFileOptions;
12use zip::{ZipArchive, ZipWriter};
13
14static PREFIX_TAG: &str = "{{";
15static SUFFIX_TAG: &str = "}}";
16
17pub struct DocxTemplate {
18 text_replacements: HashMap<String, String>,
20 image_replacements: HashMap<String, Option<DocxImage>>,
22 client: Client,
24}
25
26impl DocxTemplate {
27 pub fn new() -> Self {
28 DocxTemplate {
29 text_replacements: HashMap::new(),
30 image_replacements: HashMap::new(),
31 client: Client::builder()
32 .timeout(Duration::from_secs(10)) .build()
34 .unwrap(),
35 }
36 }
37
38 pub fn add_text_replacement(&mut self, placeholder: &str, value: &str) {
42 self.text_replacements
43 .insert(placeholder.to_string(), value.to_string());
44 }
45
46 pub fn add_image_file_replacement(
50 &mut self,
51 placeholder: &str,
52 image_path: Option<&str>,
53 ) -> Result<(), DocxError> {
54 match image_path {
55 None => {
56 self.image_replacements
58 .insert(placeholder.to_string(), None);
59 }
60 Some(data) => {
61 self.image_replacements
63 .insert(placeholder.to_string(), Some(DocxImage::new(data)?));
64 }
65 }
66
67 Ok(())
68 }
69
70 pub fn add_image_file_size_replacement(
76 &mut self,
77 placeholder: &str,
78 image_path: Option<&str>,
79 width: f32,
80 height: f32,
81 ) -> Result<(), DocxError> {
82 match image_path {
83 None => {
84 self.image_replacements
86 .insert(placeholder.to_string(), None);
87 }
88 Some(file_path) => {
89 let width_emu = (width * DOCX_EMU) as u64;
91 let height_emu = (height * DOCX_EMU) as u64;
92 self.image_replacements.insert(
94 placeholder.to_string(),
95 Some(DocxImage::new_size(file_path, width_emu, height_emu)?),
96 );
97 }
98 }
99
100 Ok(())
101 }
102
103 pub async fn add_image_url_replacement(
107 &mut self,
108 placeholder: &str,
109 image_url: Option<&str>,
110 ) -> Result<(), DocxError> {
111 match image_url {
112 None => {
113 self.image_replacements
115 .insert(placeholder.to_string(), None);
116 }
117 Some(url) => {
118 let (image_data, image_ext) = request_image_data(&self.client, url).await?;
120 self.image_replacements.insert(
122 placeholder.to_string(),
123 Some(DocxImage::new_image_data(url, image_data, &image_ext)?),
124 );
125 }
126 }
127
128 Ok(())
129 }
130
131 pub async fn add_image_url_size_replacement(
137 &mut self,
138 placeholder: &str,
139 image_url: Option<&str>,
140 width: f32,
141 height: f32,
142 ) -> Result<(), DocxError> {
143 match image_url {
144 None => {
145 self.image_replacements
147 .insert(placeholder.to_string(), None);
148 }
149 Some(url) => {
150 let (image_data, image_ext) = request_image_data(&self.client, url).await?;
152 let width_emu = (width * DOCX_EMU) as u64;
154 let height_emu = (height * DOCX_EMU) as u64;
155 self.image_replacements.insert(
157 placeholder.to_string(),
158 Some(DocxImage::new_image_data_size(
159 url, image_data, &image_ext, width_emu, height_emu,
160 )?),
161 );
162 }
163 }
164
165 Ok(())
166 }
167
168 pub fn process_template(
172 &self,
173 template_path: &str,
174 output_path: &str,
175 ) -> Result<(), DocxError> {
176 let template_file = File::open(template_path)?;
178 let mut archive = ZipArchive::new(template_file)?;
179
180 let output_file = File::create(output_path)?;
182 let mut zip_writer = ZipWriter::new(output_file);
183
184 for i in 0..archive.len() {
186 let mut file = archive.by_index(i)?;
187 let mut contents = Vec::new();
189 file.read_to_end(&mut contents)?;
191 match file.name() {
193 "word/document.xml" => {
194 contents = self.process_document_xml(&contents)?;
196 }
197 "word/_rels/document.xml.rels" => {
198 contents = self.process_rels_xml(&contents)?;
200 }
201 &_ => {}
202 }
203
204 let option = SimpleFileOptions::default()
206 .compression_method(file.compression())
207 .unix_permissions(file.unix_mode().unwrap_or(0o644));
208 zip_writer.start_file(file.name(), option)?;
210 zip_writer.write_all(&contents)?;
211 }
212
213 for replacement in self.image_replacements.values().flatten() {
215 let image_path = format!(
216 "word/media/image_{}.{}",
217 replacement.relation_id, replacement.image_ext,
218 );
219 zip_writer.start_file(&image_path, SimpleFileOptions::default())?;
221 zip_writer.write_all(&replacement.image_data)?;
222 }
223 zip_writer.finish()?;
225 Ok(())
226 }
227
228 fn process_element(&self, _element: &mut BytesStart) -> Result<(), DocxError> {
229 Ok(())
232 }
233
234 fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
237 let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
239 let mut reader = quick_xml::Reader::from_reader(contents);
241 reader.config_mut().trim_text(true);
242 let mut buf = Vec::new();
244 let mut current_placeholder = String::new();
246 loop {
248 match reader.read_event_into(&mut buf)? {
250 Event::Start(e) => {
251 let mut element = e.to_owned();
252 self.process_element(&mut element)?;
253 if current_placeholder.is_empty() {
255 xml_writer.write_event(Event::Start(element))?;
256 }
257 }
258 Event::Text(e) => {
259 let mut text = e.unescape()?.into_owned();
261 if text.starts_with(PREFIX_TAG) {
263 if text.ends_with(SUFFIX_TAG) {
265 self.process_text(&mut text);
267 if self.image_replacements.contains_key(&text) {
269 current_placeholder.push_str(&text);
270 } else {
271 xml_writer
272 .write_event(Event::Text(BytesText::new(text.as_str())))?;
273 }
274 } else {
275 current_placeholder.push_str(&text);
277 }
278 } else {
279 if current_placeholder.is_empty() {
281 xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
283 } else {
284 current_placeholder.push_str(text.as_str());
286 if current_placeholder.ends_with(PREFIX_TAG)
288 && current_placeholder.starts_with(SUFFIX_TAG)
289 {
290 self.process_text(&mut current_placeholder);
292 if !self.image_replacements.contains_key(¤t_placeholder) {
294 xml_writer.write_event(Event::Text(BytesText::new(
295 current_placeholder.as_str(),
296 )))?;
297 current_placeholder.clear();
299 }
300 }
301 }
302 }
303 }
304 Event::End(e) => {
305 if current_placeholder.is_empty() {
307 xml_writer.write_event(Event::End(e))?;
308 } else if current_placeholder.starts_with(PREFIX_TAG)
309 && current_placeholder.ends_with(SUFFIX_TAG)
310 {
311 if e.name().as_ref() == b"w:p" {
313 if let Some(Some(docx_image)) =
315 self.image_replacements.get(¤t_placeholder)
316 {
317 DocxTemplate::create_drawing_element(
319 &mut xml_writer,
320 &docx_image.relation_id,
321 docx_image.width,
322 docx_image.height,
323 )?;
324 }
325 current_placeholder.clear();
327 }
328 xml_writer.write_event(Event::End(e))?;
330 }
331 }
332 Event::Eof => break,
333 Event::Empty(e) => {
334 if current_placeholder.is_empty() {
336 xml_writer.write_event(Event::Empty(e))?;
337 }
338 }
339 e => {
340 xml_writer.write_event(e)?;
341 }
342 }
343 buf.clear();
344 }
345 Ok(xml_writer.into_inner().into_inner())
347 }
348
349 fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
350 let mut writer = Writer::new(Cursor::new(Vec::new()));
352 writer.write_event(Event::Decl(BytesDecl::new(
354 "1.0",
355 Some("UTF-8"),
356 Some("yes"),
357 )))?;
358
359 writer.write_event(Event::Start(
361 BytesStart::new("Relationships").with_attributes([(
362 "xmlns",
363 "http://schemas.openxmlformats.org/package/2006/relationships",
364 )]),
365 ))?;
366
367 let mut reader = quick_xml::Reader::from_reader(xml_data);
369 reader.config_mut().trim_text(true);
370 let mut buf = Vec::new();
371
372 loop {
373 match reader.read_event_into(&mut buf)? {
375 Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
377 writer.write_event(Event::Empty(e))?;
379 }
380 Event::Eof => break,
382 _ => {}
383 }
384 buf.clear();
386 }
387
388 for docx_image in self.image_replacements.values().flatten() {
390 let image_path = format!(
392 "media/image_{}.{}",
393 docx_image.relation_id, docx_image.image_ext
394 );
395 let relationship = BytesStart::new("Relationship").with_attributes([
397 ("Id", docx_image.relation_id.as_str()),
398 (
399 "Type",
400 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
401 ),
402 ("Target", &image_path),
403 ]);
404 writer.write_event(Event::Empty(relationship))?;
406 }
407
408 writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
410 Ok(writer.into_inner().into_inner())
412 }
413
414 fn process_text(&self, text: &mut String) {
416 for (placeholder, value) in &self.text_replacements {
417 *text = text.replace(placeholder, value);
418 }
419 }
420
421 fn create_drawing_element<T>(
422 writer: &mut Writer<T>,
423 relation_id: &str,
424 width: u64,
425 height: u64,
426 ) -> Result<(), DocxError>
427 where
428 T: Write,
429 {
430 let drawing = format!(
431 r#"
432 <w:drawing>
433 <wp:inline distT="0" distB="0" distL="0" distR="0">
434 <wp:extent cx="{}" cy="{}"/>
435 <wp:docPr id="1" name="Picture 1" descr="Generated image"/>
436 <wp:cNvGraphicFramePr>
437 <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
438 </wp:cNvGraphicFramePr>
439 <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
440 <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
441 <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
442 <pic:nvPicPr>
443 <pic:cNvPr id="0" name="Picture 1" descr="Generated image"/>
444 <pic:cNvPicPr><a:picLocks noChangeAspect="1"/></pic:cNvPicPr>
445 </pic:nvPicPr>
446 <pic:blipFill>
447 <a:blip r:embed="{}"/>
448 <a:stretch>
449 <a:fillRect/>
450 </a:stretch>
451 </pic:blipFill>
452 <pic:spPr>
453 <a:xfrm>
454 <a:off x="0" y="0"/>
455 <a:ext cx="{}" cy="{}"/>
456 </a:xfrm>
457 <a:prstGeom prst="rect">
458 <a:avLst/>
459 </a:prstGeom>
460 </pic:spPr>
461 </pic:pic>
462 </a:graphicData>
463 </a:graphic>
464 </wp:inline>
465 </w:drawing>
466 "#,
467 width, height, relation_id, width, height,
468 );
469
470 let mut reader = quick_xml::Reader::from_str(&drawing);
471 reader.config_mut().trim_text(true);
472 let mut buf = Vec::new();
473
474 loop {
475 match reader.read_event_into(&mut buf)? {
476 Event::Eof => break,
477 e => {
478 writer.write_event(e)?;
479 }
480 }
481 }
482 Ok(())
483 }
484}
485
486impl Default for DocxTemplate {
487 fn default() -> Self {
488 Self::new()
489 }
490}