java freemarker poi 用docx文档做模板填充数据 导出pdf

前言

公司有个功能,用docx文档做模板填充数据导出pdf的功能。首先大家要知道doc和docx是两个不同的东西,docx文件可以重命名为.zip文件并进行解压,由于我在网上找到的poi工具类中,AbstractXWPFConverter类的 convert(XWPFDocument XWPFDocument, OutputStream out, T options) 方法,只能将XWPFDocument类转为pdf,然后我发现XWPFDocument只能读取docx文件(doc文件如果直接改成docx文件XWPFDocument类是无法读取的,doc只有另存为docx才真正变成了docx文件,大家可以自行测试)。

参考链接:blog.csdn.net/buertianci/...
所以大致思路就是通过freemarker将数据填充进docx模板,然后将docx文件转换成pdf。

代码

maven

这里poi版本要选3.15 org.apache.poi.xwpf.converter.core 和 org.apache.poi.xwpf.converter.pdf 要选1.0.5 不然会有很多问题,版本号要选对,博主试了好多次才试出来的。

xml 复制代码
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.15</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.15</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>3.15</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>3.15</version>
</dependency>

<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>org.apache.poi.xwpf.converter.core</artifactId>
    <version>1.0.5</version>
</dependency>

<dependency>
    <groupId>fr.opensagres.xdocreport</groupId>
    <artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
    <version>1.0.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.9</version>
</dependency>

核心代码

该工具类WordTemplateUtil是将数据填充进入docx文件中,调用该方法即可getInputStreamWordDocx(Object data, String templateName, String origTemplateName)返回一个docx的inputStream第一个参数data是freemarker模板中需要填充的数据,key value形式即可,对象,map都可以,第二个参数是freemarker的模板(即从docx中提取的一个xml文件,将docx更名为zip以后解压,目录下的word/document.xml即是该模板),第三个参数是原docx文件。getInputStreamWordDocx(Object data, String templateName, String origTemplateName)就是将模板中的数据替换后得到一个xml文件,将docx中的word/document.xml替换成刚刚的xml文件即可获得一个有数据的docx文件。

java 复制代码
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;


@Slf4j
public class WordTemplateUtil {

    private Configuration configuration;

    /**
     * 模板文件的位置
     */
    private static String tempPath = "D:\tempfile\pdf\模板\";

    /**
     * 构造函数
     */
    public WordTemplateUtil() {
        configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        configuration.setDefaultEncoding("UTF-8");
        // springboot项目设置路径
        configuration.setClassForTemplateLoading(this.getClass(), "/templates");
        try {
            // 本地测试设置路径
            configuration.setDirectoryForTemplateLoading(new File(tempPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取模板
     *
     * @param name
     * @return
     * @throws Exception
     */
    public Template getTemplate(String name) throws Exception {
        return configuration.getTemplate(name);
    }

    /**
     * 获取word byte
     *
     * @param data
     * @param templateName
     * @return
     * @throws IOException
     */
    public InputStream getInputStreamWordDoc(Object data, String templateName) {
        return getFreemarkerInputStream(data, templateName);
    }

    /**
     * 获取word byte
     *
     * @param data             填充数据
     * @param templateName     模板名称
     * @param origTemplateName 原始模板名称
     * @return
     */
    public InputStream getInputStreamWordDocx(Object data, String templateName, String origTemplateName) {
        File outFile = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        ZipOutputStream zipout = null;
        ZipFile zipFile = null;
        try {
            // 临时文件路径
            String tempFilePathName = tempPath + UUID.randomUUID().toString().replaceAll("-", "") + ".docx";
            outFile = new File(tempFilePathName);
            outputStream = new FileOutputStream(outFile);
            // 内容模板
            ByteArrayInputStream xmlTemplateInput = getFreemarkerInputStream(data, templateName);
            //最初设计的模板
            String origDocxFilePathName = tempPath + origTemplateName;
            File origDocxFile = new File(origDocxFilePathName);
            if (!origDocxFile.exists()) {
                origDocxFile.createNewFile();
            }
            zipFile = new ZipFile(origDocxFile);
            Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
            zipout = new ZipOutputStream(outputStream);
            // 开始覆盖文档
            int len = -1;
            byte[] buffer = new byte[2 * 1024];
            while (zipEntrys.hasMoreElements()) {
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                if (!next.toString().contains("media")) {
                    zipout.putNextEntry(new ZipEntry(next.getName()));
                    if ("word/document.xml".equals(next.getName())) {
                        if (xmlTemplateInput != null) {
                            while ((len = xmlTemplateInput.read(buffer)) != -1) {
                                zipout.write(buffer, 0, len);
                            }
                            xmlTemplateInput.close();
                        }
                    } else {
                        while ((len = is.read(buffer)) != -1) {
                            zipout.write(buffer, 0, len);
                        }
                        is.close();
                    }
                }
            }
            inputStream = new FileInputStream(outFile);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (zipout != null) {
                try {
                    zipout.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (zipFile != null) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
            if (outFile != null) {
                outFile.delete();
            }
        }
        return inputStream;
    }

    /**
     * 获取模板字符串输入流
     *
     * @param data         参数
     * @param templateName 模板名称
     * @return
     */
    public ByteArrayInputStream getFreemarkerInputStream(Object data, String templateName) {
        ByteArrayInputStream inputStream = null;
        try {
            //获取模板
            Template template = getTemplate(templateName);
            StringWriter swriter = new StringWriter();
            //生成文件
            template.process(data, swriter);
            //这里一定要设置utf-8编码 否则导出的word中中文会是乱码
            inputStream = new ByteArrayInputStream(swriter.toString().getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return inputStream;
    }
}

获得了有数据的docx文件以后

通过XWPFDocument document = new XWPFDocument(inputStreamWordDocx);读取docx文件

使用PdfConverter.getInstance().convert(document, pdfOutputSteam, null);进行转换

java 复制代码
public static void main(String[] args) {
    WordTemplateUtil templateUtil = new WordTemplateUtil();
    Map<String, String> obj = new HashMap<>();
    try (
            InputStream inputStreamWordDocx = templateUtil.getInputStreamWordDocx(obj, "expertApplyForm.xml",
                    "expertApplyForm.docx");
            FileOutputStream pdfOutputSteam = new FileOutputStream("D:\tempfile\pdf\expertApplyForm.pdf");
    ) {
        // 读取docx文档
        XWPFDocument document = new XWPFDocument(inputStreamWordDocx);
        // 将docx文档转换为pdf
        PdfConverter.getInstance().convert(document, pdfOutputSteam, null);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

注意事项

  1. 像这种如果将上下的单元格合并,导出成pdf会出现格式错乱的问题,左右合并没问题,暂时还没找到解决方案。
  2. 如果有的导出变形了或者中文没有显示,大概率是字体问题,我docx使用的字体是宋体五号,注意就是宋体。打包到服务器的时候把window的宋体字体直接传到服务器上即可。不会弄的可以参考这篇文章www.cnblogs.com/wjsqqj/p/17...

至此完结撒花

创作不易 有用给个赞呗!

相关推荐
Pandaconda18 分钟前
【Golang 面试题】每日 3 题(三十九)
开发语言·经验分享·笔记·后端·面试·golang·go
编程小筑1 小时前
R语言的编程范式
开发语言·后端·golang
技术的探险家1 小时前
Elixir语言的文件操作
开发语言·后端·golang
ss2731 小时前
【2025小年源码免费送】
前端·后端
Ai 编码助手1 小时前
Golang 中强大的重试机制,解决瞬态错误
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的区块链
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的循环实现
开发语言·后端·golang
梁雨珈2 小时前
Lisp语言的物联网
开发语言·后端·golang
邓熙榆3 小时前
Logo语言的网络编程
开发语言·后端·golang
羊小猪~~7 小时前
MYSQL学习笔记(四):多表关系、多表查询(交叉连接、内连接、外连接、自连接)、七种JSONS、集合
数据库·笔记·后端·sql·学习·mysql·考研