• NPOI操作EXCEL(五)——含合并单元格复杂表头的EXCEL解析


    我们在第三篇文章中谈到了那些非常反人类的excel模板,博主为了养家糊口,也玩命做出了相应的解析方法...

     

    我们先来看看第一类复杂表头:

     

    ......

    博主称这类excel模板为略复杂表头模板(蓝色部分为表头部分,蓝色前面几行是博主项目的基础样式,称为元数据),这类excel的表头多为2-3行,甚至于5/6行 ,具有合并层级关系,看似复杂,但只需要在我们以前的基础上稍微做一下重构就可以完美实现解析。

     

    我们以各地区户籍人口城乡构成表头为例:

    其实,只要我们能准确解析这类表头所表达的意思,就能复用以前的代码做解析工作

    也就是说,重点在于表头解析方法GetExcelHeaders(),

    我们返回看第三篇文章http://www.cnblogs.com/csqb-511612371/p/4891492.html中这个方法的代码:

    第17行到33行

     1                    for (int j = headerRow.FirstCellNum; j < cellCount; j++)
     2                     {
     3                         if (!string.IsNullOrEmpty(headerRow.GetCell(j).StringCellValue.Trim()))
     4                         {
     5                             // 根据 键-值 是否已存在做不同处理
     6                             try
     7                             {
     8                                 string oldValue = dict[j];
     9                                 dict.Remove(j);
    10                                 dict.Add(j, oldValue + headerRow.GetCell(j).StringCellValue.Trim());
    11                             }
    12                             catch (Exception)
    13                             {
    14                                 dict.Add(j, headerRow.GetCell(j).StringCellValue.Trim());
    15                             }
    16                         }
    17                     }

    我们在这儿做了一个列的循环,对表头所在行每一列做了一个值合并,那么我们可以预料:这个表头解析出来的结果:

    0,地区

    1,总人口(年末)(万人)

    2,城镇人口人口数

    3,比重(%)

    4,乡村人口人口数

    5,#比重(%)

    那么我们的xml配置文件就该写成这样:

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <module>
     3   <add firstHeaderRow="5"                lastHeaderRow="7"/>
     4   <add headerText="年份"                 propertyName="Year"                               dataType="System.Int32"/>
     5   <add headerText="总人口(年末)(万人)"   propertyName="TotalAmount"                        dataType="System.double"/>
     6   <add headerText="城镇人口人口数"       propertyName="UrbanPermanentPopulation"           dataType="System.double"/>
     7   <add headerText="比重(%)"              propertyName="UrbanPermanentPopulationShare" dataType="System.double"/>
     8   <add headerText="乡村人口人口数"       propertyName="RuralPermanentPopulation"           dataType="System.double"/>
     9   <add headerText="#比重(%)"              propertyName="RuralPermanentPopulationShare" dataType="System.double"/>
    10 </module>

    注:

    1.第三行:5-7代表模板表头所在位置

     

    OK,我们这样做就解析出了这个含有合并单元格的表头,那么接下来的所有流程就和简单表头一样了。

    我们来总结一下:

    1.修改配置文件xml,按我们的解析规则做映射配置

    2.重构表头解析方法,按我们的配置解析表头数据

     

    我们再来看看更复杂的表头模板:

     

    ......

    这类模板除了表头外,还含有左表头。当然左表头也是需要存入数据库的,只是需要我们能准确解析到合并单元格所表达内容,方便导出是还原excel数据样式

    以第一个excel水文特征值为例:

    1.表头按合并表头做xml配置

    2.左表头深色部分为合并区域,浅色部分为弱区域,可扩充

    3.左表头需要解析成

    水位.潮汐性质    

    水位.历年最高潮位

    水位.多年平均高潮位

    ...

    好吧,那么我们这次需要重构的就是解析数据的方法GetExcelDatas,我们看看第三篇文章:

    http://www.cnblogs.com/csqb-511612371/p/4891492.html中的代码

    我们看到第42-45行,对空值只做了简单处理,那么我们先来普及一下NPOI遇到合并单元格怎么取值?

    NPOI只能取到合并单元格最左上角单元格的值,其它单元格均为空值。那么既然我们的数据中含有左表头含有合并单元格,这个空值就需要做一个复杂判断了

    我们先来捋一捋思路:如果取值时遇到空值,可能是单元格本就是空值,也可能是该单元格是合并单元格,切不在合并坐标左上角...

    OK,我们来看重构后的这一段代码

     1                      if (value == "")
     2                     {
     3                         int firstRegionRow = 0;
     4                         if (_iCoreExcelAnalyzeService.IsMergedRegionCell(j, i, sheet, ref firstRegionRow))  //2、单元格为合并单元格且不在合并区域左上角
     5                         {
     6                             if (firstRegionRow >= lastHeaderRowIndex && i != firstRegionRow)//合并单元格  第一行无值为cell合并
     7                             {
     8                                 int resultIndex = firstRegionRow - lastHeaderRowIndex;
     9 
    10                                 var oldModel =
    11                                     resultList.Select((p, d) => new { p, d })
    12                                         .Where(p => p.d == resultIndex)
    13                                         .Select(p => p.p).First();
    14                                 var regionValue = oldModel.GetType().GetProperty(property).GetValue(oldModel, null);//获得合并单元格第一行数据
    15                                 value = regionValue.ToString();
    16                             }
    17                         }
    18                         else   //1、单元格空值
    19                         {
    20                             nullcount++;
    21                         }
    22                    }

    注:

    1.第4行涉及方法IsMergedRegionCell()是用来判断当前空值单元格是否是合并单元格,并返回合并单元格起始行

    我们查阅NPOI接口得知,目前并不支持直接判断,只有通过自己的逻辑去判断是否是合并单元格(不知道是否是博主未查到准确的API,如有该API,请指出...)

     1         // 判断单元格是否被合并
     2         public bool IsMergedRegionCell(int cellIndex, int rowIndex,ISheet sheet,ref int firstRegionRow)
     3         {
     4             bool isMerged = false;
     5             var regionLists = GetMergedCellRegion(sheet);
     6 
     7             foreach (var cellRangeAddress in regionLists)
     8             {
     9                 for (int i = cellRangeAddress.FirstRow; i <= cellRangeAddress.LastRow; i++)
    10                 {
    11                     if (rowIndex == i)
    12                     {
    13                         for (int j = cellRangeAddress.FirstColumn; j <= cellRangeAddress.LastColumn; j++)
    14                         {
    15                             if (cellIndex == j)
    16                             {
    17                                 isMerged = true;
    18                                 firstRegionRow = cellRangeAddress.FirstRow;
    19                                 break;
    20                             }
    21                             else
    22                             {
    23                                 continue;
    24                             }
    25                         }
    26                     }
    27                     else
    28                     {
    29                         continue;
    30                     }
    31                 }
    32             }
    33 
    34             return isMerged;
    35         }
    36 
    37         // 获取合并区域信息
    38         private List<CellRangeAddress> GetMergedCellRegion(ISheet sheet)
    39         {
    40             int mergedRegionCellCount = sheet.NumMergedRegions;
    41             var returnList = new List<CellRangeAddress>();
    42 
    43             for (int i = 0; i < mergedRegionCellCount; i++)
    44             {
    45                 returnList.Add(sheet.GetMergedRegion(i));
    46             }
    47 
    48             return returnList;
    49         }

    博主只查阅到NPOI有sheet所有合并区域属性,以及获取某合并区域合并坐标方法...故做了此方法来做判断

    2.第6-16行则是在获取合并单元格值,具体思路是:

    合并坐标起始行不等于当前行,若等则代表有列合并(已经是空值),而我们暂不对列合并做值的特殊处理

    resultIndex是计算该合并单元格值已被读取到DTO中的索引

    oldModel是获得含有该合并单元格值的数据对象

     

    这样,我们就成功的读取到了左合并单元格的数据,在入库时稍作处理即可得到我们想要的“水位.潮汐性质”数据字段。

     

    至此,我们已经完成了绝大部分excel表格模板的解析工作。

    上述代码如有任何不对之处,欢迎指出,一定虚心请教~~~

     

    不过,博主的甲方特别难缠,最近又给了一个矩阵模板excel,让解析入库,还说有同样类型的模板很多个....

    意思很明显,这尼玛又得加班加点的搞了...

    模板样式如下:

    出发城市、到达城市内容、个数不定,意思就是连表头内容都是不确定的....

    还要求数据进库后,再能把筛选出来的数据按原模板顺序导出....

     

    好吧,吐血中~~~如果博主下周还活着,请关注下一篇文章查看解决方案(为什么是下周呢?因为尼玛这周末是最后期限...)

     

    原创文章,代码都是从自己项目里贴出来的。转载请注明出处哦,亲~~~

  • 相关阅读:
    idea删除module
    使用腾讯云mysql的一下小坑
    docker 从 tomcat 容器连接到 mysql 容器
    数据结构开发(16):选择排序和插入排序
    数据结构开发(15):递归的思想与应用
    数据结构开发(14):KMP 子串查找算法
    数据结构开发(13):字符串类的创建
    数据结构开发(11):双向循环链表的实现
    数据结构开发(10):Linux内核链表
    数据结构开发(9):循环链表与双向链表
  • 原文地址:https://www.cnblogs.com/csqb-511612371/p/4895548.html
Copyright © 2020-2023  润新知