poi-tl(1.10.0)动态合并同列不同行单元格(动态数据)

一、在有些业务场景下,我们需要导出一些word文档的报告等,这里我推荐一下poi-tl,这是我开发当中用到的。由于业务数据的复杂性,easyWord没法满足需求,所以这里推荐使用poi-tl,因为它提供了针对处理复杂数据的很多接口策略,还可以根据自己具体的业务需求来扩展。

二、这次是关于如何动态合并单元格。参考了其他帖子,但是他们用到的数据都是固定的,但是对于动态数据是不适用的,下面直接上代码,分享一下我是如何处理的?

package com.cantai.qmsat.utils.poi;

import com.cantai.qmsat.at.operate.entity.InstrumentEntity;
import com.deepoove.poi.data.CellRenderData;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author shijin
 * @create 2022-11-04 19:30
 */
public class RowspanPolicy extends DynamicTableRenderPolicy {

    @Override
    public void render(XWPFTable table, Object data) throws Exception {
        int num = 0;
        if (null == data) return;
        List<RowRenderData> detailData = (List<RowRenderData>) data;
        if (null != detailData) {
            table.removeRow(2);
            // 循环插入行,必须采用这种遍历,不然数据会反过来
            for (int i = detailData.size() - 1; i >= 0; i--) {
                //根据数据长度创建对应行数
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(2);
                for (int j = 0; j < 5; j++) {
                    //根据列的数量创建对应单元格
                    insertNewTableRow.createCell();
                }
                // 单行渲染
                TableRenderPolicy.Helper.renderRow(table.getRow(2), detailData.get(i));
            }
            //遍历完整个图表之后再合并
            List<String> list = new ArrayList<>();
            HashMap<String, Object> nameNumMap = new HashMap<>();
            for (int i = 0; i < detailData.size(); i++) {
                String typeName = detailData.get(i).getCells().get(1).getParagraphs().get(0).getContents().get(0).toString();
                list.add(typeName);
            }
            List<String> collect = list.stream().distinct().collect(Collectors.toList());
            for (String s : collect) {
                for (int i = 0; i < detailData.size(); i++) {
                    String typeName1 = detailData.get(i).getCells().get(1).getParagraphs().get(0).getContents().get(0).toString();
                    if (s == typeName1) {
                        num++;
                    }
                }
                nameNumMap.put(s, num);
                num = 0;
            }
            //开始合并
            for (String s : collect) {
                //从我们map中取出刚才放入的单位的个数
                Integer change = Integer.parseInt(String.valueOf(nameNumMap.get(s)));
                //开始循环数据
                //元素第一次出现的位置
                int firstLocation = list.lastIndexOf(s) + 1;
                int fromRow = firstLocation - change + 2;
                int toRow = list.lastIndexOf(s) + 2;
                if (change > 1) {
                    TableTools.mergeCellsVertically(table, 1, fromRow, toRow);
                }
            }
        }
    }
}
  1. 创建一个类RowspanPolicy继承DynamicTableRenderPolicy,重写render方法

  1. 准备数据:参考了官网的合并方法,data中的数据类型要转为RowRenderData,所以在组装数据的时候,直接组装成RowRenderData类型的,如下:

for (AfTtiInstrumentVo afTtiInstrumentVo : instrumentVos) {
            int i = instrumentVos.indexOf(afTtiInstrumentVo) + 1;
                    DateFormat cst = new SimpleDateFormat("yyyy-MM-dd");
                    DateFormat gmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
                    Date date1 = gmt.parse(afTtiInstrumentVo.getNextCalibrationDate().toString());
                    String date2 = cst.format(date1);
                    RowRenderData rowRenderData = Rows.of(Integer.toString(i), afTtiInstrumentVo.getTestName(), afTtiInstrumentVo.getDeviceName(), afTtiInstrumentVo.getFactoryNumber(), date2).center().create();
                    instrumentList1.add(rowRenderData);
        }
  1. 数据准备好之后,开始重写DynamicTableRenderPolicy策略接口,首先是将数据逐个添加到每一个的单元格,如下:

//如果顺序遍历的话,数据的顺序和原来的顺序会相反(这是在debug的时候发现的,具体原因也没深究),所以采取倒叙遍历
for (int i = detailData.size() - 1; i >= 0; i--) {
                //根据数据长度创建对应行数
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(2);
                for (int j = 0; j < 5; j++) {
                    //根据列的数量创建对应单元格
                    insertNewTableRow.createCell();
                }
                // 单行渲染数据
                TableRenderPolicy.Helper.renderRow(table.getRow(2), detailData.get(i));
            }
  1. 下面就开始合并同一列下的数据相同的单元格

//收集操作列的全部数据,放在List中(我这里需要操作的是第2列数据getCells().get(1))
List<String> list = new ArrayList<>();
            HashMap<String, Object> nameNumMap = new HashMap<>();
            for (int i = 0; i < detailData.size(); i++) {
                String typeName = detailData.get(i).getCells().get(1).getParagraphs().get(0).getContents().get(0).toString();
                list.add(typeName);
            }
//筛选出不同的数据重新放在新list中
            List<String> collect = list.stream().distinct().collect(Collectors.toList());
//统计不同数据在全部数据中出现的次数num,并整合放在map中,后面合并的行数需要用到num
            for (String s : collect) {
                for (int i = 0; i < detailData.size(); i++) {
                    String typeName1 = detailData.get(i).getCells().get(1).getParagraphs().get(0).getContents().get(0).toString();
                    if (s == typeName1) {
                        num++;
                    }
                }
                nameNumMap.put(s, num);
//每统计完一个,需要重置num
                num = 0;
            }
            //开始合并
            for (String s : collect) {
                //从我们map中取出刚才放入的单位的个数
                Integer change = Integer.parseInt(String.valueOf(nameNumMap.get(s)));
                //开始循环数据
                //元素第一次出现的位置
                int firstLocation = list.lastIndexOf(s) + 1;
                int fromRow = firstLocation - change + 2;
                int toRow = list.lastIndexOf(s) + 2;
//由于TableTools.mergeCellsVertically中参数toRow要大于fromRow,否则会报错,所以num>1的才参与合并
                if (change > 1) {
                    TableTools.mergeCellsVertically(table, 1, fromRow, toRow);
                }
            }

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
我理解你的问题是如何使用poi-tl动态循环打印数据并且合并单元格避免重复。以下是一个示例代码片段,可以帮助你实现这个功能: ``` // 创建一个Word模板 XWPFTemplate template = XWPFTemplate.compile("template.docx").render(); // 获取需要循环打印的数据 List<Map<String, Object>> dataList = getDataList(); // 获取模板中需要替换的变量 Map<String, Object> replaceMap = getReplaceMap(); // 在模板中插入表格 template.insertTable("{{table}}", dataList.size() + 1, 5, new ITableRenderPolicy() { @Override public void render(XWPFTable table, Object data) { // 设置表格样式 setTableStyle(table); // 获取表格中的第一,用于设置表头 XWPFTableRow headerRow = table.getRow(0); headerRow.getCell(0).setText("序号"); headerRow.getCell(1).setText("姓名"); headerRow.getCell(2).setText("性别"); headerRow.getCell(3).setText("年龄"); headerRow.getCell(4).setText("地址"); // 循环插入数据 for (int i = 0; i < dataList.size(); i++) { Map<String, Object> dataMap = dataList.get(i); XWPFTableRow dataRow = table.getRow(i + 1); dataRow.getCell(0).setText(String.valueOf(i + 1)); dataRow.getCell(1).setText(String.valueOf(dataMap.get("name"))); dataRow.getCell(2).setText(String.valueOf(dataMap.get("gender"))); dataRow.getCell(3).setText(String.valueOf(dataMap.get("age"))); dataRow.getCell(4).setText(String.valueOf(dataMap.get("address"))); // 判断是否需要合并单元格 if (i > 0 && dataMap.get("name").equals(dataList.get(i - 1).get("name"))) { mergeCellsVertically(table, i, 0, i - 1); } } } }); // 替换模板中的变量 template.replaceDocument(replaceMap); // 输出生成的Word文档 template.write(new FileOutputStream("output.docx")); // 关闭模板 template.close(); ``` 在上述代码中,我们使用了poi-tl库中的`ITableRenderPolicy`来实现表格动态循环渲染。在渲染每一数据时,我们判断是否需要合并单元格,如果当前的姓名与前一的姓名相同,则需要将当前的姓名单元格与前一的姓名单元格合并。为了实现合并单元格,我们使用了`mergeCellsVertically`方法,该方法来自于poi库。 希望以上代码可以帮助你解决问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hx_try

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值