使用POI向Word模版文档填充数据,一些注意事项以及解决办法

有这样一个需求:需要将用户输入的数据填写到准备好的Word模版中并提供下载,最终选择POI-tl和POI来完成上述需求。

使用POI主要有下面几个步骤: 

1.准备好相应的Word模版,在模版想要填充的位置使用设计好的占位符(本文使用的是 ${占位字符});

2.将要填充的数据以 key-value 的形式存储到Map中,其中key为Word文档中的占位字符,value为要在对应位置填充的数据;

3.使用POI进行填充。

在这个过程中,主要遇到了以下两个问题:

1. Word的两个格式doc和docx(两种文件的区别大家可以自行百度了解下),POI并没有提供统一的处理类。分别用 HWPFDocument 处理doc文档,用 XWPFTemplate 处理docx文档。

2. 在用 HWPFDocument 处理doc文档时,如果遇到一些复杂的表格(像是有合并的单元格时),会导致一读取doc文档,生成的doc文档就无法打开,一般打开时,word会报如下错误:

(这里不知道是为什么,如果有大佬知道原因,希望可以指教下)。而将文件模版转换为docx后,可以正常填充。

所以这里采取的解决办法是:先将文件模版转换为docx,填充好后,再通过POI转换为doc。(其中docx转换为doc的代码看了好多都没有好用的,试了下将在输入输出流进行读取时候,将文件名换一下,居然好用了,然而我并没有搞明白是为什么,同样希望可以请教下大佬。)

下面是填充Word并提供下载的完整代码:

/**
*  Word模版填充工具类
*/
public class WordUtil {

    /**
     * .doc和.docx类型的文件需要分别进行处理,
     * 同时由于无法正确读取部分doc文档,采用先进行docx填充,再转换为doc方案
     */
    public static void fillAndDownloadWordDocument(String templateFilePath, Map<String, String> dataMap, HttpServletResponse response, String cacheFilePath) {
        // 判断文件类型,根据不同类填充
        if (templateFilePath.endsWith(".docx") && templateFilePath.contains("复杂表格模板")) {
            fillAndConvertDocxToDoc(templateFilePath, dataMap, cacheFilePath, response);
        } else if (templateFilePath.endsWith(".docx")) {
            fillAndDownloadDocx(templateFilePath, dataMap, response);
        } else if (templateFilePath.endsWith(".doc")) {
            fillAndDownloadDoc(templateFilePath, dataMap, response);
        } else {
            throw new RuntimeException("文件类型错误");
        }
    }

    /**
     * 将数据填充到对应word模版中,并提供下载docx类型文件
     *
     * @param templateFilePath 文件名称
     * @param dataMap        填充word模板中的数据
     */
    private static void fillAndDownloadDocx(String templateFilePath, Map<String, String> dataMap, HttpServletResponse response) {
        ConfigureBuilder builder = Configure.newBuilder();
        builder.buildGramer("${", "}");
        XWPFTemplate template;

        try {
            template = XWPFTemplate.compile(new FileInputStream(templateFilePath), builder.build()).render(dataMap);
        } catch (IOException e) {
            throw new RuntimeException("文件模版填充失败");
        }

        if (template == null) {
            throw new RuntimeException("文件模版填充失败");
        }

        //浏览器单个下载生成的word文件
        // 文件名 日期-文件名
        StringBuffer resultDocName = new StringBuffer();
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
        resultDocName.append(dateFormat.format(date)).append(templateFilePath.substring(templateFilePath.lastIndexOf("\\") + 1));
        String exportFileName = resultDocName.toString();

        ServletOutputStream outputStream = null;
        // 进行文件下载
        try {
            outputStream = response.getOutputStream();
            // 对文件名进行编码处理中文问题
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(exportFileName, "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            template.write(outputStream);
        } catch (Exception e) {
            throw new RuntimeException("文件下载失败");
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                template.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将数据填充到对应word模版中,并提供下载doc类型文件
     *
     * @param templateFilePath 导入文件路径
     * @param dataMap        填充word模板中的数据
     */
    private static void fillAndDownloadDoc(String templateFilePath, Map<String, String> dataMap, HttpServletResponse response) {
        HWPFDocument doc;
        try {
            doc = new HWPFDocument(new FileInputStream(templateFilePath));
        } catch (IOException e) {
            throw new RuntimeException("加载word文件模版失败");
        }
        // 填充内容
        Range range = doc.getRange();
        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
            // 这里需要判空,poi内部会调用value.length
            String value = entry.getValue();
            if (value == null) {
                value = "";
            }
            range.replaceText(build(entry.getKey()), value);
        }

        // 填充页眉
        Range headerStoryRange = doc.getHeaderStoryRange();
        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
            String value = entry.getValue();
            if (value == null) {
                value = "";
            }
            headerStoryRange.replaceText(build(entry.getKey()), value);
        }

        //浏览器单个下载生成的word文件
        // 文件名 日期-文件名
        StringBuffer resultDocName = new StringBuffer();
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
        resultDocName.append(dateFormat.format(date)).append(templateFilePath.substring(templateFilePath.lastIndexOf("\\") + 1));
        String exportFileName = resultDocName.toString();

        ServletOutputStream outputStream = null;
        // 进行文件下载
        try {
            outputStream = response.getOutputStream();
            // 对文件名进行编码处理中文问题
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(exportFileName, "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            doc.write(outputStream);
        } catch (Exception e) {
            throw new RuntimeException("文件下载失败");
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                doc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static String build(String s) {
        return "${" + s + "}";
    }

    /**
     * 由于部分doc文档,poi无法直接读取,采用以下方案进行
     * 1.外界将模版保存为docx,进行填充
     * 2.填充后docx保存至本地,转换为doc
     * 3.提供下载,删除过程中产生的文件
     *
     * @param templateFilePath 模版文件位置
     * @param dataMap        填充数据
     * @param cacheFilePath  文件缓存目录
     */
    private static void fillAndConvertDocxToDoc(String templateFilePath, Map<String, String> dataMap, String cacheFilePath, HttpServletResponse response) {
        ConfigureBuilder builder = Configure.newBuilder();
        builder.buildGramer("${", "}");
        XWPFTemplate template;

        try {
            template = XWPFTemplate.compile(new FileInputStream(templateFilePath), builder.build()).render(dataMap);
        } catch (IOException e) {
            throw new RuntimeException("文件模版填充失败");
        }

        if (template == null) {
            throw new RuntimeException("文件模版填充失败");
        }

        //浏览器单个下载生成的word文件
        // 文件名 日期-文件名
        StringBuffer resultDocName = new StringBuffer();
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");
        resultDocName.append(dateFormat.format(date)).append(templateFilePath.substring(templateFilePath.lastIndexOf("\\") + 1));
        String exportFileName = resultDocName.toString();

        String exportFilePath = cacheFilePath + exportFileName;
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(exportFilePath);
            template.write(fos);
        } catch (IOException e) {
            System.out.println(exportFileName + "文件保存失败");
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                template.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 填充完毕后,进行docx转doc,并提供下载
        String docName = exportFileName.replaceFirst(".docx", ".doc");
        String docPath = cacheFilePath + docName;
        convertDocxToDoc(exportFilePath, docPath);
        downloadWord(response, docName, docPath);
        deleteFile(exportFilePath);
        deleteFile(docPath);
    }

    /**
     * 上传文件
     *
     * @param response 响应
     * @param filePath 文件路径
     */
    private static void downloadWord(HttpServletResponse response, String exportFileName, String filePath) {
        FileInputStream fis = null;
        ServletOutputStream outputStream = null;
        try {
            fis = new FileInputStream(filePath);
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(exportFileName, "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            outputStream = response.getOutputStream();
            byte[] bytes = new byte[1024];
            int readCount;
            while ((readCount = fis.read(bytes)) != -1) {
                outputStream.write(bytes, 0, readCount);
            }
            fis.close();
            outputStream.close();
        } catch (IOException e) {
            System.out.println(filePath + "文件上传失败");
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * docx文档转换为doc文档
     *
     * @param docxPath docx路径
     * @param docPath  doc路径
     */
    private static void convertDocxToDoc(String docxPath, String docPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(docxPath);
            XWPFDocument docx = new XWPFDocument(fis);
            fos = new FileOutputStream(docPath);
            docx.write(fos);

        } catch (IOException e) {
            System.out.println(docxPath + "文件转换失败");
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 删除文件
     *
     * @param filePath 文件路径
     */
    private static void deleteFile(String filePath) {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            try {
                boolean delete = file.delete();
                if (!delete) {
                    System.out.println("文件删除失败");
                }
            } catch (Exception e) {
                System.out.println("文件删除失败");
            }
        }
    }
}

我这里用的依赖是比较老的依赖了,其他版本api名字可能有不同,当然也要注意下不同版本的poi的依赖之间可能会有冲突。所用依赖如下:

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>4.0.0</version>
        </dependency>

有错误还请各位兄弟指出

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Java中,可以使用Apache POI库来填充Word模板的表格数据POI是一个用于读取和写入Microsoft Office格式文件的Java库。使用POI,我们可以创建和填充Word模板中的表格数据。 首先,需要导入POI库到项目中。接着,就可以使用POI的XWPFDocument类来操作Word文档。可以使用XWPFDocument类的构造方法来打开模板文件,并将其读取到内存中。然后,需要定位到要填充表格数据的位置。使用XWPFTable类可以获取表格对象,并使用getRows()方法获取表格中的行。然后,可以使用XWPFTableRow类来获取特定行的单元格对象,并使用getCell()方法获取单元格。 为了填充表格数据,需要提供数据源。可以使用Java中的集合类(如List或Map)来存储数据。然后可以使用循环来遍历数据源,依次填充表格中的单元格。可以使用XWPFParagraph类来创建段落对象,并使用该类的createRun()方法创建Run对象。使用Run对象的setText()方法即可将数据填充到单元格中。 填充完表格数据后,需要保存Word文档。可以使用XWPFDocument类的write()方法将修改后的文档保存到磁盘上。 总的来说,使用POI填充Word模板表格数据可以简化表格数据填充的过程,并可以提高代码的可读性和可维护性。 ### 回答2: Java使用POI填充Word模板表格数据是一种非常常见的应用场景。POI是一个开放源代码库,允许Java程序员使用Java语言创建、修改和读取各种Microsoft Office格式文件,如Excel、Word和PowerPoint。 填充Word模板表格数据的主要步骤如下: 1.导入POI的相关jar包,可通过Maven或手动引入方式。 2.创建一个Word文档,将其保存为模板文件(*.dotx)。 3.使用POI的XWPFDocument类将模板文件加载到内存中,然后通过XWPFTable类获取表格对象。 4.使用XWPFTableRow和XWPFTableCell类,遍历表格行和列,对需要填充的单元格进行赋值操作。 5.完成赋值后,使用OutputSteam将数据写入新的Word文件中,保存完成。 下面给出一个使用POI填充Word模板表格数据的例子: ``` public static void fillTemplate(String templatePath, String destPath, Map<String, String> data) throws Exception { //加载模板文件 XWPFDocument document = new XWPFDocument(new FileInputStream(new File(templatePath))); //获取表格对象 List<XWPFTable> tables = document.getTables(); XWPFTable table = tables.get(0); //遍历表格行和列 for (XWPFTableRow row : table.getRows()) { for (XWPFTableCell cell : row.getTableCells()) { //获取单元格内容 String text = cell.getText(); if (text != null && data.containsKey(text)) { //填充数据 cell.setText(data.get(text)); } } } //保存填充数据后的新文件 OutputStream os = new FileOutputStream(new File(destPath)); document.write(os); os.flush(); os.close(); } ``` 在上面的例子中,我们首先通过XWPFDocument类将模板文件加载到内存中,然后获取表格对象。接着,我们遍历表格行和列,对需要填充的单元格进行赋值操作,最后使用OutputStream将数据写入新的Word文件中,完成填充数据的操作。 综上所述,使用POI填充Word模板表格数据是一种实现简单、使用方便的方式,对于需要生成大量Word文档且格式固定的企业业务而言,具有非常实际的意义和应用价值。 ### 回答3: Java使用POI填充Word模板表格数据的方法主要分为以下几个步骤: 1.打开Word模板 使用POI的`XWPFDocument`类打开Word模板文件,例如: ```java XWPFDocument doc = new XWPFDocument(new FileInputStream("模板.docx")); ``` 2.获取Word表格 通过`XWPFDocument`对象获取模板中的表格,例如: ```java List<XWPFTable> tables = doc.getTables(); XWPFTable table = tables.get(0); // 获取第一个表格 ``` 3.获取表格行和单元格 通过`XWPFTable`对象获取表格的行和单元格,例如: ```java List<XWPFTableRow> rows = table.getRows(); XWPFTableRow row = rows.get(rowIndex); // 获取第rowIndex行 XWPFTableCell cell = row.getCell(cellIndex); // 获取第cellIndex个单元格 ``` 4.填充单元格数据 通过`XWPFTableCell.setText()`方法填充单元格数据,例如: ```java cell.setText(data); // 填充数据 ``` 5.保存Word文档 使用`XWPFDocument`的`write()`方法将填充数据Word文档保存到本地,例如: ```java doc.write(new FileOutputStream("填充后的文档.docx")); ``` 使用以上的步骤,就可以很方便地使用JavaPOI填充Word模板表格数据了。需要注意的是,填充数据的过程中,表格的行和列都需要对应好数据的长度。如果数据填充不全,可能会影响整个表格的显示效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值