poi-tl中行循环插件原理解析

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]

搜索

------------------------------------------

也就是说表格的数据插入进去了,但是单元格合并出现了问题,这个在代码中我们有过说明

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值