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 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 writer_image(&mut zip_writer, replacement)?;
277 }
278 }
279 zip_writer.finish()?;
281 Ok(())
282 }
283
284 fn process_element(&self, _element: &mut BytesStart) -> Result<(), DocxError> {
285 let tag = String::from_utf8_lossy(_element.name().as_ref()).to_string();
286 debug!("{:?}", tag);
287 Ok(())
288 }
289
290 fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
293 let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
295 let mut reader = quick_xml::Reader::from_reader(contents);
297 let mut buf = Vec::new();
300 let mut current_placeholder = String::new();
302 loop {
304 match reader.read_event_into(&mut buf)? {
306 Event::Start(e) => {
307 let mut element = e.to_owned();
308 self.process_element(&mut element)?;
309 if current_placeholder.is_empty() {
311 xml_writer.write_event(Event::Start(element))?;
312 }
313 }
314 Event::Text(e) => {
315 let mut text = e.unescape()?.into_owned();
317 if text.contains(PREFIX_TAG) {
319 if text.contains(SUFFIX_TAG) {
321 self.process_text(&mut text);
323 if self.image_replacements.contains_key(&text) {
325 current_placeholder.push_str(&text);
326 } else {
327 xml_writer
328 .write_event(Event::Text(BytesText::new(text.as_str())))?;
329 }
330 } else {
331 current_placeholder.push_str(&text);
333 }
334 } else {
335 if current_placeholder.is_empty() {
337 xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
339 } else {
340 current_placeholder.push_str(text.as_str());
342 if current_placeholder.contains(PREFIX_TAG)
344 && current_placeholder.contains(SUFFIX_TAG)
345 {
346 self.process_text(&mut current_placeholder);
348 if !self.image_replacements.contains_key(¤t_placeholder) {
350 xml_writer.write_event(Event::Text(BytesText::new(
351 current_placeholder.as_str(),
352 )))?;
353 current_placeholder.clear();
355 }
356 }
357 }
358 }
359 }
360 Event::End(e) => {
361 if current_placeholder.is_empty() {
363 xml_writer.write_event(Event::End(e))?;
364 } else if current_placeholder.contains(PREFIX_TAG)
365 && current_placeholder.contains(SUFFIX_TAG)
366 {
367 if e.name().as_ref() == WORD_PARAGRAPH_TAG {
369 if let Some(Some(docx_image)) =
371 self.image_replacements.get(¤t_placeholder)
372 {
373 create_drawing_element(
375 &mut xml_writer,
376 &docx_image.relation_id,
377 docx_image.width,
378 docx_image.height,
379 )?;
380 }
381 current_placeholder.clear();
383 }
384 xml_writer.write_event(Event::End(e))?;
386 }
387 }
388 Event::Eof => break,
389 Event::Empty(e) => {
390 if current_placeholder.is_empty() {
392 xml_writer.write_event(Event::Empty(e))?;
393 }
394 }
395 e => {
396 xml_writer.write_event(e)?;
397 }
398 }
399 buf.clear();
400 }
401 Ok(xml_writer.into_inner().into_inner())
403 }
404
405 fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
406 let mut writer = Writer::new(Cursor::new(Vec::new()));
408 writer.write_event(Event::Decl(BytesDecl::new(
410 "1.0",
411 Some("UTF-8"),
412 Some("yes"),
413 )))?;
414
415 writer.write_event(Event::Start(
417 BytesStart::new("Relationships").with_attributes([(
418 "xmlns",
419 "http://schemas.openxmlformats.org/package/2006/relationships",
420 )]),
421 ))?;
422
423 let mut reader = quick_xml::Reader::from_reader(xml_data);
425 reader.config_mut().trim_text(true);
426 let mut buf = Vec::new();
427
428 loop {
429 match reader.read_event_into(&mut buf)? {
431 Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
433 writer.write_event(Event::Empty(e))?;
435 }
436 Event::Eof => break,
438 _ => {}
439 }
440 buf.clear();
442 }
443
444 for placeholder in self.images_map.values() {
446 if let Some(Some(docx_image)) = self.image_replacements.get(placeholder) {
447 let image_path = format!(
449 "media/image_{}.{}",
450 docx_image.relation_id, docx_image.image_ext
451 );
452 let relationship = BytesStart::new("Relationship").with_attributes([
454 ("Id", docx_image.relation_id.as_str()),
455 (
456 "Type",
457 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
458 ),
459 ("Target", &image_path),
460 ]);
461 writer.write_event(Event::Empty(relationship))?;
463 }
464 }
465
466 writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
468 Ok(writer.into_inner().into_inner())
470 }
471
472 fn process_text(&self, text: &mut String) {
474 for (placeholder, value) in &self.text_replacements {
475 *text = text.replace(placeholder, value);
476 }
477 }
478}
479
480impl Default for DocxTemplate {
481 fn default() -> Self {
482 Self::new()
483 }
484}
485
486fn writer_image(
490 zip_writer: &mut ZipWriter<File>,
491 replacement: &DocxImage,
492) -> Result<(), DocxError> {
493 let image_path = format!(
494 "{}{}.{}",
495 WORD_MEDIA_IMAGE, replacement.relation_id, replacement.image_ext,
496 );
497 zip_writer.start_file(&image_path, SimpleFileOptions::default())?;
499 zip_writer.write_all(&replacement.image_data)?;
500 Ok(())
501}
502
503pub fn writer_file(
504 zip_writer: &mut ZipWriter<File>,
505 file: &ZipFile<File>,
506 contents: &[u8],
507) -> Result<(), DocxError> {
508 let option = SimpleFileOptions::default()
510 .compression_method(file.compression())
511 .unix_permissions(file.unix_mode().unwrap_or(0o644));
512 zip_writer.start_file(file.name(), option)?;
514 zip_writer.write_all(contents)?;
515
516 Ok(())
517}