前言
公司有个功能,用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();
}
}
注意事项
- 像这种如果将上下的单元格合并,导出成pdf会出现格式错乱的问题,左右合并没问题,暂时还没找到解决方案。
- 如果有的导出变形了或者中文没有显示,大概率是字体问题,我docx使用的字体是宋体五号,注意就是宋体。打包到服务器的时候把window的宋体字体直接传到服务器上即可。不会弄的可以参考这篇文章www.cnblogs.com/wjsqqj/p/17...
至此完结撒花
创作不易 有用给个赞呗!