NPOI操做EXCEL(五)——含合併單元格複雜表頭的EXCEL解析

咱們在第三篇文章中談到了那些很是反人類的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,讓解析入庫,還說有一樣類型的模板不少個....

意思很明顯,這尼瑪又得加班加點的搞了...

模板樣式以下:

出發城市、到達城市內容、個數不定,意思就是連表頭內容都是不肯定的....

還要求數據進庫後,再能把篩選出來的數據按原模板順序導出....

 

好吧,吐血中~~~若是博主下週還活着,請關注下一篇文章查看解決方案(爲何是下週呢?由於尼瑪這週末是最後期限...)

 

原創文章,代碼都是從本身項目裏貼出來的。轉載請註明出處哦,親~~~

相關文章
相關標籤/搜索