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模板如下:

java使用docx4j+freemarker+echarts实现向word模板填充内容并插入echart生成的表格图片

需求把对应的占位符替换为咱们需求的内容,并且在指定的赤色方框区域内刺进咱们想要的图片。图片咱们能够采用书签的方法,经过书签来进行占位符。

留意: 运用如${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();
    }
}

需求把对应的途径替换掉,履行后的效果如下:

java使用docx4j+freemarker+echarts实现向word模板填充内容并插入echart生成的表格图片
能够看到咱们这个word模板的占位符和标签都被替换掉了。