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(())
}
相关推荐
Vallelonga2 小时前
Rust Option.as_ref() 方法
开发语言·rust
大卫小东(Sheldon)17 小时前
GIM 2.0 发布:真正让 AI 提交消息可定制、可控、可项目级优化
git·rust·gim
roamingcode1 天前
我是如何 Vibe Coding,将 AI CLI 工具从 Node.js 迁移到 Rust 并成功发布的
人工智能·rust·node.js·github·claude·github copilot
初恋叫萱萱1 天前
构建高性能生成式AI应用:基于Rust Axum与蓝耘DeepSeek-V3.2大模型服务的全栈开发实战
开发语言·人工智能·rust
superman超哥3 天前
Serde 性能优化的终极武器
开发语言·rust·编程语言·rust serde·serde性能优化·rust开发工具
sayang_shao3 天前
Rust多线程编程学习笔记
笔记·学习·rust
鸿乃江边鸟3 天前
Spark Datafusion Comet 向量化Rust Native--读数据
rust·spark·native·arrow
硬汉嵌入式3 天前
基于Rust构建的单片机Ariel RTOS,支持Cortex-M、RISC-V 和 Xtensa
单片机·rust·risc-v
低调滴开发4 天前
Tauri开发桌面端服务,配置指定防火墙端口
rust·tauri·桌面端·windows防火墙规则