咱們在第三篇文章中談到了那些很是反人類的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,讓解析入庫,還說有一樣類型的模板不少個....
意思很明顯,這尼瑪又得加班加點的搞了...
模板樣式以下:
出發城市、到達城市內容、個數不定,意思就是連表頭內容都是不肯定的....
還要求數據進庫後,再能把篩選出來的數據按原模板順序導出....
好吧,吐血中~~~若是博主下週還活着,請關注下一篇文章查看解決方案(爲何是下週呢?由於尼瑪這週末是最後期限...)
原創文章,代碼都是從本身項目裏貼出來的。轉載請註明出處哦,親~~~