Java 根据模板生成Word

书接上文,要做的功能其实是把条形码word文档下载,在生成条形码之后,就是写入word。(条形码生成见此文章

本文没有采用原始的poi,而是使用了poi-tl,一个poi的封装,可以更好的根据模板生成word文档。

首先是maven依赖:

<!-- 截止2021-10-1,最新版是1.10.0,本文同时也使用了最新版的api -->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.10.0</version>
</dependency>

引入时注意poi的版本是否冲突(这就是另一个坑了,详见我的[下一篇博文(还没写)]())

这个是官方文档(注意版本),写的其实已经很详细了,本文只是根据文档写出一个例子。

本例子主要参考了官方文档中5.3、6.2、8.7、8.8、9.2、9.3等说明。
先说需求:把生成的条形码放到word中方便打印,每行两个,条形码数量不定。

首先,由于条形码数量不定,因此模板应该是动态生成的,因此

首先创建一个基础word模板,如图所示:

初始word模板

我们要做的是:初始模板word -> 渲染文字 -> 新模板word -> 渲染图片 -> 带图片的word

首先生成模板并进行测试:

package fun.psgame.test.util;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.DocumentRenderData;
import com.deepoove.poi.data.Documents;
import com.deepoove.poi.data.Paragraphs;
import com.deepoove.poi.policy.DocumentRenderPolicy;
import org.springframework.core.io.ClassPathResource;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WordUtils {
    /**
     * 图片的tag名
     */
    private static final String PICTURE_TAG_NAME = "pic";

    /**
     * 初始多个图片的tag名
     */
    private static final String PICTURES_TAG_NAME = "pics";

    /**
     * resources下初始模板文件路径
     */
    private static final String TEMPLATE_PATH = "wordTemplate/studentBarCodeTemplate.docx";

    /**
     * 生成word
     *
     * @param imageList 要放入word的图片
     * @return pio-tl的word对象
     */
    public static XWPFTemplate generateWord(List<BufferedImage> imageList) {
        ClassPathResource resource = new ClassPathResource(TEMPLATE_PATH);
        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException("模板读取失败", e);
        }

        // 用初始模板生成新的模板
        Map<String, Object> firstRenderData = new HashMap<>();
        // ① 官方提供的生成方式
        Configure config = Configure.builder().bind(PICTURES_TAG_NAME, new DocumentRenderPolicy()).build();
        // 读取模板
        XWPFTemplate template = XWPFTemplate.compile(inputStream, config);
        // 创建新模板内容
        Documents.DocumentBuilder docBuilder = Documents.of();
        for (int i = 0; i < imageList.size(); i++) {
            docBuilder.addParagraph(Paragraphs.of(getPictureTemplate(i)).create());
        }
        DocumentRenderData documentRenderData = docBuilder.create();
        firstRenderData.put(PICTURES_TAG_NAME, documentRenderData);

        template.render(firstRenderData);

        return template;
    }

    /**
     * 获取图片占位符
     * @return
     */
    private static String getPictureTemplate(int index) {
        // 图片的占位符格式:{{@var}}
        return "{{@" + PICTURE_TAG_NAME + index + "}}";
    }
}

然后可以测试一下生成的模板:

    @Test
    public void generateWordTest() throws IOException {
        List<BufferedImage> imageList = new ArrayList<>();
        int imageCount = 10;

        // 准备要放入word的图片
        // 这里使用了之前写的条形码生成工具,实际随便放什么图片都可以
        for (int i = 0; i < imageCount; i++) {
            String stuCode = String.format("%09d", i);
            imageList.add(BarCodeUtils.getBarCodeWithWords(stuCode,
                    "学号:" + stuCode,
                    "三年二班",
                    "王宝强"));
        }

        XWPFTemplate xwpfTemplate = WordUtils.generateWord(imageList);

        xwpfTemplate.writeToFile("D:/Temp/学生条形码.docx");
    }

可以看一下生成的模板:

生成的模板

由于我们想要的是一行放2个图片,使用Documents.DocumentBuilder.addParagraph()方法会出现不想要的换行符

因此采用自定义插件的方式生成新模板

把模板的创建方式修改为如下:

// ② 使用自定义插件的方式生成新模板
Configure config = Configure.builder().bind(PICTURES_TAG_NAME, (eleTemplate, data, template) -> {
    XWPFRun run = ((RunTemplate) eleTemplate).getRun();
    StringBuilder tempSb = new StringBuilder();

    for (int i = 0; i < imageList.size(); i++) {
        // 每两个加一个换行符
        if (i != 0 && (i % 2) == 0) {
            tempSb.append(System.lineSeparator());
        }

        tempSb.append(getPictureTemplate(i));
    }


    run.setText(tempSb.toString(), 0);
}).build();
// 读取模板
XWPFTemplate template = XWPFTemplate.compile(inputStream, config);

这次看一下结果:

自定义生成的模板

可以看到换行符没有生效,先不用管,把图片渲染上去再说:

// 读取模板
XWPFTemplate template = XWPFTemplate.compile(inputStream, config);

template.render(firstRenderData);

// 创建一个空白文档
XWPFTemplate xwpfTemplate = XWPFTemplate.create(Documents.of().create());
// 把渲染好的模板内容加载进去
xwpfTemplate.reload(template.getXWPFDocument());
Map<String, Object> secondData = new HashMap<>();

for (int i = 0; i < imageList.size(); i++) {
    secondData.put(PICTURE_TAG_NAME + i,
            Pictures.ofBufferedImage(imageList.get(i), PictureType.JPEG)
                    .size(250, 180)
                    .create());
}

xwpfTemplate.render(secondData);

return xwpfTemplate;

在模板创建好之后渲染图片:

看一下最终效果:

最终word文档效果

这样生成好之后就可以让浏览器直接下载了。

最后给一下完整代码:


WordUtils

package fun.psgame.test.util;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.*;
import com.deepoove.poi.policy.DocumentRenderPolicy;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.springframework.core.io.ClassPathResource;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WordUtils {
    /**
     * 图片的tag名
     */
    private static final String PICTURE_TAG_NAME = "pic";

    /**
     * 初始多个图片的tag名
     */
    private static final String PICTURES_TAG_NAME = "pics";

    /**
     * resources下初始模板文件路径
     */
    private static final String TEMPLATE_PATH = "wordTemplate/studentBarCodeTemplate.docx";

    /**
     * 生成word
     *
     * @param imageList 要放入word的图片
     * @return pio-tl的word对象
     */
    public static XWPFTemplate generateWord(List<BufferedImage> imageList) {
        ClassPathResource resource = new ClassPathResource(TEMPLATE_PATH);
        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException e) {
            throw new RuntimeException("模板读取失败", e);
        }

        // 用初始模板生成新的模板
        Map<String, Object> firstRenderData = new HashMap<>();
//        // ① 官方提供的生成方式
//        Configure config = Configure.builder().bind(PICTURES_TAG_NAME, new DocumentRenderPolicy()).build();
//        // 读取模板
//        XWPFTemplate template = XWPFTemplate.compile(inputStream, config);
//        // 创建新模板内容
//        Documents.DocumentBuilder docBuilder = Documents.of();
//        for (int i = 0; i < imageList.size(); i++) {
//            docBuilder.addParagraph(Paragraphs.of(getPictureTemplate(i)).create());
//        }
//        DocumentRenderData documentRenderData = docBuilder.create();
//        firstRenderData.put(PICTURES_TAG_NAME, documentRenderData);
        // ② 使用自定义插件的方式生成新模板
        Configure config = Configure.builder().bind(PICTURES_TAG_NAME, (eleTemplate, data, template) -> {
            XWPFRun run = ((RunTemplate) eleTemplate).getRun();
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < imageList.size(); i++) {
                // 每两个加一个换行符
                // 不加换行符会导致最后一行出现奇怪的布局
                if (i != 0 && (i % 2) == 0) {
                    sb.append(System.lineSeparator());
                }

                sb.append(getPictureTemplate(i));
            }

            run.setText(sb.toString(), 0);
        }).build();
        // 读取模板
        XWPFTemplate template = XWPFTemplate.compile(inputStream, config);

        template.render(firstRenderData);

        // 创建一个空白文档
        XWPFTemplate xwpfTemplate = XWPFTemplate.create(Documents.of().create());
        // 把渲染好的模板内容加载进去
        xwpfTemplate.reload(template.getXWPFDocument());
        Map<String, Object> secondData = new HashMap<>();

        for (int i = 0; i < imageList.size(); i++) {
            secondData.put(PICTURE_TAG_NAME + i,
                    Pictures.ofBufferedImage(imageList.get(i), PictureType.JPEG)
                            .size(250, 180)
                            .create());
        }

        xwpfTemplate.render(secondData);

        return xwpfTemplate;
    }

    /**
     * 获取图片占位符
     * @return
     */
    private static String getPictureTemplate(int index) {
        // 图片的占位符格式:{{@var}}
        return "{{@" + PICTURE_TAG_NAME + index + "}}";
    }
}

测试用例:


测试方法

@Test
public void generateWordTest() throws IOException {
    List<BufferedImage> imageList = new ArrayList<>();
    int imageCount = 10;

    // 准备要放入word的图片
    for (int i = 0; i < imageCount; i++) {
        String stuCode = String.format("%09d", i + 1);
        // 这里的image使用的是之前写好的工具类,实际随便什么图片都可以
        imageList.add(BarCodeUtils.getBarCodeWithWords(stuCode,
                "学号:" + stuCode,
                "三年二班",
                "王宝强" + i));
    }

    XWPFTemplate xwpfTemplate = WordUtils.generateWord(imageList);

    xwpfTemplate.writeToFile("D:/Temp/学生条形码.docx");
}


参考:

标签: Java

添加新评论