rust的docx-rs库,自定义docx模版批量分页生成一个docx文档(方便打印)(逐行注释)

一、docx文档分页

  • docx-rs库,写文档时添加分页符(下一页)
rust 复制代码
doc.add_paragraph(
    Paragraph::new().add_run(docx_rs::Run::new().add_break(docx_rs::BreakType::Page)),
);

二、主要功能

三、完整代码

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(())
}
相关推荐
独孤留白7 小时前
从C到Rust:Trait From Into 类型转换
rust
doiito8 小时前
【RUST AI】把 TTS 搬进浏览器:kokoroi-rs 的 WASM 实践
ai·rust·架构设计
jinshw8 小时前
自己实现GIS配图软件(一)
rust·开源·gis
程序员爱钓鱼8 小时前
Rust 元组 Tuple 详解:组合不同类型的数据
前端·后端·rust
humbinal1 天前
同时支持 gui & cli 的 parquet 文件查看工具,高性能小清新!
hive·python·rust·spark·开源·github·parquet
belowfrog1 天前
Rust 的 Deref 到底为啥这么乱呀!
rust
程序员爱钓鱼1 天前
Rust 数组 Array 详解:定义、访问、遍历与切片
后端·rust
程序员爱钓鱼2 天前
Rust if let 与 while let 详解:简化模式匹配
前端·后端·rust
第一程序员3 天前
Rust trait 入门:把 AI 客户端抽象成可替换接口
python·rust·github
分布式存储与RustFS3 天前
RustFS Beta.10 性能解读:PUT 全面反超 MinIO,Rust 重写对象存储成了?
开发语言·后端·rust