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(())
}
相关推荐
a1117761 小时前
剪切板助手TieZ(开源项目rust)
rust·开源·剪切板
盒马盒马12 小时前
Rust:迭代器
开发语言·后端·rust
Source.Liu1 天前
【Iced】stream.rs文件
rust·iced
Kapaseker1 天前
精通 Rust 宏 — 包装新类型
rust
飞函安全1 天前
Vite 8.0:Rust.bundle,性能提升10-30倍
开发语言·人工智能·rust
奋斗中的小猩猩2 天前
OpenClaw不安全,Rust写的ZeroClaw给出满意答案
安全·rust·openclaw·小龙虾
海奥华22 天前
Rust初步学习
开发语言·学习·rust
VermouthSp2 天前
上下文切换
linux·rust
小杍随笔2 天前
【Rust 1.94.0 正式发布:数组窗口、Cargo 配置模块化、TOML 1.1 全面升级|开发者必看】
开发语言·后端·rust
敬业小码哥3 天前
记一次:clion使用rust插件配置环境并开发
学习·rust