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...

至此完结撒花

创作不易 有用给个赞呗!

相关推荐
AskHarries2 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion3 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp4 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder4 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚5 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心5 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴6 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲6 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心6 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky7 小时前
本地摄像头视频流在html中打开
前端·后端·html