poi-tl中行循环插件原理解析
package com.deepoove.poi.plugin.table; LoopRowTableRenderPolicy
准备工作:
1.创建一个测试用的实体类,并准备测试用例
@Test
public void test1() throws IOException {
//新建委托
List<DelegateInfo> delegateInfos=new ArrayList<>();
DelegateInfo info1 = new DelegateInfo();
info1.setId(1234l);
info1.setStatus("已经启用");
info1.setName("委托模板1");
info1.setSubmitter("张三");
delegateInfos.add(info1);
DelegateInfo info2 = new DelegateInfo();
info2.setId(1235l);
info2.setStatus("已经启用");
info2.setName("委托模板2");
info2.setSubmitter("李四");
delegateInfos.add(info2);
DelegateInfo info3 = new DelegateInfo();
info3.setId(1236l);
info3.setStatus("已经弃用");
info3.setName("委托模板3");
info3.setSubmitter("王五");
delegateInfos.add(info3);
DelegateInfo info4 = new DelegateInfo();
info4.setId(1237l);
info4.setStatus("已经启用");
info4.setName("委托模板4");
info4.setSubmitter("赵六");
delegateInfos.add(info4);
DelegateInfo info5 = new DelegateInfo();
info5.setId(1238l);
info5.setStatus("已经启用");
info5.setName("委托模板5");
info5.setSubmitter("麻子");
delegateInfos.add(info5);
com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy policy = new com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy();
Configure config = Configure.builder()
.bind("delegateInfos", policy).build();
String resource = "D:\\wny\\文档\\测试\\test.docx";
XWPFTemplate template = XWPFTemplate.compile(resource, config).render(
new HashMap<String, Object>() {{
put("delegateInfos", delegateInfos);
}}
);
File file1 = new File("D:\\wny\\文档\\测试\\test_result.docx");
if (!file1.exists()) {
file1.createNewFile();
}
template.writeAndClose(new FileOutputStream(file1));
}
2.创建测试所用模板
新建一个word插入表格
测试表格1
委托id{{delegateInfos}} | 委托状态 | 委托名称 | 提交人 |
[id] | [status] | [name] | [submitter] |
测试表格2
委托id{{delegateInfos}} | 委托状态 | 委托名称 | 提交人 |
[id] | [status] | [name] | [submitter] |
[submitter] | |||
搜索 |
3.开始debug调试
3.1插件主流程调试
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
//获取到行循环插件绑定的数据和模板,即第二步中的{{delegateInfos}}元数据
RunTemplate runTemplate = (RunTemplate)eleTemplate;
//获取到模板信息对应的内容获取到{{delegateInfos}}在word中当前呈现的内容
XWPFRun run = runTemplate.getRun();
try {
if (!TableTools.isInsideTable(run)) {
//判断这个标签是不是在表格当中
throw new IllegalStateException("The template tag " + runTemplate.getSource() + " must be inside a table");
} else {
//如果这个标签在表格当中获取到当前{{delegateInfos}}所在的单元格信息
XWPFTableCell tagCell = (XWPFTableCell)((XWPFParagraph)run.getParent()).getBody();
//根据标签所在的单元格获取到当前标签所在的表格
XWPFTable table = tagCell.getTableRow().getTable();
//把当前标签在word模板中展示的内容替换成“” 即{{delegateInfos}}替换成空
run.setText("", 0);
//获取模板行的下标即第二步中第一行在表格中的下标0,因为该LoopRowTableRenderPolicy默认初始化创建的时候onSameLine为false
//,该方法中进行了判断,onSameLine为false要加1,所以模板行下标为1,是第二步中的第二行。
int templateRowIndex = this.getTemplateRowIndex(tagCell);
//data是改标签对绑定的数据,即我们第一步创建的List<DelegateInfo> delegateInfos
if (null != data && data instanceof Iterable) {
//把数据放入迭代器
Iterator<?> iterator = ((Iterable)data).iterator();
//通过模板行下标获取模板行数据
XWPFTableRow templateRow = table.getRow(templateRowIndex);
//TemplateResolver提供了对Word文档模板进行解析和处理的功能,这行代码是把文件匹配规则的前后缀由{{xxx}}换成了[xxx]
//便于对模板行数据进行匹配,prefix,suffix是当前插件的属性,在插件初始化时可设置,默认为'[',']'
TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(this.prefix, this.suffix));
//判断是不是第一次的填充数据的标识
boolean firstFlag = true;
//数据填充遍历标识
int index = 0;
//行循环标识,判断List<DelegateInfo> 中是否还有数据为填充
boolean hasNext = iterator.hasNext();
while(hasNext) {
//取出List<DelegateInfo> delegateInfos中的第一个实体类,下标值+1;root=delegateInfos.get(0),hasNext判断下标1
Object root = iterator.next();
//这一行是不是最后一行
hasNext = iterator.hasNext();
//insertPosition是要插入模板行的下标,模板行下标+1;
int insertPosition = templateRowIndex++;
//表格里把模板行所在的位置插入了一个空白行,模板行在表格中的下标+1,和templateRowIndex增加后的值保持一致
table.insertNewTableRow(insertPosition);
//setTableRow方法是插件的内部方法,根据(List)ReflectionUtils.getValue("tableRows", table);获取到所有行,再把刚插入的一行替换成模板行
this.setTableRow(table, templateRow, insertPosition);
//创建一个XML的游标,指向模板行的xml标签;通过该游标,我们可以访问并修改CTRow对象中的所有XML元素,如<w:tr>、<w:tc>、<w:p>等
XmlCursor newCursor = templateRow.getCtRow().newCursor();
//newCursor.toPrevSibling()的作用是将XML游标向前移动一个兄弟节点,即模板行的上一行,也是我们新插入的一行
newCursor.toPrevSibling();
//获取到新插入一行的xml数据
XmlObject object = newCursor.getObject();
//通过新插入行的数据反向生成XWPFTableRow类
XWPFTableRow nextRow = new XWPFTableRow((CTRow)object, table);
if (!firstFlag) {
//如果插入的不是第一行,对垂直合并格式进行筛选
List<XWPFTableCell> tableCells = nextRow.getTableCells();
Iterator var21 = tableCells.iterator();
while(var21.hasNext()) {
XWPFTableCell cell = (XWPFTableCell)var21.next();
CTTcPr tcPr = TableTools.getTcPr(cell);
CTVMerge vMerge = tcPr.getVMerge();
//如果单元格不存在合并格式就跳过,如果是RESTART就保持上一次合并的格式也就是不合并,也就是说一些由垂直合并操作的表格很难处理如测试表格2
if (null != vMerge && STMerge.RESTART == vMerge.getVal()) {
vMerge.setVal(STMerge.CONTINUE);
}
}
} else {
firstFlag = false;
}
//对模板表格进行再处理后(垂直合并处理后)重新替换表格新插入的一行
this.setTableRow(table, nextRow, insertPosition);
//实现自定义的数据计算逻辑。通过该接口,我们可以在Excel导出过程中动态地生成或修改某些单元格的数据
//Index代表列的下标,hasNext判断是否有下一行
RenderDataCompute dataCompute = template.getConfig().getRenderDataComputeFactory().newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
//获取新插入行的所有单元格
List<XWPFTableCell> cells = nextRow.getTableCells();
//对单元格遍历操作
cells.forEach((cellx) -> {
//对单元格里面的标签对进行替换 resolver是我们上文中自定义的转换器,它使用了poi-tl原本的转换规则,但是将{{xxx}}换成了[xxx],它得到的结果是是一个元素数组类似[id]
//与上文中的runTemplate相似,可以通过这个元素修改它在word文档中显示的内容
List<MetaTemplate> templates = resolver.resolveBodyElements(cellx.getBodyElements());
//内含大量操作,标签对列表templates的内容通过 resolver,和dataCompute替换掉
(new DocumentProcessor(template, resolver, dataCompute)).process(templates);
});
}
}
table.removeRow(templateRowIndex);
this.afterloop(table, data);
}
} catch (Exception var25) {
throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + var25.getMessage(), var25);
}
}
//行替换方法,用于新插入空白行替换为模板行
private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
/*
这段代码的作用是将一个XWPFTableRow对象插入到Word文档表格中的指定位置。
具体来说,rows是当前Word文档表格中所有行的集合,通过反射机制获取到名为"tableRows"的私有属性。pos表示要插入的行在表格中的索引位置,从0开始计数,而templateRow则是要插入的行的模板对象。
该代码片段分别调用了rows.set()和table.getCTTbl().setTrArray()两个方法来实现插入操作,它们的作用分别如下:
rows.set(pos, templateRow):将指定位置上的行替换为新的行对象。其中,
pos表示要替换的行在表格中的索引位置,
templateRow表示要插入的新行对象。如果指定位置上存在原有行,则会将其覆盖替换为新行;否则会将新行添加到表格末尾处。
table.getCTTbl().setTrArray(pos, templateRow.getCtRow()):将指定位置上的CTRow对象替换为新的CTRow对象。其中,
pos表示要替换的行在表格中的索引位置,
templateRow.getCtRow()表示要插入的新行对象对应的CTRow对象。如果指定位置上存在原有行,则会将其覆盖替换为新行;否则会将新行添加到表格末尾处。
需要注意的是,这两个方法的主要区别在于操作的对象不同。rows.set()方法用于直接操作XWPFTableRow对象,而table.getCTTbl().setTrArray()方法则用于操作XML DOM中的CTRow对象。在实际使用中,我们可以根据具体情况来选择使用哪种方式插入行对象,以便更加方便地实现Word文档表格的自动化生成和编辑。
*/
List<XWPFTableRow> rows = (List)ReflectionUtils.getValue("tableRows", table);
rows.set(pos, templateRow);
table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
}
4.测试结果
测试表格1
委托id | 委托状态 | 委托名称 | 提交人 |
1234 | 已经启用 | 委托模板1 | 张三 |
1235 | 已经启用 | 委托模板2 | 李四 |
1236 | 已经弃用 | 委托模板3 | 王五 |
1237 | 已经启用 | 委托模板4 | 赵六 |
1238 | 已经启用 | 委托模板5 | 麻子 |
测试表格2
在word中显示:
但是表格数据为
1235已经启用
李四1236已经弃用
王五1237已经启用
赵六1238已经启用
麻子
委托id | 委托状态 | 委托名称 | 提交人 |
1234 | 已经启用 | 委托模板1 | 张三 |
委托模板 2 | |||
委托模板3 | |||
委托模板4 | |||
委托模板5 | |||
[submitter] | |||
搜索 |
------------------------------------------
也就是说表格的数据插入进去了,但是单元格合并出现了问题,这个在代码中我们有过说明