Java根据模板生成word
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
首先生成模板并进行测试:
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;
在模板创建好之后渲染图片:
看一下最终效果:
这样生成好之后就可以让浏览器直接下载了。
最后给一下完整代码:
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");
}
参考: