咱们在第三篇文章中谈到了那些很是反人类的excel模板,博主为了养家糊口,也玩命作出了相应的解析方法...html
咱们先来看看第一类复杂表头:数据库
......spa
博主称这类excel模板为略复杂表头模板(蓝色部分为表头部分,蓝色前面几行是博主项目的基础样式,称为元数据),这类excel的表头多为2-3行,甚至于5/6行 ,具备合并层级关系,看似复杂,但只须要在咱们之前的基础上稍微作一下重构就能够完美实现解析。3d
咱们以各地区户籍人口城乡构成表头为例:excel
其实,只要咱们能准确解析这类表头所表达的意思,就能复用之前的代码作解析工做code
也就是说,重点在于表头解析方法GetExcelHeaders(),xml
咱们返回看第三篇文章http://www.cnblogs.com/csqb-511612371/p/4891492.html中这个方法的代码:htm
第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 }
咱们在这儿作了一个列的循环,对表头所在行每一列作了一个值合并,那么咱们能够预料:这个表头解析出来的结果:blog
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)) //二、单元格为合并单元格且不在合并区域左上角 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 //一、单元格空值 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,让解析入库,还说有一样类型的模板不少个....
意思很明显,这尼玛又得加班加点的搞了...
模板样式以下:
出发城市、到达城市内容、个数不定,意思就是连表头内容都是不肯定的....
还要求数据进库后,再能把筛选出来的数据按原模板顺序导出....
好吧,吐血中~~~若是博主下周还活着,请关注下一篇文章查看解决方案(为何是下周呢?由于尼玛这周末是最后期限...)
原创文章,代码都是从本身项目里贴出来的。转载请注明出处哦,亲~~~