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(100)) // 设置超时
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            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                writer_image(&mut zip_writer, replacement)?;
277            }
278        }
279        // 将内容写入压缩文件(docx)
280        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    /// 处理文件内容
291    /// @param contents 文件内容数组
292    fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
293        // 创建xml写对象
294        let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
295        // 读取xml文件的内容
296        let mut reader = quick_xml::Reader::from_reader(contents);
297        // reader.config_mut().trim_text(true);
298        // 缓存数组
299        let mut buf = Vec::new();
300        // 图片对应的字符串占位符
301        let mut current_placeholder = String::new();
302        // 循环读取xml数据
303        loop {
304            // 读取数据
305            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                    // 如果为空,写入标签头
310                    if current_placeholder.is_empty() {
311                        xml_writer.write_event(Event::Start(element))?;
312                    }
313                }
314                Event::Text(e) => {
315                    // 读取标签的内容
316                    let mut text = e.unescape()?.into_owned();
317                    // 判断是否有替换字符串开头内容"{{"
318                    if text.contains(PREFIX_TAG) {
319                        // 判断是否包含结束字符串}}
320                        if text.contains(SUFFIX_TAG) {
321                            // 1、替换文本占位符操作
322                            self.process_text(&mut text);
323                            // 2、替换图片占位符操作
324                            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                            // 将字符串保存
332                            current_placeholder.push_str(&text);
333                        }
334                    } else {
335                        // 判断current_placeholder字符串是否有内容
336                        if current_placeholder.is_empty() {
337                            // 将原有字符串写入文档
338                            xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
339                        } else {
340                            // 将字符串写入
341                            current_placeholder.push_str(text.as_str());
342                            // 判断是否有结束字符串}}
343                            if current_placeholder.contains(PREFIX_TAG)
344                                && current_placeholder.contains(SUFFIX_TAG)
345                            {
346                                // 1、替换文本占位符操作
347                                self.process_text(&mut current_placeholder);
348                                // 2、如果不包含写入数据
349                                if !self.image_replacements.contains_key(&current_placeholder) {
350                                    xml_writer.write_event(Event::Text(BytesText::new(
351                                        current_placeholder.as_str(),
352                                    )))?;
353                                    // 清理数据
354                                    current_placeholder.clear();
355                                }
356                            }
357                        }
358                    }
359                }
360                Event::End(e) => {
361                    // 判断是否为空,为空,直接添加结尾标签
362                    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                        // 判断是否为段落
368                        if e.name().as_ref() == WORD_PARAGRAPH_TAG {
369                            // 判断是否为完整替换字符串
370                            if let Some(Some(docx_image)) =
371                                self.image_replacements.get(&current_placeholder)
372                            {
373                                // 替换占位符为图片
374                                create_drawing_element(
375                                    &mut xml_writer,
376                                    &docx_image.relation_id,
377                                    docx_image.width,
378                                    docx_image.height,
379                                )?;
380                            }
381                            // 清除字符串
382                            current_placeholder.clear();
383                        }
384                        // 写入结尾标签
385                        xml_writer.write_event(Event::End(e))?;
386                    }
387                }
388                Event::Eof => break,
389                Event::Empty(e) => {
390                    // 如果为空写入文档
391                    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        // 返回文件数组
402        Ok(xml_writer.into_inner().into_inner())
403    }
404
405    fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
406        // 创建xml写对象
407        let mut writer = Writer::new(Cursor::new(Vec::new()));
408        // 写入xml标签头
409        writer.write_event(Event::Decl(BytesDecl::new(
410            "1.0",
411            Some("UTF-8"),
412            Some("yes"),
413        )))?;
414
415        // 写入XML根元素
416        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        // 读取原始数据
424        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            // 读取关系文件
430            match reader.read_event_into(&mut buf)? {
431                // 判断关系文件内容是否为关联标签
432                Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
433                    // 写入关系标签内容
434                    writer.write_event(Event::Empty(e))?;
435                }
436                // 文件读取完毕
437                Event::Eof => break,
438                _ => {}
439            }
440            // 清理内容
441            buf.clear();
442        }
443
444        // 添加新的图片关系
445        for placeholder in self.images_map.values() {
446            if let Some(Some(docx_image)) = self.image_replacements.get(placeholder) {
447                // 创建图片路径
448                let image_path = format!(
449                    "media/image_{}.{}",
450                    docx_image.relation_id, docx_image.image_ext
451                );
452                // 创建图片关系标签
453                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                // 写入关系标签数据
462                writer.write_event(Event::Empty(relationship))?;
463            }
464        }
465
466        // 结束根元素
467        writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
468        // 输出关系文件内容
469        Ok(writer.into_inner().into_inner())
470    }
471
472    // 替换模板属性
473    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
486/// 写入图片  
487/// @param zip_writer 写入对象  
488/// @param replacement 图片对象  
489fn 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    // 写入图片到word压缩文件中
498    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    // 写入新文件
509    let option = SimpleFileOptions::default()
510        .compression_method(file.compression())
511        .unix_permissions(file.unix_mode().unwrap_or(0o644));
512    // 写入内容
513    zip_writer.start_file(file.name(), option)?;
514    zip_writer.write_all(contents)?;
515
516    Ok(())
517}