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模板的占位符和标签都被替换掉了。