poi-tl是干嘛的?
poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档,
支持:
1.单系列图表指的是饼图(3D饼图)、圆环图等。
2.多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、散点图等。
3.组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。
等等…
官网地址:Poi-tl Documentation
-
目录
poi-tl是干嘛的?
文章目录
展示
一、效果展示
二、模板
三、使用步骤
1.引包
2.代码
四.测试及数据处理
五.总结
展示
一、效果展示
二、模板
三、使用步骤
1.引包
该案例使用poi-tl版本: 1.12.0
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
该案例引入的包
<dependencies>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
</dependencies>
2.代码
ServerTablePolicy:主要处理合并数据表格工具类
MergeRowsRenderPolicy:含图片表格合并工具类
MultiImageRenderPolicy:注册添加前缀为’&’的自定义插件:
2.1.ServerTablePolicy:主要处理合并数据表格工具类
public class ServerTablePolicy extends DynamicTableRenderPolicy {
@Override
public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
if (null == tableData) {
return;
}
// 参数数据声明
ServerTableData serverTableData = (ServerTableData) tableData;
List<RowRenderData> serverDataList = serverTableData.getServerDataList();
List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();
Integer mergeColumn = serverTableData.getMergeColumn();
if (CollectionUtils.isNotEmpty(serverDataList)) {
// 先删除一行, demo中第一行是为了调整 三线表 样式
xwpfTable.removeRow(1);
// 行从中间插入, 因此采用倒序渲染数据
for (int i = serverDataList.size() - 1; i >= 0; i--) {
XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
newRow.setHeight(400);
//每一行填充多少个单元格
for (int j = 0; j < serverDataList.get(0).getCells().size(); j++) {
newRow.createCell();
}
// 渲染一行数据
TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
}
// 处理合并
for (int i = 0; i < serverDataList.size(); i++) {
// 获取要合并的名称那一列数据 mergeColumn代表要合并的列,从0开始
String typeNameData = serverDataList.get(i).getCells().get(mergeColumn).getParagraphs().get(0).getContents().get(0).toString();
for (int j = 0; j < groupDataList.size(); j++) {
String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));
//如果只有一条数据不进行合并处理
if (listSize!=1) {
// 若匹配上 就直接合并
if (typeNameTemplate.equals(typeNameData)) {
//如果合并列不为空直接合并,否则提示输入要合并的列
if (CollectionUtils.isNotEmpty(serverTableData.getMergeColumnList())) {
for (Integer mergeColumns : serverTableData.getMergeColumnList()) {
TableTools.mergeCellsVertically(xwpfTable, mergeColumns, i + 1, i + listSize);
}
groupDataList.remove(j);
break;
} else {
throw new Exception("要合并列不能为空!");
}
}
}
}
}
}
}
}
2.2.MergeRowsRenderPolicy:含图片表格合并工具类
public class MergeRowsRenderPolicy implements RenderPolicy {
private final String prefix;
private final String suffix;
private final boolean onSameLine;
public MergeRowsRenderPolicy() {
this(false);
}
public MergeRowsRenderPolicy(boolean onSameLine) {
this("[", "]", onSameLine);
}
public MergeRowsRenderPolicy(String prefix, String suffix) {
this(prefix, suffix, false);
}
public MergeRowsRenderPolicy(String prefix, String suffix, boolean onSameLine) {
this.prefix = prefix;
this.suffix = suffix;
this.onSameLine = onSameLine;
}
@Override
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
RunTemplate runTemplate = (RunTemplate) eleTemplate;
XWPFRun run = runTemplate.getRun();
try {
if (!TableTools.isInsideTable(run)) {
throw new IllegalStateException(
"The template tag " + runTemplate.getSource() + " must be inside a table");
}
XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
XWPFTable table = tagCell.getTableRow().getTable();
run.setText("", 0);
int templateRowIndex = getTemplateRowIndex(tagCell);
if (data instanceof Iterable) {
Iterator<?> iterator = ((Iterable<?>) data).iterator();
XWPFTableRow templateRow = table.getRow(templateRowIndex);
int insertPosition = templateRowIndex;
TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
boolean firstFlag = true;
int index = 0;
boolean hasNext = iterator.hasNext();
while (hasNext) {
Object root = iterator.next();
hasNext = iterator.hasNext();
insertPosition = templateRowIndex++;
table.insertNewTableRow(insertPosition);
setTableRow(table, templateRow, insertPosition);
// double set row
XmlCursor newCursor = templateRow.getCtRow().newCursor();
newCursor.toPrevSibling();
XmlObject object = newCursor.getObject();
XWPFTableRow nextRow = new XWPFTableRow((CTRow) object, table);
if (!firstFlag) {
// update VMerge cells for non-first row
List<XWPFTableCell> tableCells = nextRow.getTableCells();
for (XWPFTableCell cell : tableCells) {
CTTcPr tcPr = TableTools.getTcPr(cell);
CTVMerge vMerge = tcPr.getVMerge();
if (null == vMerge) {continue;}
if (STMerge.RESTART == vMerge.getVal()) {
vMerge.setVal(STMerge.CONTINUE);
}
}
} else {
firstFlag = false;
}
setTableRow(table, nextRow, insertPosition);
RenderDataCompute dataCompute = template.getConfig()
.getRenderDataComputeFactory()
.newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
List<XWPFTableCell> cells = nextRow.getTableCells();
cells.forEach(cell -> {
List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
new DocumentProcessor(template, resolver, dataCompute).process(templates);
});
}
// 处理连续数据相同行合并
int currPos = getTemplateRowIndex(tagCell);
XWPFTableRow prevRow = table.getRow(currPos);
while (currPos <= insertPosition) {
currPos++;
XWPFTableRow nextRow = table.getRow(currPos);
List<XWPFTableCell> cells = nextRow.getTableCells();
int currCellPos = 0;
while (currCellPos < cells.size()) {
CTTcPr tcPrPrev = TableTools.getTcPr(prevRow.getCell(currCellPos));
CTVMerge vMergePrev = tcPrPrev.getVMerge();
if (null == vMergePrev) {
vMergePrev = tcPrPrev.addNewVMerge();
vMergePrev.setVal(STMerge.RESTART);
}
CTTcPr tcPrNext = TableTools.getTcPr(nextRow.getCell(currCellPos));
CTVMerge vMergeNext = tcPrNext.getVMerge();
if (null == vMergeNext) {
vMergeNext = tcPrNext.addNewVMerge();
vMergeNext.setVal(STMerge.RESTART);
}
// 判断上下两行的字符串值是否一样,一样就合并
if (StringUtils.equalsIgnoreCase(
prevRow.getCell(currCellPos).getText(),
nextRow.getCell(currCellPos).getText())) {
vMergeNext.setVal(STMerge.CONTINUE);
}
currCellPos++;
}
prevRow = nextRow;
}
}
table.removeRow(templateRowIndex);
afterloop(table, data);
} catch (Exception e) {
throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
}
}
private int getTemplateRowIndex(XWPFTableCell tagCell) {
XWPFTableRow tagRow = tagCell.getTableRow();
return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
}
protected void afterloop(XWPFTable table, Object data) {
}
@SuppressWarnings("unchecked")
private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
rows.set(pos, templateRow);
table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
}
private int getRowIndex(XWPFTableRow row) {
List<XWPFTableRow> rows = row.getTable().getRows();
return rows.indexOf(row);
}
}
2.3.MultiImageRenderPolicy:注册添加前缀为’&’的自定义插件:
public class MultiImageRenderPolicy extends AbstractRenderPolicy<Collection<PictureRenderData>> {
@Override
protected void afterRender(RenderContext<Collection<PictureRenderData>> context) {
clearPlaceholder(context, true);
}
@Override
public void doRender(RenderContext<Collection<PictureRenderData>> context) throws Exception {
IRunBody iRunBody = context.getRun().getParent();
if (iRunBody instanceof XWPFParagraph) {
XWPFParagraph p = (XWPFParagraph) iRunBody;
Collection<PictureRenderData> data = context.getData();
if (CollectionUtils.isNotEmpty(data)) {
for (PictureRenderData pic : data) {
PictureRenderPolicy.Helper.renderPicture(p.createRun(), pic);
}
}
}
}
}
2.4. 数据表格合并实体类
@Data
public class ServerTableData {
/**
* 携带表格中真实数据
*/
private List<RowRenderData> serverDataList;
/**
* 携带要分组的信息
*/
private List<Map<String, Object>> groupDataList;
/**
* 需要合并的列,从0开始
*/
private Integer mergeColumn;
/**
* 具体要合并的列集合
*/
private List<Integer> mergeColumnList;
}
四.测试及数据处理
public class WordExport {
public static void main(String[] args) throws IOException {
// 获取模板文件流
String filePath = "D:\\poi-tl\\template.docx";
String targetPath = "D:\\poi-tl\\templateTest.docx";
String picturePath = "D:\\poi-tl\\1_1.jpg";
String picturePaths="D:\\poi-tl\\1_1.jpg,D:\\poi-tl\\2_2.jpg,D:\\poi-tl\\3_3.jpg,D:\\poi-tl\\4_4.jpg";
String httpPicturePath = "http://deepoove.com/images/icecream.png";
String httpPicturePaths = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";
//单个图片数据模板
PictureRenderData picture = Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
.size(400, 300).create();
//多张图片数据模板
ArrayList<JSONObject> pictures = new ArrayList<JSONObject>() {
{
add(new JSONObject().fluentPut("url",picturePath));
add(new JSONObject().fluentPut("url", httpPicturePath));
}
};
//多张图片处理
List<Map<String, Object>> pictureList = new ArrayList(){{
for (int i = 0; i < 2; i++) {
add(new HashMap<String,Object>(){{
put("url", Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
.size(220,150).create());
}});
}}};
//如果表格内不需要合并数据直接put("",new ArrayList<string,object>/new ArrayList<实体类>)
//单元格多图片
ArrayList<JSONObject> arrayList = getArrayList(picturePaths,httpPicturePaths);
// 伪造一个表格数据
//具体要合并的列
List<Integer> mergeColumnList = Arrays.asList(0,3);
ServerTableData oneTable = getServerTableData(mergeColumnList);
//表格绑定
Configure config = Configure.builder()
//注册添加前缀为'&'的自定义插件:
.addPlugin('&', new MultiImageRenderPolicy())
//含图片数据表格合并同列相邻的单元格
.bind("items", new MergeRowsRenderPolicy())
//数据表格可指定合并列
.bind("oneTable",new ServerTablePolicy())
.build();
XWPFTemplate template = XWPFTemplate.compile(filePath, config).render(
new HashMap<String, Object>(){{
//单张图片
put("image", picture);
//单张图片描述
put("imageDesc","这是图片描述");
//多张图片1
put("pictures", pictures);
//多张图片2
put("pictureList", pictureList);
//合并表格
put("oneTable",oneTable);
//单元格多个图片
put("items", arrayList);
}});
//输出网络流 本地导出
template.writeAndClose(new FileOutputStream(targetPath));
//网络导出
//exportFile(filePath,hashMap,config);
}
/**
* 表格合并数据处理
* @param mergeColumnList 要合并的列集合
* @return 数据集合
*/
private static ServerTableData getServerTableData(List<Integer> mergeColumnList) {
ServerTableData serverTableData = new ServerTableData();
List<RowRenderData> serverDataList = new ArrayList<>();
for (int j = 0; j < 5; j++) {
String typeName;
if (j > 1) {
typeName = "张三";
serverDataList.add(Rows.of(typeName, "男", "3", "喝酒").center().create());
}else {
typeName = "李四";
serverDataList.add(Rows.of(typeName, "女", "4", "逛街").center().create());
}
}
List<Map<String, Object>> groupDataList = new ArrayList<>();
Map<String, Object> groupData1 = new HashMap<>();
//typeName是张三的数据有两条
groupData1.put("typeName", "张三");
groupData1.put("listSize", "3");
Map<String, Object> groupData2 = new HashMap<>();
//typeName是李四的数据有三条
groupData2.put("typeName", "李四");
groupData2.put("listSize", "2");
groupDataList.add(groupData1);
groupDataList.add(groupData2);
//具体合并的列
serverTableData.setMergeColumnList(mergeColumnList);
//表格中数据
serverTableData.setServerDataList(serverDataList);
//分组的信息
serverTableData.setGroupDataList(groupDataList);
//从哪列开始合并
serverTableData.setMergeColumn(0);
return serverTableData;
}
/**
* 含图片表格数据处理, 可单独抽成工具类,官网支持多种不同图片处理
* @param picturePaths java图片路径
* @param httpPicturePaths 网络图片路径
* @return 数据集合
* @throws IOException 抛出io流异常
*/
public static ArrayList<JSONObject> getArrayList(String picturePaths,String httpPicturePaths) throws IOException {
String[] picturePathArr = picturePaths.split(",");
String[] httpPicturePathArr = httpPicturePaths.split(",");
word模板中使用 {{&images}}来插入多张图片了
ArrayList<JSONObject> arrayList = new ArrayList<JSONObject>() {{
//网络图片
add(new JSONObject().fluentPut("title", "第一组图片")
.fluentPut("detail", "这是第1.1组图片描述")
.fluentPut("images", new ArrayList<PictureRenderData>() {{
for (String httpPicturePath : httpPicturePathArr) {
add(Pictures.ofUrl(httpPicturePath).create());
}
}}));
//网络图片
add(new JSONObject().fluentPut("title", "第一组图片")
.fluentPut("detail", "这是第1.2组图片描述")
.fluentPut("images", new ArrayList<PictureRenderData>() {{
for (String httpPicturePath : httpPicturePathArr) {
add(Pictures.ofUrl(httpPicturePath).create());
}
}}));
// java图片
add(new JSONObject().fluentPut("title", "第二组图片")
.fluentPut("detail", "这是第2.1组图片描述")
.fluentPut("images", new ArrayList<PictureRenderData>() {{
for (String picturePath : picturePathArr) {
BufferedImage bufferedImage = ImageIO.read(new FileInputStream(picturePath));
add(Pictures.ofBufferedImage(bufferedImage, PictureType.PNG)
.size(205, 205).create());
}
}}));
}};
return arrayList;
}
/**
* 浏览器导出
* @param fileName 文件名称
* @param map 数据集合
* @param configure 配置信息
*/
public static void exportFile(String fileName, Map<String, Object> map,Configure configure) {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = attributes.getResponse();
// 获取Word模板,模板存放路径在项目的resources目录下
//PoiController就是方法所在的类
XWPFTemplate template = XWPFTemplate.compile(fileName,configure).render(map);
//处理文件名乱码
String attachName = new String(("测试.docx").getBytes(), "ISO-8859-1");
// 浏览器端下载
response.setContentType("application/x-download;charset=" + "utf-8");
response.addHeader("Content-Disposition", "attachment;filename=" + attachName);
OutputStream out = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);
} catch (Exception e) {
e.printStackTrace();
}
}
}
五.总结
如果出现出现与项目其他技术不兼容,可单独拉出一个服务,但此时涉及到不同服务之间通过feign实现Response传递可参考: https://mp.csdn.net/mp_blog/creation/editor/129615552
针对于未接触的技术,先了解官方文档,然后百度,谷歌,嘎嘎一顿乱造,想要的结果也就差不多了,
有疑问欢迎交流沟通!!!
版权声明:本文为博主作者:小菜鸡℡原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/m0_47520320/article/details/129855663