docx_template/
docx.rs

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    // 待替换的字符串
19    text_replacements: HashMap<String, String>,
20    // 待替换的图片
21    image_replacements: HashMap<String, Option<DocxImage>>,
22    // 请求对象
23    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)) // 设置超时
33                .build()
34                .unwrap(),
35        }
36    }
37
38    /// 添加待替换的字符以及对应的值
39    /// @param placeholder 待替换的字符串
40    /// @param value 替换的值
41    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    /// 添加待替换的图片
47    /// @param placeholder 待替换的字符串
48    /// @param image_path 图片路径
49    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                // 插入图片到属性中
57                self.image_replacements
58                    .insert(placeholder.to_string(), None);
59            }
60            Some(data) => {
61                // 插入图片到属性中
62                self.image_replacements
63                    .insert(placeholder.to_string(), Some(DocxImage::new(data)?));
64            }
65        }
66
67        Ok(())
68    }
69
70    /// 添加待替换的图片
71    /// @param placeholder 替换的字符串
72    /// @param image_path 图片路径
73    /// @param width 图片的宽度(厘米)
74    /// @param height 图片的高度(厘米)
75    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                // 插入图片到属性中
85                self.image_replacements
86                    .insert(placeholder.to_string(), None);
87            }
88            Some(file_path) => {
89                // 将厘米单位换算成emu
90                let width_emu = (width * DOCX_EMU) as u64;
91                let height_emu = (height * DOCX_EMU) as u64;
92                // 插入图片到属性中
93                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    /// 添加待替换的图片,替换的图片大小默认6.09*5.9厘米
104    /// @param placeholder 替换的字符串
105    /// @param image_url 图片路径
106    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                // 插入图片到属性中
114                self.image_replacements
115                    .insert(placeholder.to_string(), None);
116            }
117            Some(url) => {
118                // 发送请求
119                let (image_data, image_ext) = request_image_data(&self.client, url).await?;
120                // 插入图片到属性中
121                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    /// 添加待替换的图片
132    /// @param placeholder 替换的字符串
133    /// @param image_url 图片路径
134    /// @param width 图片的宽度(厘米)
135    /// @param height 图片的高度(厘米)
136    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                // 插入图片到属性中
146                self.image_replacements
147                    .insert(placeholder.to_string(), None);
148            }
149            Some(url) => {
150                // 发送请求
151                let (image_data, image_ext) = request_image_data(&self.client, url).await?;
152                // 将厘米单位换算成emu
153                let width_emu = (width * DOCX_EMU) as u64;
154                let height_emu = (height * DOCX_EMU) as u64;
155                // 插入图片到属性中
156                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    /// 处理模板
169    /// @param template_path 模板路径
170    /// @param output_path 输出路径
171    pub fn process_template(
172        &self,
173        template_path: &str,
174        output_path: &str,
175    ) -> Result<(), DocxError> {
176        // 1. 打开模板文件
177        let template_file = File::open(template_path)?;
178        let mut archive = ZipArchive::new(template_file)?;
179
180        // 2. 创建输出文件
181        let output_file = File::create(output_path)?;
182        let mut zip_writer = ZipWriter::new(output_file);
183
184        // 3. 遍历ZIP中的文件
185        for i in 0..archive.len() {
186            let mut file = archive.by_index(i)?;
187            // 文件内容
188            let mut contents = Vec::new();
189            // 读取文件内容到数组中
190            file.read_to_end(&mut contents)?;
191            // 匹配文件类型
192            match file.name() {
193                "word/document.xml" => {
194                    // 处理文档主内容,替换模板内容
195                    contents = self.process_document_xml(&contents)?;
196                }
197                "word/_rels/document.xml.rels" => {
198                    // 处理关系文件
199                    contents = self.process_rels_xml(&contents)?;
200                }
201                &_ => {}
202            }
203
204            // 写入新文件
205            let option = SimpleFileOptions::default()
206                .compression_method(file.compression())
207                .unix_permissions(file.unix_mode().unwrap_or(0o644));
208            // 写入内容
209            zip_writer.start_file(file.name(), option)?;
210            zip_writer.write_all(&contents)?;
211        }
212
213        // 4. 添加新的图片文件
214        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            // 写入图片到word压缩文件中
220            zip_writer.start_file(&image_path, SimpleFileOptions::default())?;
221            zip_writer.write_all(&replacement.image_data)?;
222        }
223        // 将内容写入压缩文件(docx)
224        zip_writer.finish()?;
225        Ok(())
226    }
227
228    fn process_element(&self, _element: &mut BytesStart) -> Result<(), DocxError> {
229        // let aa = String::from_utf8_lossy(_element.name().as_ref()).to_string();
230        // println!("{:?}",aa );
231        Ok(())
232    }
233
234    /// 处理文件内容
235    /// @param contents 文件内容数组
236    fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
237        // 创建xml写对象
238        let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
239        // 读取xml文件的内容
240        let mut reader = quick_xml::Reader::from_reader(contents);
241        reader.config_mut().trim_text(true);
242        // 缓存数组
243        let mut buf = Vec::new();
244        // 图片对应的字符串占位符
245        let mut current_placeholder = String::new();
246        // 循环读取xml数据
247        loop {
248            // 读取数据
249            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                    // 如果为空,写入标签头
254                    if current_placeholder.is_empty() {
255                        xml_writer.write_event(Event::Start(element))?;
256                    }
257                }
258                Event::Text(e) => {
259                    // 读取标签的内容
260                    let mut text = e.unescape()?.into_owned();
261                    // 判断是否有替换字符串开头内容"{{"
262                    if text.starts_with(PREFIX_TAG) {
263                        // 判断是否包含结束字符串}}
264                        if text.ends_with(SUFFIX_TAG) {
265                            // 1、替换文本占位符操作
266                            self.process_text(&mut text);
267                            // 2、替换图片占位符操作
268                            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                            // 将字符串保存
276                            current_placeholder.push_str(&text);
277                        }
278                    } else {
279                        // 判断current_placeholder字符串是否有内容
280                        if current_placeholder.is_empty() {
281                            // 将原有字符串写入文档
282                            xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
283                        } else {
284                            // 将字符串写入
285                            current_placeholder.push_str(text.as_str());
286                            // 判断是否有结束字符串}}
287                            if current_placeholder.ends_with(PREFIX_TAG)
288                                && current_placeholder.starts_with(SUFFIX_TAG)
289                            {
290                                // 1、替换文本占位符操作
291                                self.process_text(&mut current_placeholder);
292                                // 2、如果不包含写入数据
293                                if !self.image_replacements.contains_key(&current_placeholder) {
294                                    xml_writer.write_event(Event::Text(BytesText::new(
295                                        current_placeholder.as_str(),
296                                    )))?;
297                                    // 清理数据
298                                    current_placeholder.clear();
299                                }
300                            }
301                        }
302                    }
303                }
304                Event::End(e) => {
305                    // 判断是否为空,为空,直接添加结尾标签
306                    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                        // 判断是否为段落
312                        if e.name().as_ref() == b"w:p" {
313                            // 判断是否为完整替换字符串
314                            if let Some(Some(docx_image)) =
315                                self.image_replacements.get(&current_placeholder)
316                            {
317                                // 替换占位符为图片
318                                DocxTemplate::create_drawing_element(
319                                    &mut xml_writer,
320                                    &docx_image.relation_id,
321                                    docx_image.width,
322                                    docx_image.height,
323                                )?;
324                            }
325                            // 清除字符串
326                            current_placeholder.clear();
327                        }
328                        // 写入结尾标签
329                        xml_writer.write_event(Event::End(e))?;
330                    }
331                }
332                Event::Eof => break,
333                Event::Empty(e) => {
334                    // 如果为空写入文档
335                    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        // 返回文件数组
346        Ok(xml_writer.into_inner().into_inner())
347    }
348
349    fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
350        // 创建xml写对象
351        let mut writer = Writer::new(Cursor::new(Vec::new()));
352        // 写入xml标签头
353        writer.write_event(Event::Decl(BytesDecl::new(
354            "1.0",
355            Some("UTF-8"),
356            Some("yes"),
357        )))?;
358
359        // 写入XML根元素
360        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        // 读取原始数据
368        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            // 读取关系文件
374            match reader.read_event_into(&mut buf)? {
375                // 判断关系文件内容是否为关联标签
376                Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
377                    // 写入关系标签内容
378                    writer.write_event(Event::Empty(e))?;
379                }
380                // 文件读取完毕
381                Event::Eof => break,
382                _ => {}
383            }
384            // 清理内容
385            buf.clear();
386        }
387
388        // 添加新的图片关系
389        for docx_image in self.image_replacements.values().flatten() {
390            // 创建图片路径
391            let image_path = format!(
392                "media/image_{}.{}",
393                docx_image.relation_id, docx_image.image_ext
394            );
395            // 创建图片关系标签
396            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            // 写入关系标签数据
405            writer.write_event(Event::Empty(relationship))?;
406        }
407
408        // 结束根元素
409        writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
410        // 输出关系文件内容
411        Ok(writer.into_inner().into_inner())
412    }
413
414    // 替换模板属性
415    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}