You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Apache POI和Gradle从Excel生成带唯一文件名的独立Word文档

实现每行生成独立Word文档的修改方案

核心修改思路

不再复用单个Word模板文档,而是为CSV/Excel的每一行数据重新加载模板文件,替换占位符后以该行的filename字段值为文件名单独保存到输出目录。

具体修改步骤及代码

  1. 调整merge方法逻辑

    • 遍历CSV每一行时,重新读取模板文件创建XWPFDocument实例,避免前一行的修改污染后续文档
    • 替换当前行的占位符后,提取filename字段生成合法的输出路径
    • 使用try-with-resources自动关闭流和文档对象,防止资源泄漏
  2. 添加辅助工具方法

    • 确保输出目录存在,避免IO异常
    • 处理filename中的非法字符,防止文件创建失败

修改后的MailMerge类代码示例:

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;

public class MailMerge {

    public void merge(String templateFile, String csvFile, String outputDir) throws IOException {
        // 确保输出目录存在,不存在则创建
        Files.createDirectories(Paths.get(outputDir));

        try (Reader reader = Files.newBufferedReader(Paths.get(csvFile));
             CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT.withHeader())) {

            for (CSVRecord record : csvParser) {
                // 为每一行重新加载模板,避免交叉污染
                try (InputStream templateStream = new FileInputStream(templateFile);
                     XWPFDocument document = new XWPFDocument(templateStream)) {

                    // 替换当前行的占位符
                    replaceMarkers(document, record);

                    // 提取并处理filename字段
                    String filename = sanitizeFilename(record.get("filename"));
                    if (!filename.toLowerCase().endsWith(".docx")) {
                        filename += ".docx";
                    }
                    String outputPath = Paths.get(outputDir, filename).toString();

                    // 保存生成的独立文档
                    try (OutputStream out = new FileOutputStream(outputPath)) {
                        document.write(out);
                    }
                }
            }
        }
    }

    private void replaceMarkers(XWPFDocument document, CSVRecord record) {
        // 保留原有的占位符替换逻辑,确保仅修改当前文档实例
        // 示例逻辑(可根据原项目实现调整):
        document.getParagraphs().forEach(p -> {
            String text = p.getText();
            for (String header : record.toMap().keySet()) {
                String marker = String.format("${%s}", header);
                if (text.contains(marker)) {
                    p.replaceText(marker, record.get(header));
                }
            }
        });
        // 处理表格等其他内容的占位符替换(原项目已有逻辑则保留)
    }

    // 清理文件名中的非法字符,适配多操作系统
    private String sanitizeFilename(String filename) {
        Objects.requireNonNull(filename, "filename字段不能为空");
        return filename.replaceAll("[<>:\"/\\\\|?*]", "_").trim();
    }
}

额外注意事项

  • Excel支持:处理Excel文件时逻辑类似,使用Apache POI的XSSFWorkbook遍历每一行,提取filename字段后执行相同的模板加载、替换、保存流程。
  • 性能优化:若模板文件较大,可提前将模板内容缓存为字节数组,避免重复读取磁盘:
    // 在merge方法开头缓存模板字节
    byte[] templateBytes = Files.readAllBytes(Paths.get(templateFile));
    // 循环中使用缓存的字节流加载模板
    try (InputStream templateStream = new ByteArrayInputStream(templateBytes);
         XWPFDocument document = new XWPFDocument(templateStream)) {
        // ... 后续逻辑
    }
    
  • 空值处理:可根据业务需求调整sanitizeFilename方法,为空的filename字段设置默认文件名(如default_${行号}.docx)。
  • Gradle依赖检查:确保build.gradle中已包含必要依赖(原项目已有,若新增工具类可补充):
    dependencies {
        implementation 'org.apache.poi:poi-ooxml:5.2.5'
        implementation 'org.apache.commons:commons-csv:1.10.0'
    }
    

内容的提问来源于stack exchange,提问作者kheder47

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 07:25:19