docx_template/
docx.rs

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    // 待替换的字符串
26    text_replacements: HashMap<String, String>,
27    // 待替换的图片
28    image_replacements: HashMap<String, Option<DocxImage>>,
29    // 已经添加的图片路径
30    images_map: HashMap<String, String>,
31    // 请求对象
32    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)) // 设置超时
43                .build()
44                .unwrap(),
45        }
46    }
47
48    /// 添加待替换的字符以及对应的值
49    /// @param placeholder 待替换的字符串
50    /// @param value 替换的值
51    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    /// 添加待替换的图片
57    /// @param placeholder 待替换的字符串
58    /// @param image_path 图片路径
59    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                // 插入图片到属性中
67                self.image_replacements
68                    .insert(placeholder.to_string(), None);
69            }
70            Some(file_path) => {
71                // 判断是否添加过该图片
72                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                    // 插入图片到属性中
76                    self.image_replacements
77                        .insert(placeholder.to_string(), image_option.clone());
78                } else {
79                    // 收集添加的图片路径
80                    self.images_map
81                        .insert(file_path.to_string(), placeholder.to_string());
82                    // 插入图片到属性中
83                    self.image_replacements
84                        .insert(placeholder.to_string(), Some(DocxImage::new(file_path)?));
85                }
86            }
87        }
88
89        Ok(())
90    }
91
92    /// 添加待替换的图片
93    /// @param placeholder 替换的字符串
94    /// @param image_path 图片路径
95    /// @param width 图片的宽度(厘米)
96    /// @param height 图片的高度(厘米)
97    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                // 插入图片到属性中
107                self.image_replacements
108                    .insert(placeholder.to_string(), None);
109            }
110            Some(file_path) => {
111                // 将厘米单位换算成emu
112                let width_emu = (width * DOCX_EMU) as u64;
113                let height_emu = (height * DOCX_EMU) as u64;
114                // 判断是否添加过该图片
115                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                        // 插入图片到属性中
123                        self.image_replacements
124                            .insert(placeholder.to_string(), Some(docx_image));
125                    }
126                } else {
127                    // 收集添加的图片路径
128                    self.images_map
129                        .insert(file_path.to_string(), placeholder.to_string());
130                    // 插入图片到属性中
131                    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    /// 添加待替换的图片,替换的图片大小默认6.09*5.9厘米
143    /// @param placeholder 替换的字符串
144    /// @param image_url 图片路径
145    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                // 插入图片到属性中
153                self.image_replacements
154                    .insert(placeholder.to_string(), None);
155            }
156            Some(url) => {
157                // 判断是否添加过该图片
158                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                    // 插入图片到属性中
162                    self.image_replacements
163                        .insert(placeholder.to_string(), image_option.clone());
164                } else {
165                    // 收集添加的图片路径
166                    self.images_map
167                        .insert(url.to_string(), placeholder.to_string());
168                    // 发送请求
169                    let (image_data, image_ext) = request_image_data(&self.client, url).await?;
170                    // 插入图片到属性中
171                    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    /// 添加待替换的图片
183    /// @param placeholder 替换的字符串
184    /// @param image_url 图片路径
185    /// @param width 图片的宽度(厘米)
186    /// @param height 图片的高度(厘米)
187    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                // 插入图片到属性中
197                self.image_replacements
198                    .insert(placeholder.to_string(), None);
199            }
200            Some(url) => {
201                // 将厘米单位换算成emu
202                let width_emu = (width * DOCX_EMU) as u64;
203                let height_emu = (height * DOCX_EMU) as u64;
204                // 判断是否添加过该图片
205                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                        // 插入图片到属性中
212                        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                    // 发送请求
219                    let (image_data, image_ext) = request_image_data(&self.client, url).await?;
220                    // 插入图片到属性中
221                    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    /// 处理模板
235    /// @param template_path 模板路径
236    /// @param output_path 输出路径
237    pub fn process_template(
238        &self,
239        template_path: &str,
240        output_path: &str,
241    ) -> Result<(), DocxError> {
242        // 1. 打开模板文件
243        let template_file = File::open(template_path)?;
244        let mut archive = ZipArchive::new(template_file)?;
245
246        // 2. 创建输出文件
247        let output_file = File::create(output_path)?;
248        let mut zip_writer = ZipWriter::new(output_file);
249
250        // 3. 遍历ZIP中的文件
251        for i in 0..archive.len() {
252            let mut file = archive.by_index(i)?;
253            // 文件内容
254            let mut contents = Vec::new();
255            // 读取文件内容到数组中
256            file.read_to_end(&mut contents)?;
257            // 匹配文件类型
258            match file.name() {
259                x if x == WORD_DOCUMENT => {
260                    // 处理文档主内容,替换模板内容
261                    contents = self.process_document_xml(&contents)?;
262                }
263                x if x == WORD_RELS_DOCUMENT => {
264                    // 处理关系文件
265                    contents = self.process_rels_xml(&contents)?;
266                }
267                &_ => {}
268            }
269            // 写入新文件
270            self.writer_file(&mut zip_writer, &file, &contents)?
271        }
272
273        // 4. 添加新的图片文件
274        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        // 将内容写入压缩文件(docx)
280        zip_writer.finish()?;
281        Ok(())
282    }
283
284    /// 写入图片  
285    /// @param zip_writer 写入对象  
286    /// @param replacement 图片对象  
287    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        // 写入图片到word压缩文件中
297        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        // 写入新文件
309        let option = SimpleFileOptions::default()
310            .compression_method(file.compression())
311            .unix_permissions(file.unix_mode().unwrap_or(0o644));
312        // 写入内容
313        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    /// 处理文件内容
326    /// @param contents 文件内容数组
327    fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
328        // 创建xml写对象
329        let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
330        // 读取xml文件的内容
331        let mut reader = quick_xml::Reader::from_reader(contents);
332        reader.config_mut().trim_text(true);
333        // 缓存数组
334        let mut buf = Vec::new();
335        // 图片对应的字符串占位符
336        let mut current_placeholder = String::new();
337        // 循环读取xml数据
338        loop {
339            // 读取数据
340            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                    // 如果为空,写入标签头
345                    if current_placeholder.is_empty() {
346                        xml_writer.write_event(Event::Start(element))?;
347                    }
348                }
349                Event::Text(e) => {
350                    // 读取标签的内容
351                    let mut text = e.unescape()?.into_owned();
352                    // 判断是否有替换字符串开头内容"{{"
353                    if text.contains(PREFIX_TAG) {
354                        // 判断是否包含结束字符串}}
355                        if text.contains(SUFFIX_TAG) {
356                            // 1、替换文本占位符操作
357                            self.process_text(&mut text);
358                            // 2、替换图片占位符操作
359                            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                            // 将字符串保存
367                            current_placeholder.push_str(&text);
368                        }
369                    } else {
370                        // 判断current_placeholder字符串是否有内容
371                        if current_placeholder.is_empty() {
372                            // 将原有字符串写入文档
373                            xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
374                        } else {
375                            // 将字符串写入
376                            current_placeholder.push_str(text.as_str());
377                            // 判断是否有结束字符串}}
378                            if current_placeholder.contains(PREFIX_TAG)
379                                && current_placeholder.contains(SUFFIX_TAG)
380                            {
381                                // 1、替换文本占位符操作
382                                self.process_text(&mut current_placeholder);
383                                // 2、如果不包含写入数据
384                                if !self.image_replacements.contains_key(&current_placeholder) {
385                                    xml_writer.write_event(Event::Text(BytesText::new(
386                                        current_placeholder.as_str(),
387                                    )))?;
388                                    // 清理数据
389                                    current_placeholder.clear();
390                                }
391                            }
392                        }
393                    }
394                }
395                Event::End(e) => {
396                    // 判断是否为空,为空,直接添加结尾标签
397                    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                        // 判断是否为段落
403                        if e.name().as_ref() == WORD_PARAGRAPH_TAG {
404                            // 判断是否为完整替换字符串
405                            if let Some(Some(docx_image)) =
406                                self.image_replacements.get(&current_placeholder)
407                            {
408                                // 替换占位符为图片
409                                create_drawing_element(
410                                    &mut xml_writer,
411                                    &docx_image.relation_id,
412                                    docx_image.width,
413                                    docx_image.height,
414                                )?;
415                            }
416                            // 清除字符串
417                            current_placeholder.clear();
418                        }
419                        // 写入结尾标签
420                        xml_writer.write_event(Event::End(e))?;
421                    }
422                }
423                Event::Eof => break,
424                Event::Empty(e) => {
425                    // 如果为空写入文档
426                    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        // 返回文件数组
437        Ok(xml_writer.into_inner().into_inner())
438    }
439
440    fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
441        // 创建xml写对象
442        let mut writer = Writer::new(Cursor::new(Vec::new()));
443        // 写入xml标签头
444        writer.write_event(Event::Decl(BytesDecl::new(
445            "1.0",
446            Some("UTF-8"),
447            Some("yes"),
448        )))?;
449
450        // 写入XML根元素
451        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        // 读取原始数据
459        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            // 读取关系文件
465            match reader.read_event_into(&mut buf)? {
466                // 判断关系文件内容是否为关联标签
467                Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
468                    // 写入关系标签内容
469                    writer.write_event(Event::Empty(e))?;
470                }
471                // 文件读取完毕
472                Event::Eof => break,
473                _ => {}
474            }
475            // 清理内容
476            buf.clear();
477        }
478
479        // 添加新的图片关系
480        for placeholder in self.images_map.values() {
481            if let Some(Some(docx_image)) = self.image_replacements.get(placeholder) {
482                // 创建图片路径
483                let image_path = format!(
484                    "media/image_{}.{}",
485                    docx_image.relation_id, docx_image.image_ext
486                );
487                // 创建图片关系标签
488                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                // 写入关系标签数据
497                writer.write_event(Event::Empty(relationship))?;
498            }
499        }
500
501        // 结束根元素
502        writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
503        // 输出关系文件内容
504        Ok(writer.into_inner().into_inner())
505    }
506
507    // 替换模板属性
508    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}