docx_template/
docx.rs

1use crate::error::DocxError;
2use crate::image::{DOCX_EMU, DocxImage};
3use quick_xml::Writer;
4use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
5use reqwest::Client;
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::{Cursor, Read, Write};
9use std::path::Path;
10use std::time::Duration;
11use zip::write::SimpleFileOptions;
12use zip::{ZipArchive, ZipWriter};
13
14pub struct DocxTemplate {
15    // 待替换的字符串
16    text_replacements: HashMap<String, String>,
17    // 待替换的图片
18    image_replacements: HashMap<String, Option<DocxImage>>,
19    // 请求对象
20    client: Client,
21}
22
23impl DocxTemplate {
24    pub fn new() -> Self {
25        DocxTemplate {
26            text_replacements: HashMap::new(),
27            image_replacements: HashMap::new(),
28            client: Client::builder()
29                .timeout(Duration::from_secs(10)) // 设置超时
30                .build()
31                .unwrap(),
32        }
33    }
34
35    /// 添加待替换的字符以及对应的值
36    /// @param placeholder 待替换的字符串
37    /// @param value 替换的值
38    pub fn add_text_replacement(&mut self, placeholder: &str, value: &str) {
39        self.text_replacements
40            .insert(placeholder.to_string(), value.to_string());
41    }
42
43    /// 添加待替换的图片
44    /// @param placeholder 待替换的字符串
45    /// @param image_path 图片路径
46    pub fn add_image_file_replacement(
47        &mut self,
48        placeholder: &str,
49        image_path: Option<&str>,
50    ) -> Result<(), DocxError> {
51        match image_path {
52            None => {
53                // 插入图片到属性中
54                self.image_replacements
55                    .insert(placeholder.to_string(), None);
56            }
57            Some(data) => {
58                // 插入图片到属性中
59                self.image_replacements
60                    .insert(placeholder.to_string(), Some(DocxImage::new(data)?));
61            }
62        }
63
64        Ok(())
65    }
66
67    /// 添加待替换的图片
68    /// @param placeholder 替换的字符串
69    /// @param image_path 图片路径
70    /// @param width 图片的宽度(厘米)
71    /// @param height 图片的高度(厘米)
72    pub fn add_image_file_size_replacement(
73        &mut self,
74        placeholder: &str,
75        image_path: Option<&str>,
76        width: f32,
77        height: f32,
78    ) -> Result<(), DocxError> {
79        match image_path {
80            None => {
81                // 插入图片到属性中
82                self.image_replacements
83                    .insert(placeholder.to_string(), None);
84            }
85            Some(file_path) => {
86                // 将厘米单位换算成emu
87                let width_emu = (width * DOCX_EMU) as u64;
88                let height_emu = (height * DOCX_EMU) as u64;
89                // 插入图片到属性中
90                self.image_replacements.insert(
91                    placeholder.to_string(),
92                    Some(DocxImage::new_size(file_path, width_emu, height_emu)?),
93                );
94            }
95        }
96
97        Ok(())
98    }
99
100    /// 添加待替换的图片,替换的图片大小默认6.09*5.9厘米
101    /// @param placeholder 替换的字符串
102    /// @param image_url 图片路径
103    pub async fn add_image_url_replacement(
104        &mut self,
105        placeholder: &str,
106        image_url: Option<&str>,
107    ) -> Result<(), DocxError> {
108        match image_url {
109            None => {
110                // 插入图片到属性中
111                self.image_replacements
112                    .insert(placeholder.to_string(), None);
113            }
114            Some(url) => {
115                // 发送请求
116                let response = self.client.get(url).send().await?;
117                // 检查状态码
118                if response.status().is_success() {
119                    // 读取字节
120                    let image_data = response.bytes().await?.to_vec();
121                    // 插入图片到属性中
122                    self.image_replacements.insert(
123                        placeholder.to_string(),
124                        Some(DocxImage::new_image_data(url, image_data)?),
125                    );
126                }
127            }
128        }
129
130        Ok(())
131    }
132
133    /// 添加待替换的图片
134    /// @param placeholder 替换的字符串
135    /// @param image_url 图片路径
136    /// @param width 图片的宽度(厘米)
137    /// @param height 图片的高度(厘米)
138    pub async fn add_image_url_size_replacement(
139        &mut self,
140        placeholder: &str,
141        image_url: Option<&str>,
142        width: f32,
143        height: f32,
144    ) -> Result<(), DocxError> {
145        match image_url {
146            None => {
147                // 插入图片到属性中
148                self.image_replacements
149                    .insert(placeholder.to_string(), None);
150            }
151            Some(url) => {
152                // 发送请求
153                let response = self.client.get(url).send().await?;
154                // 检查状态码
155                if response.status().is_success() {
156                    // 读取字节
157                    let image_data = response.bytes().await?.to_vec();
158                    // 将厘米单位换算成emu
159                    let width_emu = (width * DOCX_EMU) as u64;
160                    let height_emu = (height * DOCX_EMU) as u64;
161                    // 插入图片到属性中
162                    self.image_replacements.insert(
163                        placeholder.to_string(),
164                        Some(DocxImage::new_image_data_size(
165                            url, image_data, width_emu, height_emu,
166                        )?),
167                    );
168                }
169            }
170        }
171
172        Ok(())
173    }
174
175    /// 处理模板
176    /// @param template_path 模板路径
177    /// @param output_path 输出路径
178    pub fn process_template(
179        &self,
180        template_path: &str,
181        output_path: &str,
182    ) -> Result<(), DocxError> {
183        // 1. 打开模板文件
184        let template_file = File::open(template_path)?;
185        let mut archive = ZipArchive::new(template_file)?;
186
187        // 2. 创建输出文件
188        let output_file = File::create(output_path)?;
189        let mut zip_writer = ZipWriter::new(output_file);
190
191        // 3. 遍历ZIP中的文件
192        for i in 0..archive.len() {
193            let mut file = archive.by_index(i)?;
194            // 文件内容
195            let mut contents = Vec::new();
196            // 读取文件内容到数组中
197            file.read_to_end(&mut contents)?;
198            // 匹配文件类型
199            match file.name() {
200                "word/document.xml" => {
201                    // 处理文档主内容,替换模板内容
202                    contents = self.process_document_xml(&contents)?;
203                }
204                "word/_rels/document.xml.rels" => {
205                    // 处理关系文件
206                    contents = self.process_rels_xml(&contents)?;
207                }
208                &_ => {}
209            }
210
211            // 写入新文件
212            let option = SimpleFileOptions::default()
213                .compression_method(file.compression())
214                .unix_permissions(file.unix_mode().unwrap_or(0o644));
215            // 写入内容
216            zip_writer.start_file(file.name(), option)?;
217            zip_writer.write_all(&contents)?;
218        }
219
220        // 4. 添加新的图片文件
221        for replacement in self.image_replacements.values().flatten() {
222            let image_path = format!(
223                "word/media/image_{}.{}",
224                replacement.relation_id,
225                DocxTemplate::get_extension(&replacement.image_path)?
226            );
227            // 写入图片到word压缩文件中
228            zip_writer.start_file(&image_path, SimpleFileOptions::default())?;
229            zip_writer.write_all(&replacement.image_data)?;
230        }
231        // 将内容写入压缩文件(docx)
232        zip_writer.finish()?;
233        Ok(())
234    }
235
236    fn process_element(&self, _element: &mut BytesStart) -> Result<(), DocxError> {
237        // let aa = String::from_utf8_lossy(_element.name().as_ref()).to_string();
238        // println!("{:?}",aa );
239        Ok(())
240    }
241
242    /// 处理文件内容
243    /// @param contents 文件内容数组
244    fn process_document_xml(&self, contents: &[u8]) -> Result<Vec<u8>, DocxError> {
245        // 创建xml写对象
246        let mut xml_writer = Writer::new(Cursor::new(Vec::new()));
247        // 写入xml文件头
248        // xml_writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), Some("yes"))))?;
249        // 读取xml文件的内容
250        let mut reader = quick_xml::Reader::from_reader(contents);
251        reader.config_mut().trim_text(true);
252        // 缓存数组
253        let mut buf = Vec::new();
254        // 图片对应的字符串占位符
255        let mut current_placeholder = String::new();
256        // 循环读取xml数据
257        loop {
258            // 读取数据
259            match reader.read_event_into(&mut buf)? {
260                Event::Start(e) => {
261                    let mut element = e.to_owned();
262                    self.process_element(&mut element)?;
263                    // 如果为空,写入标签头
264                    if current_placeholder.is_empty() {
265                        xml_writer.write_event(Event::Start(element))?;
266                    }
267                }
268                Event::Text(e) => {
269                    // 读取标签的内容
270                    let mut text = e.unescape()?.into_owned();
271                    // 判断是否有替换字符串开头内容"{{"
272                    if text.starts_with("{{") {
273                        // 判断是否包含结束字符串}}
274                        if text.ends_with("}}") {
275                            // 1、替换文本占位符操作
276                            self.process_text(&mut text);
277                            // 2、替换图片占位符操作
278                            if self.image_replacements.contains_key(&text) {
279                                current_placeholder.push_str(&text);
280                            } else {
281                                xml_writer
282                                    .write_event(Event::Text(BytesText::new(text.as_str())))?;
283                            }
284                        } else {
285                            // 将字符串保存
286                            current_placeholder.push_str(&text);
287                        }
288                    } else {
289                        // 判断current_placeholder字符串是否有内容
290                        if current_placeholder.is_empty() {
291                            // 将原有字符串写入文档
292                            xml_writer.write_event(Event::Text(BytesText::new(text.as_str())))?;
293                        } else {
294                            // 将字符串写入
295                            current_placeholder.push_str(text.as_str());
296                            // 判断是否有结束字符串}}
297                            if current_placeholder.ends_with("}}")
298                                && current_placeholder.starts_with("{{")
299                            {
300                                // 1、替换文本占位符操作
301                                self.process_text(&mut current_placeholder);
302                                // 2、如果不包含写入数据
303                                if !self.image_replacements.contains_key(&current_placeholder) {
304                                    xml_writer.write_event(Event::Text(BytesText::new(
305                                        current_placeholder.as_str(),
306                                    )))?;
307                                    // 清理数据
308                                    current_placeholder.clear();
309                                }
310                            }
311                        }
312                    }
313                }
314                Event::End(e) => {
315                    // 判断是否为空,为空,直接添加结尾标签
316                    if current_placeholder.is_empty() {
317                        xml_writer.write_event(Event::End(e))?;
318                    }else if current_placeholder.starts_with("{{") && current_placeholder.ends_with("}}"){
319                        // 判断是否为段落
320                        if e.name().as_ref() == b"w:p" {
321                            // 判断是否为完整替换字符串
322                            if let Some(Some(docx_image)) =
323                                self.image_replacements.get(&current_placeholder)
324                            {
325                                // 替换占位符为图片
326                                DocxTemplate::create_drawing_element(
327                                    &mut xml_writer,
328                                    &docx_image.relation_id,
329                                    docx_image.width,
330                                    docx_image.height,
331                                )?;
332                            }
333                            // 清除字符串
334                            current_placeholder.clear();
335                        }
336                        // 写入结尾标签
337                        xml_writer.write_event(Event::End(e))?;
338                    }
339
340                }
341                Event::Eof => break,
342                Event::Empty(e) => {
343                    // 如果为空写入文档
344                    if current_placeholder.is_empty() {
345                        xml_writer.write_event(Event::Empty(e))?;
346                    }
347                },
348                e => {
349                    xml_writer.write_event(e)?;
350                },
351            }
352            buf.clear();
353        }
354        // 返回文件数组
355        Ok(xml_writer.into_inner().into_inner())
356    }
357
358    fn process_rels_xml(&self, xml_data: &[u8]) -> Result<Vec<u8>, DocxError> {
359        // 创建xml写对象
360        let mut writer = Writer::new(Cursor::new(Vec::new()));
361        // 写入xml标签头
362        writer.write_event(Event::Decl(BytesDecl::new(
363            "1.0",
364            Some("UTF-8"),
365            Some("yes"),
366        )))?;
367
368        // 写入XML根元素
369        writer.write_event(Event::Start(
370            BytesStart::new("Relationships").with_attributes([(
371                "xmlns",
372                "http://schemas.openxmlformats.org/package/2006/relationships",
373            )]),
374        ))?;
375
376        // 读取原始数据
377        let mut reader = quick_xml::Reader::from_reader(xml_data);
378        reader.config_mut().trim_text(true);
379        let mut buf = Vec::new();
380
381        loop {
382            // 读取关系文件
383            match reader.read_event_into(&mut buf)? {
384                // 判断关系文件内容是否为关联标签
385                Event::Empty(e) if e.name().as_ref() == b"Relationship" => {
386                    // 写入关系标签内容
387                    writer.write_event(Event::Empty(e))?;
388                }
389                // 文件读取完毕
390                Event::Eof => break,
391                _ => {}
392            }
393            // 清理内容
394            buf.clear();
395        }
396
397        // 添加新的图片关系
398        for docx_image in self.image_replacements.values().flatten() {
399            // 获取图片扩展名
400            let extension = DocxTemplate::get_extension(&docx_image.image_path)?;
401            // 创建图片路径
402            let image_path = format!("media/image_{}.{}", docx_image.relation_id, extension);
403            // 创建图片关系标签
404            let relationship = BytesStart::new("Relationship").with_attributes([
405                ("Id", docx_image.relation_id.as_str()),
406                (
407                    "Type",
408                    "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
409                ),
410                ("Target", &image_path),
411            ]);
412            // 写入关系标签数据
413            writer.write_event(Event::Empty(relationship))?;
414        }
415
416        // 结束根元素
417        writer.write_event(Event::End(BytesEnd::new("Relationships")))?;
418        // 输出关系文件内容
419        Ok(writer.into_inner().into_inner())
420    }
421
422    fn get_extension(image_path: &str) -> Result<&str, DocxError> {
423        Path::new(image_path)
424            .extension()
425            .and_then(|s| s.to_str())
426            .ok_or_else(|| {
427                DocxError::ImageNotFound("Could not determine image extension".to_string())
428            })
429    }
430    // 替换模板属性
431    fn process_text(&self, text: &mut String) {
432        for (placeholder, value) in &self.text_replacements {
433            *text = text.replace(placeholder, value);
434        }
435    }
436
437    fn create_drawing_element<T>(
438        writer: &mut Writer<T>,
439        relation_id: &str,
440        width: u64,
441        height: u64,
442    ) -> Result<(), DocxError>
443    where
444        T: Write,
445    {
446        let drawing = format!(
447            r#"
448        <w:drawing>
449            <wp:inline distT="0" distB="0" distL="0" distR="0">
450                <wp:extent cx="{}" cy="{}"/>
451                <wp:docPr id="1" name="Picture 1" descr="Generated image"/>
452                <wp:cNvGraphicFramePr>
453                    <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
454                </wp:cNvGraphicFramePr>
455                <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
456                    <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
457                        <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
458                            <pic:nvPicPr>
459                                <pic:cNvPr id="0" name="Picture 1" descr="Generated image"/>
460                                <pic:cNvPicPr><a:picLocks noChangeAspect="1"/></pic:cNvPicPr>
461                            </pic:nvPicPr>
462                            <pic:blipFill>
463                                <a:blip r:embed="{}"/>
464                                <a:stretch>
465                                    <a:fillRect/>
466                                </a:stretch>
467                            </pic:blipFill>
468                            <pic:spPr>
469                                <a:xfrm>
470                                    <a:off x="0" y="0"/>
471                                    <a:ext cx="{}" cy="{}"/>
472                                </a:xfrm>
473                                <a:prstGeom prst="rect">
474                                    <a:avLst/>
475                                </a:prstGeom>
476                            </pic:spPr>
477                        </pic:pic>
478                    </a:graphicData>
479                </a:graphic>
480            </wp:inline>
481        </w:drawing>
482    "#,
483            width, height, relation_id, width, height,
484        );
485
486        let mut reader = quick_xml::Reader::from_str(&drawing);
487        reader.config_mut().trim_text(true);
488        let mut buf = Vec::new();
489
490        loop {
491            match reader.read_event_into(&mut buf)? {
492                Event::Eof => break,
493                e => {
494                    writer.write_event(e)?;
495                }
496            }
497        }
498        Ok(())
499    }
500}
501
502impl Default for DocxTemplate {
503    fn default() -> Self {
504        Self::new()
505    }
506}