一、 关键词
POI:Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能
POI-TL:基于Apache POI的Word模板引擎,通过插件机制使其具有高度扩展性
word格式:
1. doc:POI组件HWPF组件支持对doc文件的操作
2. docx:POI组件XWPF组件支持对docx文件的操作
3. POI-TL使用的是XWPF组件,所以更好的支持docx文件的操作。
二、 简单用法示例
1. 依赖
org.apache.poi
poi
4.1.0
org.apache.poi
poi-ooxml
4.1.0
com.deepoove
poi-tl
1.5.0
2. word模板
3. 初始化数据
public Map initWordData() throws Exception {
Map renderData = new HashMap<>();
// 文本标题
TextRenderData title = new TextRenderData("这是一个标题");
renderData.put("title", title);
// 文本副标题
TextRenderData subTitle = new TextRenderData("这是一个副标题");
renderData.put("sub_title", subTitle);
// 文本内容
StringBuilder contentValue = new StringBuilder()
.append("内容一")
.append(System.lineSeparator())
.append("内容二")
.append(System.lineSeparator())
.append("内容三")
.append(System.lineSeparator())
.append("内容四")
.append(System.lineSeparator())
.append("......");
TextRenderData content = new TextRenderData("00C1FF" ,contentValue.toString());
renderData.put("content", content);
// 作者信息
Map author = new HashMap<>();
author.put("name", "作者");
author.put("email", new HyperLinkTextRenderData("[email protected]","https://www.baidu.com"));
PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
author.put("avatar", avatar);
renderData.put("author", author);
// 表格1 (使用表格模板)
RowRenderData header = RowRenderData.build("单元格1", "单元格2");
RowRenderData row1 = RowRenderData.build("测试11", "测试12");
RowRenderData row2 = RowRenderData.build("测试21", "测试22");
List table1RenderData = new ArrayList<>();
table1RenderData.add(row1);
table1RenderData.add(row2);
MiniTableRenderData miniTableRenderData = new MiniTableRenderData(header, table1RenderData, MiniTableRenderData.WIDTH_A4_MEDIUM_FULL);
renderData.put("table1", miniTableRenderData);
// 表格2 (需要添加填充表信息策略)
List table2RenderData = new ArrayList<>();
table2RenderData.add(RowRenderData.build("张三","地址xxx"));
table2RenderData.add(RowRenderData.build("李四","地址xxx"));
renderData.put("table2", table2RenderData);
return renderData;
}
4. 定义现有表格填充数据策略
// 自定义表格填充策略
public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 填充的起始行,
// 注意:行号从0开始
private int row = 1;// 默认从第一行开始
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public DetailTablePolicy() {
}
public DetailTablePolicy(int row) {
this.row = row;
}
@Override
public void render(XWPFTable table, Object data) {
if (null == data) return;
// 对应渲染的表格数据
List table2RenderData = (List) data;
// 删除当前起始行
table.removeRow(row);
// 插入每一行
for (int i = 0,len = table2RenderData.size(); i < len; i++) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(row+i);
RowRenderData rowRenderData = table2RenderData.get(i);
for (int j = 0,len2 = rowRenderData.size(); j < len2; j++) insertNewTableRow.createCell();
// 渲染每一行数据
MiniTableRenderPolicy.Helper.renderRow(table, row + i, rowRenderData);
}
}
}
5. 导出word
public void exportWord() {
OutputStream os = null;
XWPFTemplate template = null;
try {
Map renderData = initWordData();
Configure.ConfigureBuilder builder = Configure.newBuilder();
// 使用自定义表格填充策略
builder.customPolicy("table2", new DetailTablePolicy(1));
Configure configure = builder.build();
// 模板文件
String templatePath = "D:\\Desktop\\SimpleTemplate.docx";
InputStream is = new FileInputStream(new File(templatePath));
// 输出位置
String outPath = "D:\\Desktop\\test.docx";
os = new FileOutputStream(new File(outPath));
// 编译,导入策略插件,并渲染数据
template = XWPFTemplate.compile(is, configure).render(renderData);
// 输出
template.write(os);
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (template != null) {
template.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
6. 运行结果
7. 注意:
当word中有表格需要渲染时(无论是{{#table}}
或者是自定义的表格),都需要加入poi-ooxml依赖,否则会报错
如图:(模板中存在table1和table2)
运行时会报错:
三、 自定义策略
1. 导出表格(带图片)
1.1. 依赖
同简单示例
1.2. 模板
1.3. 初始化数据
1.3.1. 注意:
● RowRenderData中存放的数据是String或TextRenderData,因此需要将PictureRenderData转为String类型
● 然后渲染数据时,再将String转为PictureRenderData类型
● PictureRenderData没有无参构造函数,无法使用com.fasterxml.jackson.databind.ObjectMapper进行相互转换。因此需要自定义一个类来实现PictureRenderData与String之间的转换
● 具体实现如下:
○ 将PictureRenderData转换为PicRenderDataConvert对象,然后将PicRenderDataConvert对象转为String类型写入RowRenderData中
PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
PicRenderDataConvert picRenderDataConvert = new PicRenderDataConvert(avatar);
List picRenderData = new ArrayList<>();
RowRenderData picRow = RowRenderData.build("张三", mapper.writeValueAsString(picRenderDataConvert));
○ 在渲染数据时,再获取PictureRenderData对象
PicRenderDataConvert picRenderDataConvert = mapper.readValue(text, PicRenderDataConvert.class);
// 转换图片
PictureRenderData pictureRenderData = picRenderDataConvert.convert();
if (pictureRenderData != null) {
com.deepoove.poi.policy.PictureRenderPolicy.Helper.renderPicture(par.createRun(), pictureRenderData);
}
1.3.2. 自定义PictureRenderData转换类
public class PicRenderDataConvert {
// 字段与PictureRenderData相同
private int width;
private int height;
private String path;
private transient byte[] data;
private String altMeta;
// 无参构造
public PicRenderDataConvert() {
}
public PicRenderDataConvert(PictureRenderData pictureRenderData) {
this.width = pictureRenderData.getWidth();
this.height = pictureRenderData.getHeight();
this.path = pictureRenderData.getPath();
this.altMeta = pictureRenderData.getAltMeta();
this.data = pictureRenderData.getData();
}
// 转换方法,获取PictureRenderData对象
public PictureRenderData convert() {
if (data == null && StringUtils.isEmpty(path)) {
return null;
}
PictureRenderData pictureRenderData = new PictureRenderData(width, height, path, data);
return pictureRenderData;
}
}
1.3.3. 初始化数据
public Map initWordData() throws Exception {
Map renderData = new HashMap<>();
PictureRenderData avatar = new PictureRenderData(100, 100, ".jpg", BytePictureUtils.getUrlBufferedImage("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1566878817&di=ecc0bc000703c669f5d660ca9bb70f17&src=http://img11.photophoto.cn/20090505/0035035789118626_s.jpg"));
// 渲染表格(包含图片)
List picRenderData = new ArrayList<>();
PicRenderDataConvert picRenderDataConvert = new PicRenderDataConvert(avatar);
RowRenderData picRow = RowRenderData.build("张三", mapper.writeValueAsString(picRenderDataConvert));
picRenderData.addAll(Arrays.asList(picRow, picRow));
renderData.put("pic_table", picRenderData);
return renderData;
}
1.4. 定义图片表格策略
public class DetailPicTablePolicy extends DynamicTableRenderPolicy {
// 填充数据其实行
// 注意:行号从0开始
private int row = 1;
ObjectMapper mapper = new ObjectMapper();
public DetailPicTablePolicy() {
}
public DetailPicTablePolicy(int row) {
this.row = row;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
@Override
public void render(XWPFTable xwpfTable, Object data) {
if (null == data) return;
List renderData = (List) data;
if (null != renderData) {
xwpfTable.removeRow(row);
// 循环插入行
for (int i = 0, len = renderData.size(); i < len; i++) {
XWPFTableRow insertNewTableRow = xwpfTable.insertNewTableRow(row + i);
RowRenderData rowRenderData = renderData.get(i);
// 插入每一个单元格
for (int j = 0, len2 = rowRenderData.getCellDatas().size(); j < len2; j++)
insertNewTableRow.createCell();
// 自定义渲染表格(包括图片)
renderCellPic(xwpfTable, row + i, rowRenderData);
}
}
}
private void renderCellPic(XWPFTable table, int row, RowRenderData rowData) {
if (null != rowData && rowData.size() > 0) {
XWPFTableRow tableRow = table.getRow(row);
ObjectUtils.requireNonNull(tableRow, "Row " + row + " do not exist in the table");
TableStyle rowStyle = rowData.getRowStyle();
List cellList = rowData.getCellDatas();
XWPFTableCell cell = null;
for (int i = 0; i < cellList.size(); ++i) {
cell = tableRow.getCell(i);
if (null == cell) {
RenderPolicy.logger.warn("Extra cell data at row {}, but no extra cell: col {}", row, cell);
break;
}
renderCell(cell, (CellRenderData) cellList.get(i), rowStyle);
}
}
}
public void renderCell(XWPFTableCell cell, CellRenderData cellData, TableStyle rowStyle) {
TableStyle cellStyle = null == cellData.getCellStyle() ? rowStyle : cellData.getCellStyle();
if (null != cellStyle && null != cellStyle.getBackgroundColor()) {
cell.setColor(cellStyle.getBackgroundColor());
}
TextRenderData renderData = cellData.getRenderData();
String cellText = renderData.getText();
if (!StringUtils.isBlank(cellText)) {
CTTc ctTc = cell.getCTTc();
CTP ctP = ctTc.sizeOfPArray() == 0 ? ctTc.addNewP() : ctTc.getPArray(0);
XWPFParagraph par = new XWPFParagraph(ctP, cell);
StyleUtils.styleTableParagraph(par, cellStyle);
String text = renderData.getText();
String[] fragment = text.split("\\n", -1);
if (fragment.length <= 1) {
try {
PicRenderDataConvert picRenderDataConvert = mapper.readValue(text, PicRenderDataConvert.class);
// 渲染图片
PictureRenderData pictureRenderData = picRenderDataConvert.convert();
if (pictureRenderData != null) {
com.deepoove.poi.policy.PictureRenderPolicy.Helper.renderPicture(par.createRun(), pictureRenderData);
}
} catch (Exception e) {
// 渲染文本
com.deepoove.poi.policy.TextRenderPolicy.Helper.renderTextRun(par.createRun(), renderData);
}
} else {
for (int j = 0; j < fragment.length; ++j) {
if (0 != j) {
par = cell.addParagraph();
StyleUtils.styleTableParagraph(par, cellStyle);
}
XWPFRun run = par.createRun();
StyleUtils.styleRun(run, renderData.getStyle());
run.setText(fragment[j]);
}
}
}
}
}
1.5. 浏览器导出word
public void exportWord(HttpServletRequest request,
HttpServletResponse response) throws Exception {
Map renderData = initWordData();
Configure.ConfigureBuilder builder = Configure.newBuilder();
// 表格(含图片)渲染策略
builder.customPolicy("pic_table", new DetailPicTablePolicy(1));
Configure configure = builder.build();
String fileName = "测试文档";
String templatePath = "D:\\Desktop\\PicTableTemplate.docx";
renderWord(request, response, fileName, templatePath, renderData, configure);
}
public void renderWord(HttpServletRequest request,
HttpServletResponse response,
String fileName,
String templatePath,
Map renderData,
Configure configure) {
String codedFileName = "临时文档.docx";
ServletOutputStream out = null;
XWPFTemplate template = null;
try {
if (!StringUtils.isEmpty(fileName)) {
codedFileName = fileName + ".docx";
}
if (request.getHeader("USER-AGENT").toLowerCase().indexOf("msie") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("rv:11.0") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("edge") > 0) {
codedFileName = URLEncoder.encode(codedFileName, "UTF8");
} else {
codedFileName = new String(codedFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
}
response.setHeader("content-disposition", "attachment;filename=" + codedFileName);
InputStream is = new FileInputStream(new File(templatePath));
if (configure == null) {
template = XWPFTemplate.compile(is).render(renderData);
} else {
template = XWPFTemplate.compile(is, configure).render(renderData);
}
out = response.getOutputStream();
template.write(out);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (template != null) {
template.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.6. 运行结果
四、 总结
1. 依赖完整
必须存在依赖,否则无法进行word中的表格渲染
2. 插件机制实现可扩展的word导出
3. POI-TL支持文件操作
4. 参考文档
● POI-TL入门
● Apache POI Word(docx) 入门
● Apache POI