likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 16

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