1use crate::docx::template::create_drawing_element;
2use crate::docx::word::*;
3use crate::error::DocxError;
4use crate::image::{DOCX_EMU, DocxImage};
5use crate::request::request_image_data;
6use log::debug;
7use quick_xml::Writer;
8use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
9use reqwest::Client;
10use std::collections::HashMap;
11use std::fs::File;
12use std::io::{Cursor, Read, Write};
13use std::time::Duration;
14use zip::read::ZipFile;
15use zip::write::SimpleFileOptions;
16use zip::{ZipArchive, ZipWriter};
17
18mod template;
19mod word;
20
21static PREFIX_TAG: &str = "{{";
22static SUFFIX_TAG: &str = "}}";
23
24pub struct DocxTemplate {
25 text_replacements: HashMap<String, String>,
27 image_replacements: HashMap<String, Option<DocxImage>>,
29 images_map: HashMap<String, String>,
31 client: Client,
33}
34
35impl DocxTemplate {
36 pub fn new() -> Self {
37 DocxTemplate {
38 text_replacements: HashMap::new(),
39 image_replacements: HashMap::new(),
40 images_map: HashMap::new(),
41 client: Client::builder()
42 .timeout(Duration::from_secs(10)) .build()
44 .unwrap(),
45 }
46 }
47
48 pub fn add_text_replacement(&mut self, placeholder: &str, value: &str) {
52 self.text_replacements
53 .insert(placeholder.to_string(), value.to_string());
54 }
55
56 pub fn add_image_file_replacement(
60 &mut self,
61 placeholder: &str,
62 image_path: Option<&str>,
63 ) -> Result<(), DocxError> {
64 match image_path {
65 None => {
66 self.image_replacements
68 .insert(placeholder.to_string(), None);
69 }
70 Some(file_path) => {
71 if self.images_map.contains_key(file_path) {
73 let old_placeholder = &self.images_map[file_path];
74 let image_option = &self.image_replacements[old_placeholder];
75 self.image_replacements
77 .insert(placeholder.to_string(), image_option.clone());
78 } else {
79 self.images_map
81 .insert(file_path.to_string(), placeholder.to_string());
82 self.image_replacements
84 .insert(placeholder.to_string(), Some(DocxImage::new(file_path)?));
85 }
86 }
87 }
88
89 Ok(())
90 }
91
92 pub fn add_image_file_size_replacement(
98 &mut self,
99 placeholder: &str,
100 image_path: Option<&str>,
101 width: f32,
102 height: f32,
103 ) -> Result<(), DocxError> {
104 match image_path {
105 None => {
106 self.image_replacements
108 .insert(placeholder.to_string(), None);
109 }
110 Some(file_path) => {
111 let width_emu = (width * DOCX_EMU) as u64;
113 let height_emu = (height * DOCX_EMU) as u64;
114 if self.images_map.contains_key(file_path) {
116 let old_placeholder = &self.images_map[file_path];
117 let image_option = &self.image_replacements[old_placeholder];
118
119 if let Some(image) = image_option {
120 let docx_image =
121 DocxImage::clone_image_reset_size(image, width_emu, height_emu);
122 self.image_replacements
124 .insert(placeholder.to_string(), Some(docx_image));
125 }
126 } else {
127 self.images_map
129 .insert(file_path.to_string(), placeholder.to_string());
130 self.image_replacements.insert(
132 placeholder.to_string(),
133 Some(DocxImage::new_size(file_path, width_emu, height_emu)?),
134 );
135 }
136 }
137 }
138
139 Ok(())
140 }
141
142 pub async fn add_image_url_replacement(
146 &mut self,
147 placeholder: &str,
148 image_url: Option<&str>,
149 ) -> Result<(), DocxError> {
150 match image_url {
151 None => {
152 self.image_replacements
154 .insert(placeholder.to_string(), None);
155 }
156 Some(url) => {
157 if self.images_map.contains_key(url) {
159 let old_placeholder = &self.images_map[url];
160 let image_option = &self.image_replacements[old_placeholder];
161 self.image_replacements
163 .insert(placeholder.to_string(), image_option.clone());
164 } else {
165 self.images_map
167 .insert(url.to_string(), placeholder.to_string());
168 let (image_data, image_ext) = request_image_data(&self.client, url).await?;
170 self.image_replacements.insert(
172 placeholder.to_string(),
173 Some(DocxImage::new_image_data(url, image_data, &image_ext)?),
174 );
175 }
176 }
177 }
178
179 Ok(())
180 }
181
182 pub async fn add_image_url_size_replacement(
188 &mut self,
189 placeholder: &str,
190 image_url: Option<&str>,
191 width: f32,
192 height: f32,
193 ) -> Result<(), DocxError> {
194 match image_url {
195 None => {
196 self.image_replacements
198 .insert(placeholder.to_string(), None);
199 }
200 Some(url) => {
201 let width_emu = (width * DOCX_EMU) as u64;
203 let height_emu = (height * DOCX_EMU) as u64;
204 if self.images_map.contains_key(url) {
206 let old_placeholder = &self.images_map[url];
207 let image_option = &self.image_replacements[old_placeholder];
208 if let Some(image) = image_option {
209 let docx_image =
210 DocxImage::clone_image_reset_size(image, width_emu, height_emu);
211 self.image_replacements
213 .insert(placeholder.to_string(), Some(docx_image));
214 }
215 } else {
216 self.images_map
217 .insert(url.to_string(), placeholder.to_string());
218 let (image_data, image_ext) = request_image_data(&self.client, url).await?;
220 self.image_replacements.insert(
222 placeholder.to_string(),
223 Some(DocxImage::new_image_data_size(
224 url, image_data, &image_ext, width_emu, height_emu,
225 )?),
226 );
227 }
228 }
229 }
230
231 Ok(())
232 }
233
234 pub fn process_template(
238 &self,
239 template_path: &str,
240 output_path: &str,
241 ) -> Result<(), DocxError> {
242 let template_file = File::open(template_path)?;
244 let mut archive = ZipArchive::new(template_file)?;
245
246 let output_file = File::create(output_path)?;
248 let mut zip_writer = ZipWriter::new(output_file);
249
250 for i in 0..archive.len() {
252 let mut file = archive.by_index(i)?;
253 let mut contents = Vec::new();
255 file.read_to_end(&mut contents)?;
257 match file.name() {
259 x if x == WORD_DOCUMENT => {
260 contents = self.process_document_xml(&contents)?;
262 }
263 x if x == WORD_RELS_DOCUMENT => {
264 contents = self.process_rels_xml(&contents)?;
266 }
267 &_ => {}
268 }
269 self.writer_file(&mut zip_writer, &file, &contents)?
271 }
272
273 for replacement in self.images_map.values() {
275 if let Some(Some(replacement)) = self.image_replacements.get(replacement) {
276 self.writer_image(&mut zip_writer, replacement)?;
277 }
278 }
279 zip_writer.finish()?;
281 Ok(())
282 }
283
284 fn writer_image(
288 &self,
289 zip_writer: &mut ZipWriter<File>,
290 replacement: &DocxImage,
291 ) -> Result<(), DocxError> {
292 let image_path = format!(
293 "{}{}.{}",
294 WORD_MEDIA_IMAGE, replacement.relation_id, replacement.image_ext,
295 );
296 zip_writer.start_file(&image_path, SimpleFileOptions::default())?;
298 zip_writer.write_all(&replacement.image_data)?;
299 Ok(())
300 }
301
302 fn writer_file(
303 &self,
304 zip_writer: &mut ZipWriter<File>,
305 file: &ZipFile<File>,
306 contents: &[u8],
307 ) -> Result<(), DocxError> {
308 let option = SimpleFileOptions::default()
310 .compression_method(file.compression())
311 .unix_permissions(file.unix_mode().unwrap_or(0o644));
312 zip_writer.start_file(file.name(), option)?;
314 zip_writer.write_all(contents)?;
315
316 Ok(())
317 }
318
319 fn process_element(&self, _element: &mut BytesStart) -> Result<(), DocxError> {
320 let tag = String::from_utf8_lossy(_element.name().as_ref()).to_string();
321 debug!("{:?}", tag);
322 Ok(())
323 }
324
325 fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
328 let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
330 let mut reader = quick_xml::Reader::from_reader(contents);
332 reader.config_mut().trim_text(true);
333 let mut buf = Vec::new();
335 let mut current_placeholder = String::new();
337 loop {
339 match reader.read_event_into(&mut buf)? {
341 Event::Start(e) => {
342 let mut element = e.to_owned();
343 self.process_element(&mut element)?;
344 if current_placeholder.is_empty() {
346 xml_writer.write_event(Event::Start(element))?;
347 }
348 }
349 Event::Text(e) => {
350 let mut text = e.unescape()?.into_owned();
352 if text.contains(PREFIX_TAG) {
354 if text.contains(SUFFIX_TAG) {
356 self.process_text(&mut text);
358 if self.image_replacements.contains_key(&text) {
360 current_placeholder.push_str(&text);
361 } else {
362 xml_writer
363 .write_event(Event::Text(BytesText::new(text.as_str())))?;
364 }
365 } else {
366 current_placeholder.push_str(&text);
368 }
369 } else {
370 if current_placeholder.is_empty() {
372 xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
374 } else {
375 current_placeholder.push_str(text.as_str());
377 if current_placeholder.contains(PREFIX_TAG)
379 && current_placeholder.contains(SUFFIX_TAG)
380 {
381 self.process_text(&mut current_placeholder);
383 if !self.image_replacements.contains_key(¤t_placeholder) {
385 xml_writer.write_event(Event::Text(BytesText::new(
386 current_placeholder.as_str(),
387 )))?;
388 current_placeholder.clear();
390 }
391 }
392 }
393 }
394 }
395 Event::End(e) => {
396 if current_placeholder.is_empty() {
398 xml_writer.write_event(Event::End(e))?;
399 } else if current_placeholder.contains(PREFIX_TAG)
400 && current_placeholder.contains(SUFFIX_TAG)
401 {
402 if e.name().as_ref() == WORD_PARAGRAPH_TAG {
404 if let Some(Some(docx_image)) =
406 self.image_replacements.get(¤t_placeholder)
407 {
408 create_drawing_element(
410 &mut xml_writer,
411 &docx_image.relation_id,
412 docx_image.width,
413 docx_image.height,
414 )?;
415 }
416 current_placeholder.clear();
418 }
419 xml_writer.write_event(Event::End(e))?;
421 }
422 }
423 Event::Eof => break,
424 Event::Empty(e) => {
425 if current_placeholder.is_empty() {
427 xml_writer.write_event(Event::Empty(e))?;
428 }
429 }
430 e => {
431 xml_writer.write_event(e)?;
432 }
433 }
434 buf.clear();
435 }
436 Ok(xml_writer.into_inner().into_inner())
438 }
439
440 fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
441 let mut writer = Writer::new(Cursor::new(Vec::new()));
443 writer.write_event(Event::Decl(BytesDecl::new(
445 "1.0",
446 Some("UTF-8"),
447 Some("yes"),
448 )))?;
449
450 writer.write_event(Event::Start(
452 BytesStart::new("Relationships").with_attributes([(
453 "xmlns",
454 "http://schemas.openxmlformats.org/package/2006/relationships",
455 )]),
456 ))?;
457
458 let mut reader = quick_xml::Reader::from_reader(xml_data);
460 reader.config_mut().trim_text(true);
461 let mut buf = Vec::new();
462
463 loop {
464 match reader.read_event_into(&mut buf)? {
466 Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
468 writer.write_event(Event::Empty(e))?;
470 }
471 Event::Eof => break,
473 _ => {}
474 }
475 buf.clear();
477 }
478
479 for placeholder in self.images_map.values() {
481 if let Some(Some(docx_image)) = self.image_replacements.get(placeholder) {
482 let image_path = format!(
484 "media/image_{}.{}",
485 docx_image.relation_id, docx_image.image_ext
486 );
487 let relationship = BytesStart::new("Relationship").with_attributes([
489 ("Id", docx_image.relation_id.as_str()),
490 (
491 "Type",
492 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
493 ),
494 ("Target", &image_path),
495 ]);
496 writer.write_event(Event::Empty(relationship))?;
498 }
499 }
500
501 writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
503 Ok(writer.into_inner().into_inner())
505 }
506
507 fn process_text(&self, text: &mut String) {
509 for (placeholder, value) in &self.text_replacements {
510 *text = text.replace(placeholder, value);
511 }
512 }
513}
514
515impl Default for DocxTemplate {
516 fn default() -> Self {
517 Self::new()
518 }
519}