java使用docx4j+freemarker+echarts实现向word模板填充内容并插入echart生成的表格图片
1.需求背景与思路
现在有个需求如下:给定了一个word模板,需要向模板的一些字段替换为指定的字段(此时我们使用docx4j),模板还需要生成一个动态的表格(需要一个echarts模板,通过freemarker向模板中渲染内容,在通过wkhtmltoimage把模板转换为图片并插入到word中)。
2.准备工作
2.1 引入依赖
<!-- freemarker依赖 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!-- docx4j依赖 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>8.1.6</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>8.1.7</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>8.1.7</version>
</dependency>
2.2 下载wkhtmltox
wkhtmltox 是一个开源的命令行工具,可以将 HTML 转换成 PDF 和各种图像格式。它是基于 QT 和 WebKit 开发的,支持多种操作系统,比如 Windows、Linux 和 Mac OS X。wkhtmltox 使用简单,无需进行安装,只需执行二进制文件即可。
我们需要将html转换为图片,我们去官网下载安装即可。 wkhtmltopdf.org/downloads.h…
3.实现代码
3.1 使用freemarker+echarts渲染html模板,使用$进行占位符,实际为我们想要的表格json数据。
<html>
<head>
<title>ECharts Bar Chart</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script>
</head>
<body>
<div id="chart" style="width: 600px; height: 400px;"></div>
<script>
var chart = echarts.init(document.getElementById('chart'));
var option = ${jsonArray};
chart.setOption(option);
</script>
</body>
</html>
java的freemarker替换html的代码如下:
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class ChartReader {
public static void main(String[] args) {
// 配置Freemarker
try {
Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
//设置模板路径 在SpringBoot项目中我们可以通过类加载的形式去加载这个文件
cfg.setDirectoryForTemplateLoading(new File("D:\study\project\hobby-item\src\main\resources\templates"));
cfg.setDefaultEncoding("UTF-8");
// 加载模板
Template template = cfg.getTemplate("echart.ftl"); // 替换为你的模板文件名
// 准备数据 我们通过freemarker渲染的形式去把我们需要的数据渲染到echarts中
Map<String, Object> data = new HashMap<>();
data.put("jsonArray","{\n" +
" xAxis: {\n" +
" type: 'category',\n" +
" data: ['A', 'B', 'C', 'D', 'F']\n" +
" },\n" +
" yAxis: {\n" +
" type: 'value'\n" +
" },\n" +
" series: [{\n" +
" data: [10, 20, 30, 40, 50],\n" +
" type: 'bar'\n" +
" }]\n" +
" }");
//设置我们的html生成路径 如果需要传递数据给模板,可以在这里添加数据到 data 对象中
File outputHtml = new File("C:\Users\Administrator\Desktop\output.html");
Writer htmlWriter = new FileWriter(outputHtml);
template.process(data, htmlWriter);
htmlWriter.close();
//设置我们的html->image的生成路径 在这里继续处理生成图片的逻辑
File image = new File(outputHtml.getParent() + "\output.png");
HtmlToImage.convert(outputHtml.getAbsolutePath(),image.getAbsolutePath()+"\output.png");
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
}
}
3.2 使用wkhtmltoiamge可执行程序生成图片
import java.io.File;
public class HtmlToImage {
// wkhtmltoimage在系统中的路径
private static String toImgTool = "D:\study\wkhtmltox\wkhtmltopdf\bin\wkhtmltoimage.exe";
/**
* html转pdf
*
* @param srcPath html路径,可以是硬盘上的路径,也可以是网络路径
* @param destPath image保存路径
* @return 转换成功返回true
*/
public static boolean convert(String srcPath, String destPath) {
File file = new File(destPath);
File parent = file.getParentFile();
// 如果pdf保存路径不存在,则创建路径
if (!parent.exists()) {
parent.mkdirs();
}
StringBuilder cmd = new StringBuilder();
cmd.append(toImgTool);
cmd.append(" ");
cmd.append(" --javascript-delay 3000 ");
cmd.append(" --disable-local-file-access ");
cmd.append(srcPath);
cmd.append(" ");
cmd.append(destPath);
boolean result = true;
try {
Process proc = Runtime.getRuntime().exec(cmd.toString());
proc.waitFor();
} catch (Exception e) {
result = false;
e.printStackTrace();
}
return result;
}
如果我们的js文件是放在本地进行进入的,我们记得要加上对应的参数,--javascript-delay 3000为设置js文件的延迟加载时间,并且disable-local-file-access允许加载我们本地的允许本地文件加载其他的本地文件,不然可能会报权限阻塞的错误。 当然也有一些别的参数,我们可以查看帮助文档,定制我们所需要的参数。
3.3 word模板替换和插入
假设我们的word模板如下:
需要把对应的占位符替换为我们需要的内容,并且在指定的红色方框区域内插入我们想要的图片。图片我们可以采用书签的方式,通过书签来进行占位符。
注意: 使用如${sex}这种占位符,建议从一个txt文本文件中,先写好,然后在复制到word中,注意复制到word中的占位符一定不能出现任何格式(我就是遇到了一个因为出现了波浪线导致替换不成功)!!!
代码如下:
import org.apache.commons.io.IOUtils;
import org.docx4j.TraversalUtil;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.RangeFinder;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Docx4j {
public static void main(String[] args) {
Map<String, String> data = new HashMap<>();
data.put("name", "张三");
data.put("sex", "女");
data.put("total", "20");
try {
replaceData(data);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 加载模板并替换数据
*
* @param data
* @return
* @throws Exception
*/
public static void replaceData(Map<String, String> data) throws Exception {
//这个是我们word模板的路径
final String TEMPLATE_NAME = "C:\Users\Administrator\Desktop\test.docx";
InputStream templateInputStream = new FileInputStream(TEMPLATE_NAME);
//加载模板文件并创建WordprocessingMLPackage对象
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
// 获取指定位置的段落对象
Document wmlDoc = documentPart.getJaxbElement();
Body body = wmlDoc.getBody();
// 提取正文中所有段落
List<Object> paragraphs = body.getContent();
// 提取书签并创建书签的游标
RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
new TraversalUtil(paragraphs, rt);
// 遍历书签
for (CTBookmark bm : rt.getStarts()) {
//这儿可以对单个书签进行操作,也可以用一个map对所有的书签进行处理
if (bm.getName().equals("book1")) {
//这里是我们图片的路径 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
InputStream is = new FileInputStream("D:\image\wallpaper\3.png");
byte[] bytes = IOUtils.toByteArray(is);
// 穿件一个行内图片
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
// createImageInline函数的前四个参数我都没有找到具体啥意思,,,,
// 最有一个是限制图片的宽度,缩放的依据
Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 30000);
// 获取该书签的父级段落
P p = (P) (bm.getParent());
ObjectFactory factory = new ObjectFactory();
// R对象是匿名的复杂类型
R run = factory.createR();
// drawing理解为画布
Drawing drawing = factory.createDrawing();
drawing.getAnchorOrInline().add(inline);
run.getContent().add(drawing);
p.getContent().add(run);
}
}
documentPart.variableReplace(data);
OutputStream os = new FileOutputStream("C:\Users\Administrator\Desktop\newtest.docx");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
wordMLPackage.save(outputStream);
outputStream.writeTo(os);
os.close();
outputStream.close();
templateInputStream.close();
}
}
需要把对应的路径替换掉,执行后的效果如下:
可以看到我们这个word模板的占位符和标签都被替换掉了。
转载自:https://juejin.cn/post/7247848097241153592