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");
}
參考: