目录
- 问题
- 解决问题
- poi-tl介绍
- 功能实现
- 引入依赖
- 模版
- 代码
- 效果图
- 附加(插件实现)
- MergeColumnData 对象
- MergeGroupData 类
- ServerMergeTableData 数据信息
- ServerMergeTablePolicy 合并插件
问题
由于在开发功能需求中,word文档需要垂直合并表格,而word模版引擎原有的插件功能只能做行循环,不满足需求;
解决问题
- 目前选择的poi-tl的模版引擎,在原有的基础上新增自定义插件来实现功能
poi-tl介绍
poi-tl 是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中;
Word模板引擎功能 | 描述 |
---|---|
文本 | 将标签渲染为文本 |
图片 | 将标签渲染为图片 |
表格 | 将标签渲染为表格 |
图表 | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染 |
If Condition判断 | 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Foreach Loop循环 | 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Loop表格行 | 循环复制渲染表格的某一行 |
Loop表格列 | 循环复制渲染表格的某一列 |
Loop有序列表 | 支持有序列表的循环,同时支持多级列表 |
Highlight代码高亮 | word中代码块高亮展示,支持26种语言和上百种着色样式 |
Markdown | 将Markdown渲染为word文档 |
Word批注 | 完整的批注功能,创建批注、修改批注等 |
Word附件 | Word中插入附件 |
SDT内容控件 | 内容控件内标签支持 |
Textbox文本框 | 文本框内标签支持 |
图片替换 | 将原有图片替换成另一张图片 |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 |
Expression Language | 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL |
样式 | 支持有序列表的循环,同时支持多级列表 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
合并 | Word合并Merge,也可以在指定位置进行合并 |
用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
功能实现
引入依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-full</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<!-- spring el表达式 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
模版
代码
@Test
public void test() throws Exception {
Configure config = Configure.builder()
.bind("precipitationInfoList", new ServerMergeTablePolicy())
.useSpringEL(false).build();
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put("precipitationInfoList", getServerMergeTableData());
ClassPathResource classPathResource = new ClassPathResource("static/word/template.docx");
try (InputStream resourceInputStream = classPathResource.getInputStream();
XWPFTemplate template = XWPFTemplate.compile(resourceInputStream, config);) {
template.render(dataMap);
template.writeAndClose(new FileOutputStream("output.docx"));
} catch (Exception e) {
e.printStackTrace();
}
}
private ServerMergeTableData getServerMergeTableData() {
List<Map<String,Object>> serverDataList = new ArrayList<>();
Map<String,Object> serverData1 = new HashMap<>();
serverData1.put("province","广东省");
serverData1.put("city","深圳市");
serverData1.put("precipitation","0.3");
serverDataList.add(serverData1);
Map<String,Object> serverData2 = new HashMap<>();
serverData2.put("province","广东省");
serverData2.put("city","广州市");
serverData2.put("precipitation","5.1");
serverDataList.add(serverData2);
Map<String,Object> serverData3 = new HashMap<>();
serverData3.put("province","广东省");
serverData3.put("city","东莞市");
serverData3.put("precipitation","10");
serverDataList.add(serverData3);
Map<String,Object> serverData4 = new HashMap<>();
serverData4.put("province","湖南");
serverData4.put("city","长沙");
serverData4.put("precipitation","10");
serverDataList.add(serverData4);
Map<String,Object> serverData6 = new HashMap<>();
serverData6.put("province","湖南");
serverData6.put("city","湘潭");
serverData6.put("precipitation","4.5");
serverDataList.add(serverData6);
List<MergeGroupData> groupDataList = new ArrayList<>();
groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("0","1","2")).build());
groupDataList.add(MergeGroupData.builder().indexList(Arrays.asList("3","4")).build());
List<MergeColumnData> mergeColumns = new ArrayList<>();
mergeColumns.add(MergeColumnData.builder().groupDataList(groupDataList).mergeColumn(0).build());
ServerMergeTableData serverMergeTableData = new ServerMergeTableData();
serverMergeTableData.setMergeColumns(mergeColumns);
serverMergeTableData.setServerDataList(serverDataList);
return serverMergeTableData;
}
效果图
附加(插件实现)
MergeColumnData 对象
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeColumnData {
/**
* 携带要分组的信息
*/
private List<MergeGroupData> groupDataList;
/**
* 需要合并的列,从0开始
*/
private Integer mergeColumn;
}
MergeGroupData 类
/**
* 合并对象信息
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MergeGroupData {
/**
* 名称
*/
private List<String> indexList;
public Integer getListSize() {
if (Objects.isNull(indexList)) {
return 0;
}
return indexList.size();
}
}
ServerMergeTableData 数据信息
import lombok.Data;
import java.util.List;
@Data
public class ServerMergeTableData {
private List<?> serverDataList;
/**
* 列合并信息
*/
private List<MergeColumnData> mergeColumns;
}
ServerMergeTablePolicy 合并插件
import cn.hutool.core.collection.CollUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.render.compute.EnvModel;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.render.processor.EnvIterator;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
import java.util.Iterator;
import java.util.List;
public class ServerMergeTablePolicy extends AbstractRenderPolicy<ServerMergeTableData> {
private String prefix;
private String suffix;
private boolean onSameLine;
public ServerMergeTablePolicy() {
this(false);
}
public ServerMergeTablePolicy(boolean onSameLine) {
this("[", "]", onSameLine);
}
public ServerMergeTablePolicy(String prefix, String suffix) {
this(prefix, suffix, false);
}
public ServerMergeTablePolicy(String prefix, String suffix, boolean onSameLine) {
this.prefix = prefix;
this.suffix = suffix;
this.onSameLine = onSameLine;
}
@Override
public void doRender(RenderContext<ServerMergeTableData> context) throws Exception {
XWPFTemplate template = context.getTemplate();
ServerMergeTableData mergeTableData = context.getData();
ElementTemplate eleTemplate = context.getEleTemplate();
this.renderTo(eleTemplate, mergeTableData, template);
}
public void renderTo(ElementTemplate eleTemplate, Object tableData, XWPFTemplate template) {
RunTemplate runTemplate = (RunTemplate) eleTemplate;
XWPFRun run = runTemplate.getRun();
try {
XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
XWPFTable table = tagCell.getTableRow().getTable();
run.setText("", 0);
if (null == tableData) {
return;
}
ServerMergeTableData serverTableData = (ServerMergeTableData) tableData;
List data = serverTableData.getServerDataList();
if (!TableTools.isInsideTable(run)) {
throw new IllegalStateException(
"The template tag " + runTemplate.getSource() + " must be inside a table");
}
int templateRowIndex = getTemplateRowIndex(tagCell);
int tempStartRow = templateRowIndex;
if (null != data && 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++;
XWPFTableRow nextRow = table.insertNewTableRow(insertPosition);
setTableRow(table, templateRow, insertPosition);
// double set row
XmlCursor newCursor = templateRow.getCtRow().newCursor();
newCursor.toPrevSibling();
XmlObject object = newCursor.getObject();
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);
});
}
}
table.removeRow(templateRowIndex);
afterloop(table, data);
//合并表格信息
mergeTable(serverTableData, tempStartRow, table);
} catch (Exception e) {
throw new RenderException("HackLoopTable for " + eleTemplate + " error: " + e.getMessage(), e);
}
}
private void mergeTable(ServerMergeTableData serverMergeTableData, int startRow, XWPFTable xwpfTable) {
List serverDataList = serverMergeTableData.getServerDataList();
List<MergeColumnData> mergeColumns = serverMergeTableData.getMergeColumns();
if (CollUtil.isNotEmpty(mergeColumns)) {
for (int i = 0; i < serverDataList.size(); i++) {
for (MergeColumnData mergeColumnData : mergeColumns) {
Integer mergeColumn = mergeColumnData.getMergeColumn();
List<MergeGroupData> groupDataList = mergeColumnData.getGroupDataList();
for (int j = 0; j < groupDataList.size(); j++) {
MergeGroupData mergeGroupData = groupDataList.get(j);
List<String> indexList = mergeGroupData.getIndexList();
int listSize = mergeGroupData.getListSize();
if (listSize == 1) {
continue;
}
// 若匹配上 就直接合并
if (indexList.contains(i + "")) {
int col = i + startRow;
int fromRow = i + (startRow - 1) + listSize;
TableTools.mergeCellsVertically(xwpfTable, mergeColumn, col, fromRow);
groupDataList.remove(j);
break;
}
}
}
}
}
}
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);
}
}