rust
复制代码
use docx_rs::{Docx, Paragraph, read_docx};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
/// 人员数据结构
#[derive(Deserialize, Debug)]
struct PersonData {
name: String,
age: u8,
city: String,
}
/// 将字符串中的占位符替换成实际值
fn replace_placeholders(text: &str, data_map: &HashMap<&str, &str>) -> String {
// 遍历替换数据映射,将占位符替换为实际值
data_map.iter().fold(text.to_string(), |acc, (key, value)| {
// 构建占位符字符串
let placeholder = format!("{{{}}}", key);
// 替换占位符为实际值
acc.replace(&placeholder, value)
})
}
/// 替换文档中的占位符
fn replace_placeholders_in_doc(doc: &mut Docx, data_map: &HashMap<&str, &str>) {
// 遍历文档中的段落,替换占位符
for child in &mut doc.document.children {
if let docx_rs::DocumentChild::Paragraph(para) = child {
// 遍历段落中的run,替换文本内容并保留格式
paragraph_replace(para, data_map.clone());
} else if let docx_rs::DocumentChild::Table(table) = child {
for table_child in &mut table.rows {
if let docx_rs::TableChild::TableRow(row) = table_child {
for row_child in &mut row.cells {
if let docx_rs::TableRowChild::TableCell(cell) = row_child {
for cell_content in &mut cell.children {
if let docx_rs::TableCellContent::Paragraph(para) = cell_content {
// 遍历段落中的run,替换文本内容并保留格式
paragraph_replace(para, data_map.clone());
}
}
}
}
}
}
}
}
}
/// 处理单个文档,替换占位符并保存
fn process_document_with_data(
template_doc: Docx,
output_path: &Path,
persons: Vec<PersonData>,
) -> Result<(), Box<dyn std::error::Error>> {
// 克隆模板文档作为主文档
let mut main_doc = template_doc.clone();
// 清空主文档的子元素
main_doc.document.children.clear();
// 循环处理每个人员的数据
for (i, person) in persons.iter().enumerate() {
// 将年龄转换为字符串
let age_str = person.age.to_string();
// 创建替换数据映射
let data_map: HashMap<&str, &str> = [
("name", person.name.as_str()),
("age", age_str.as_str()),
("city", person.city.as_str()),
]
.into_iter()
.collect();
// 为每个人员创建一个新的文档副本并替换占位符
let mut person_doc = template_doc.clone();
replace_placeholders_in_doc(&mut person_doc, &data_map);
// 将处理后的文档内容添加到主文档中
main_doc
.document
.children
.extend(person_doc.document.children);
// 如果不是最后一个人员,添加分页符
if i < persons.len() - 1 {
main_doc = main_doc.add_paragraph(
Paragraph::new().add_run(docx_rs::Run::new().add_break(docx_rs::BreakType::Page)),
);
}
}
// 保存修改后的文档
let file = fs::File::create(output_path)?;
let xml_docx = main_doc.build();
xml_docx.pack(file)?;
Ok(())
}
/// 替换段落内的模版内容,因段落、表格单独遍历,所以提取内容重复使用
fn paragraph_replace(para: &mut Paragraph, data_map: HashMap<&str, &str>) {
// 遍历段落中的run,替换文本内容并保留格式
for para_child in &mut para.children {
if let docx_rs::ParagraphChild::Run(run) = para_child {
// 保存原始run的所有子元素
let original_children = std::mem::take(&mut run.children);
// 处理每个run子元素
for run_child in original_children {
match run_child {
docx_rs::RunChild::Text(text) => {
// 替换文本内容
let replaced_text = replace_placeholders(&text.text, &data_map);
// 创建新的文本节点并添加到run的children中
run.children
.push(docx_rs::RunChild::Text(docx_rs::Text::new(replaced_text)));
}
other => {
// 保留其他类型的子元素(保留格式)
run.children.push(other);
}
}
}
}
}
}
/// 读取模板文档
fn read_template(template_path: &Path) -> Result<Docx, Box<dyn std::error::Error>> {
let doc = read_docx(&fs::read(template_path)?)?;
Ok(doc)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 示例人员数据集
let persons = vec![
PersonData {
name: "Alice".to_string(),
age: 30,
city: "New York".to_string(),
},
PersonData {
name: "Bob".to_string(),
age: 25,
city: "San Francisco".to_string(),
},
PersonData {
name: "Charlie".to_string(),
age: 35,
city: "Chicago".to_string(),
},
];
// 模板文件路径
let template_path = Path::new("template.docx");
// 验证模板文件存在
if !template_path.exists() {
return Err(format!("模板文件不存在: {:?}", template_path).into());
}
// 读取模板文档(只读取一次,提高性能)
let template_doc = read_template(template_path)?;
let output_path = Path::new("output.docx");
process_document_with_data(template_doc, output_path, persons)?;
println!("🎉 全部文档均已生成完毕!");
Ok(())
}