📊 用 ClosedXML 讀取三行合併表頭的 Excel 資料(含欄位名稱查找技巧)
Copyright Notice: This article is an original work licensed under the CC 4.0 BY-NC-ND license.
If you wish to repost this article, please include the original source link and this copyright notice.
Source link: https://v2know.com/article/1148
前言
在處理 Excel 檔時,表頭不一定總是單行的。有些報表的表頭可能會長成這樣:
資産情報 | 担当者 | ||
氏名 | 年齢 | ||
氏名 | 年齢 |
這樣的三行表頭、跨列合併,對程式來說根本就是「惡夢」。
你想做的事可能很簡單:
根據「欄位名稱」去讀取對應的資料列,比如
"担当者 > 氏名 > 年齢"
。
但 ClosedXML 並不會自動幫你解析這種結構,我們要自己動手,把「表頭→欄位編號」的對照表建立出來。
一、ClosedXML 不會幫你自動識別合併表頭
ClosedXML 是一個操作 Excel 的 C# 函式庫,它很強大,但它對於多行表頭+合併儲存格:
-
不會展開合併儲存格的所有位置;
-
不會知道「這一欄實際上應該叫什麼名字」。
也就是說,你要自己判斷:
-
哪個 cell 是被合併的;
-
如果是合併儲存格,那該去看哪一格的值(左上角);
-
最後要「拼接」表頭值來產出像
"担当者 > 氏名 > 年齢"
這種「邏輯欄位名」。
二、怎麼解析三行合併表頭?
我們先給出封裝好的工具類 ExcelHeaderReader
,讓你不用每次都寫重複邏輯。
工具類 ExcelHeaderReader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using ClosedXML.Excel;
public class ExcelHeaderReader
{
private readonly IXLWorksheet _worksheet;
private readonly int _headerRows;
private readonly Dictionary<int, string> _columnHeaders = new();
public ExcelHeaderReader(IXLWorksheet worksheet, int headerRows = 3)
{
_worksheet = worksheet;
_headerRows = headerRows;
BuildColumnHeaders();
}
private void BuildColumnHeaders()
{
var mergedLookup = new Dictionary<(int row, int col), IXLCell>();
foreach (var range in _worksheet.MergedRanges)
{
var topLeft = range.FirstCell();
foreach (var cell in range.Cells())
mergedLookup[(cell.Address.RowNumber, cell.Address.ColumnNumber)] = topLeft;
}
int maxCol = _worksheet.LastColumnUsed().ColumnNumber();
for (int col = 1; col <= maxCol; col++)
{
var parts = new List<string>();
for (int row = 1; row <= _headerRows; row++)
{
var cell = _worksheet.Cell(row, col);
if (cell.IsMerged() || mergedLookup.ContainsKey((row, col)))
cell = mergedLookup[(row, col)];
var value = cell.GetString().Trim();
if (!string.IsNullOrEmpty(value))
parts.Add(value);
}
_columnHeaders[col] = string.Join(" > ", parts);
}
}
public string? GetValueByHeaderName(string headerName, int dataRow)
{
var match = _columnHeaders.FirstOrDefault(kvp => kvp.Value == headerName);
return match.Key != 0 ? _worksheet.Cell(dataRow, match.Key).GetString() : null;
}
public Dictionary<int, string> GetAllHeaders() => _columnHeaders;
public int? GetColumnIndexByHeader(string headerName)
{
return _columnHeaders.FirstOrDefault(kvp => kvp.Value == headerName).Key;
}
}
三、使用方式
1. 載入 Excel 並建立 reader:
var wb = new XLWorkbook("三行合併表頭範例.xlsx");
var ws = wb.Worksheet(1);
var reader = new ExcelHeaderReader(ws, headerRows: 3);
2. 查找欄位名稱:
var colIndex = reader.GetColumnIndexByHeader("担当者 > 氏名 > 年齢");
Console.WriteLine($"該欄在第 {colIndex} 欄");
3. 根據欄位名稱取值:
for (int row = 4; row <= ws.LastRowUsed().RowNumber(); row++)
{
var value = reader.GetValueByHeaderName("担当者 > 氏名 > 年齢", row);
Console.WriteLine($"Row {row}: {value}");
}
四、解析出來的欄位可能長這樣
欄位位置 | 組合欄位名稱 |
---|---|
A | 資産情報 > 資産情報 > 資産情報 |
B | 担当者 > 氏名 > 氏名 |
C | 担当者 > 氏名 > 年齢 |
D | 担当者 > 年齢 > 年齢 |
你就可以根據這些名稱來查欄、讀值。
五、進階建議
你也可以進一步封裝:
-
去掉連續重複名稱(如
氏名 > 氏名
→氏名
) -
加入模糊搜尋(如包含
氏名
就匹配) -
加上 UI ComboBox 下拉選擇
結語
處理多層合併表頭的 Excel 是一項挑戰,但只要你掌握:
-
合併儲存格如何定位
-
多行表頭如何組合成欄位名
-
如何建立「欄位名 ↔ 欄位編號」映射
你就可以優雅地從 Excel 中精準取值,像讀 JSON 一樣舒服。
This article was last edited at