EasyExcel-读Excel-不创建对象的读-合并单元格的处理

EasyExcel官方文档

这几天需要读取excel的内容,但是excel中存在多个sheet页,每个sheet页的标题不同,数据不同,而且多个excel文件。决定使用easyexcel处理,感觉这样的excel文件和内容无法使用对象接收excel的数据,所以决定使用easyexceld的"不创建对象的读"来接受数据。

在测试使用的过程中,发现如果excel存在合并单元格的情况,读取出来的数据不是自己想要的,话不多说,直接上图说明,假如有一个excel文件,存在一个sheet页,数据如下:

可以看出,存在几个地方合并单元的数据,如果什么都不处理,直接读取当前数据,结果如下:

很明显这并不是我想要的,因为按照excel合并单元格之后,例如张三和李四的年龄,都应该是20才对,但实际的数据只有张三的年龄是20,李四的年龄是null。

我在网上查询的时候,看到有人说,easyexcel只有合并单元格的左上角单元格才有值,其他的单元格没有值。

所以我的想法就是根据官方文档,获取到excel中的所有行数据,以及合并单元格的数据信息,最后整合这两部分数据,根据单元格的信息,去更新行数据。

具体的easyexcel用法自行参考官方,代码是我自己写的,绝对不是随便copy过来的,亲测有效。

我写代码测试的时候是按照多个sheet页来的(返回多个sheet页的所有数据),但实际测试的excel只创建了一个sheet页。因为我只是测试这个过程,并没有涉及实际的业务,后面会根据这个demo来实现实际业务中的需求。

具体实现如下:

java 复制代码
package demo.jiangkd.easyexcel.controller;

import demo.jiangkd.easyexcel.service.EasyExcelMergeDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
 * @author jiangkd
 * @date 2024/8/9 9:16
 */
@RequiredArgsConstructor
@RequestMapping("/easyexcel")
@RestController
public class EasyExcelDemoController {

    private final EasyExcelMergeDemoService easyExcelMergeDemoService;

    /**
     * 如果excel中带有合并单元格, 又是不创建对象的读取
     *
     * @param filename
     */
    @GetMapping("/merge/demo")
    public List<Map<Integer, String>> EasyExcel(String filename) {
        //
        return easyExcelMergeDemoService.mergeDemo(filename);
    }
}
java 复制代码
package demo.jiangkd.easyexcel.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.metadata.ReadSheet;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author jiangkd
 * @date 2024/8/9 14:49
 */
@Slf4j
@Service
public class EasyExcelMergeDemoService {

    public List<Map<Integer, String>> mergeDemo(String filename) {

        // 所有的sheet, 不用管excel有几个sheet, 数据全部返回
        @Cleanup final ExcelReader build = EasyExcel.read(filename).build();
        final List<ReadSheet> readSheets = build.excelExecutor().sheetList();

		// 最终要返回的数据
        final List<Map<Integer, String>> datas = new ArrayList<>();

		// 从excel文件的第几行开始读取,默认是从第二行开始的(认为第一行是标题行)
        final int headRowNumber = 0;

        @Cleanup
        ExcelReader excelReader = EasyExcel.read(filename)
                // 从sheet页的第一行开始读取,默认是从第二行开始的
                .headRowNumber(headRowNumber)
                // 需要读取合并单元格信息 默认不读取(官方文档有说明)
                .extraRead(CellExtraTypeEnum.MERGE)
                .build();

		// 循环所有sheet页,读取数据,都放入datas集合中
        readSheets.forEach(readSheet -> {

            log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 开始读取sheet, {}, {}", readSheet.getSheetNo(),
                    readSheet.getSheetName());

			// 读取数据的监听
            final NoModelMergeDataListener noModelMergeDataListener = new NoModelMergeDataListener(headRowNumber);

            final ReadSheet sheet = EasyExcel.readSheet(readSheet.getSheetNo())
                    .registerReadListener(noModelMergeDataListener)
                    .build();

            excelReader.read(sheet);

			// 获取行数据 和 合并的单元格信息
            final List<CellExtra> extraMergeInfoList = noModelMergeDataListener.getExtraMergeInfoList();
            final List<Map<Integer, String>> excelDatas = noModelMergeDataListener.getDatas();
            if (CollectionUtils.isEmpty(extraMergeInfoList)) {
            	// 如果不存在合并单元格的数据
                datas.addAll(excelDatas);
            } else {
                // 否则处理行数据
                datas.addAll(this.explainMergeData(excelDatas, extraMergeInfoList));
            }

        });

        return datas;

    }

    private List<Map<Integer, String>> explainMergeData(List<Map<Integer, String>> excelDatas,
                                                        List<CellExtra> extraMergeInfoList) {

        // easyexcel默认处理合并单元格, 只有左上角的有值, 其他的没值

        // 先找出合并的单元格的值
        for (CellExtra cellExtra : extraMergeInfoList) {

            // 通过cellExtra的rowIndex和columnIndex来确定该单元格的值
            final Integer rowIndex = cellExtra.getRowIndex();
            final Integer columnIndex = cellExtra.getColumnIndex();

            // 通过rowIndex确定第几行数据
            final Map<Integer, String> rowData = excelDatas.get(rowIndex);
            // 通过columnIndex确定这一行数据的第几列
            final Set<Map.Entry<Integer, String>> entries = rowData.entrySet();
            final List<Map.Entry<Integer, String>> entriesList = new ArrayList<>(entries);
            final Map.Entry<Integer, String> entry = entriesList.get(columnIndex);
            final String value = entry.getValue();

            log.info("当前合并的单元格的值是:{}", value);

            // 获取合并单元格的范围Index数据, 也就是跨越了那些单元格
            final Integer firstRowIndex = cellExtra.getFirstRowIndex();
            final Integer lastRowIndex = cellExtra.getLastRowIndex();
            final Integer firstColumnIndex = cellExtra.getFirstColumnIndex();
            final Integer lastColumnIndex = cellExtra.getLastColumnIndex();

            // 纵使easyexcel合并单元只有左上角的单元格有值, 为了方便, 这里直接把范围单元格的值全部替换掉为value

            // 获取要处理的行数据都有哪些, 也就是[firstRowIndex, lastRowIndex]的行数据
            final List<Map<Integer, String>> mergeRowDatas = new ArrayList<>();

            final int size = excelDatas.size();
            for (int i = 0; i < size; i++) {
                if (firstRowIndex <= i && i <= lastRowIndex) {
                    mergeRowDatas.add(excelDatas.get(i));
                }
            }

            // 确定了哪些行数据中存在合并的单元格, 接下来更新这些行数据的那些列
            for (Map<Integer, String> mergeRowData : mergeRowDatas) {

                // 这一行数据的所有列
                final Set<Map.Entry<Integer, String>> mergeRowDataEntries = mergeRowData.entrySet();
                final List<Map.Entry<Integer, String>> mergeRowDataEntriyList = new ArrayList<>(mergeRowDataEntries);

                // 确定列并且更新列的值
                final int columnSize = mergeRowDataEntriyList.size();
                for (int i = 0; i < columnSize; i++) {
                    if (firstColumnIndex <= i && i <= lastColumnIndex) {
                        final Map.Entry<Integer, String> columnEntry = mergeRowDataEntriyList.get(i);
                        columnEntry.setValue(value);
                    }
                }

            }

        }

        return excelDatas;

    }

}
java 复制代码
package demo.jiangkd.easyexcel.service;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author jiangkd
 * @date 2024/8/9 14:52
 */
@Slf4j
public class NoModelMergeDataListener implements ReadListener<Map<Integer, String>> {

    /**
     * excel解析的数据
     */
    final List<Map<Integer, String>> datas = new ArrayList<>();

    /**
     * 正文起始行
     */
    private Integer headRowNumber;

    /**
     * 合并单元格
     */
    private List<CellExtra> extraMergeInfoList = new ArrayList<>();

    public List<Map<Integer, String>> getDatas() {
        return datas;
    }

    public List<CellExtra> getExtraMergeInfoList() {
        return extraMergeInfoList;
    }

    public NoModelMergeDataListener(Integer headRowNumber) {
        this.headRowNumber = headRowNumber;
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        //
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        datas.add(data);
    }

    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        //
        log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
        if (Objects.requireNonNull(extra.getType()) == CellExtraTypeEnum.MERGE) {
            log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                    extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                    extra.getLastColumnIndex());

            if (extra.getRowIndex() >= headRowNumber) {
                extraMergeInfoList.add(extra);
            }
        }

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //
        log.info("======================================");
        log.info("所有数据解析完成!");
        log.info("======================================");
    }
}

针对如上代码,说明几点:

  1. 里面没有说明注释的代码,翻看easyexcel官方文档就会明白了。
  2. 读取了excel中的所有sheet页,循环sheet读取数据,最后一块全部返回,如果你只读取第一个sheet页,略改代码即可(或者直接不改)。
  3. 从监听中获取excel的行数据和合并单元格的数据信息。
  4. 合并单元格的信息主要就是单元格的位置,行列的index,可以确定那些范围内的单元格是合并的。

然后再次执行,结果如下:

现在数据是我想要的了。

马上就要下班了,文章写的有点乱,格式不是很清晰,但是代码其实没有很多,结合官方文档,很容易理解的。

至于代码中的CellExtra,以及get出来的几个Index数据,执行代码测试的时候,查看控制台打印结果,对比excel,你就明白了。

相关推荐
martian66518 分钟前
C# 基于WPF实现数据记录导出excel
开发语言·c#·excel
自由之翼Sai12 小时前
Excel中超链接打开文件时报错 “打开此文件的应用程序没有注册“ 的一个解决办法
excel
糯米w15 小时前
【前端】excel文件对比
前端·javascript·excel
CodeDevMaster1 天前
Python办公自动化:用xlrd轻松读取Excel文件
python·excel
kim56591 天前
excel版数独游戏(已完成)
算法·游戏·excel·数独
爱编程的小生2 天前
Easyexcel(5-自定义列宽)
java·excel
newroad-for-myself2 天前
英文版本-带EXCEL函数的数据分析
数据挖掘·数据分析·excel
爱编程的小生2 天前
Easyexcel(6-单元格合并)
java·excel
PythonFun2 天前
Excel求和如何过滤错误值
excel
Morantkk3 天前
Word和Excel使用有感
word·excel