Excel的便捷使得其在非開發人員的辦公中非常流行,而Excel確實也提供了很多有用的功能。很多時候我們還需要以Excel為數據源來進行處理或者將Excel作為
模板
來生成一些
報表
。在Open XML SDK沒出來之前,我們大多采用引用Office類庫的方法來做處理,但這樣的操作顯得很麻煩。而Open XML SDK的出現,讓我們可以以一個很自然的方式來處理這些數據,本文我們就一起了解一下如何使用Open XML SDK來通過LINQ to XML的方式操作Excel。
這些代碼是建立在Open XML SDK CTP 2基礎上的,使用前請先下載Open XML Format SDK 2.0。點擊
這里
下載。SDK默認會
安裝
在C:\Program Files (x86)\Open XML Format SDK\V2.0 (64bit)目錄下,lib子目錄下的DocumentFormat.OpenXml.dll必須被引用到項目中。
Excel也罷,word也罷,他們都是通過Open XML的標準來組織特定標記的。其實,你只要理解這些標記的含義,你可以不用任何
工具
自己來解析XML得到你想要的內容和格式。而Open XML SDK提供給我們的是更統一的解析方式。通過下邊傳統的DOM解析,你可以得到一個Excel的worksheet集合。
-
public static List<String> GetSheets(String strFileName)
-
-
{
-
-
// Fill this collection with a list of all the sheets.
-
-
List<String> sheets = new List<String>();
-
-
-
-
using (SpreadsheetDocument xlPackage = SpreadsheetDocument.Open(strFileName, false))
-
-
{
-
-
WorkbookPart workbook = xlPackage.WorkbookPart;
-
-
Stream workbookstr = workbook.GetStream();
-
-
XmlDocument doc = new XmlDocument();
-
-
doc.Load(workbookstr);
-
-
-
-
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
-
-
nsManager.AddNamespace("default", doc.DocumentElement.NamespaceURI);
-
-
XmlNodeList nodelist = doc.SelectNodes("http://default:sheets/default:sheet", nsManager);
-
-
-
-
foreach (XmlNode node in nodelist)
-
-
{
-
-
String sheetName = String.Empty;
-
-
sheetName = node.Attributes["name"].Value;
-
-
sheets.Add(sheetName);
-
-
}
-
-
}
-
-
-
-
return sheets;
-
-
}
復制代碼
對于每一個工作簿而言,Open XML Format SDK將它以不同對象之間的關系來組織。在下邊的圖中你可以看到這些內嵌的對象間的關系。需要說明的是,他們都代表在Excel中不同的應用,例如Columns,你可以通過它得到你的工作簿中的列。TablePart則列舉了在工作簿中以Table(可以理解為以數據表,具有過濾,排序,匯總等功能)方式展現的數據區域。
而我們可能更關心的是如何來得到行、列單元格內的值。當然,worksheet除了這些對象(關系)集合外,它也通過直觀的行(Row)、列(Cell)來組織內容區域。通過LINQ to XML我們可以很容易的通過Descendents來得到。
-
IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == strSheet);
-
-
if (sheets.Count() == 0)
-
-
{
-
-
// The specified worksheet does not exist.
-
-
return null;
-
-
}
-
-
-
-
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheets.First().Id);
-
-
Worksheet worksheet = worksheetPart.Worksheet;
-
-
-
-
//Ignore row header
-
-
IEnumerable<Row> rows = worksheet.Descendants<Row>();
-
-
foreach (Row row in rows)
-
-
{
-
-
foreach (Cell cell in row)
-
-
{
-
-
……
-
-
}
-
-
}
復制代碼
能夠對Row和Cell進行遍歷,這就容易多了。你可以很容易的取出某一行某一列,或者所有列的數據并輸出(當然,同等的道理,你也可以寫入),操作起來是不是很方便呢?但不要忘記了,你很難得到cell的值:cell.InnerText并不是在任何時候都有效的。對于s類型的列(SharedStringTable類型),它的值是
存儲
在子元素內的。
-
public static String GetValue(Cell cell, SharedStringTablePart stringTablePart)
-
-
{
-
-
if (cell.ChildElements.Count == 0)
-
-
return null;
-
-
//get cell value
-
-
String value = cell.CellValue.InnerText;
-
-
//Look up real value from shared string table
-
-
if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString))
-
-
value = stringTablePart.SharedStringTable
-
-
.ChildElements[Int32.Parse(value)]
-
-
.InnerText;
-
-
return value;
-
-
}
復制代碼
這時,你可以通過String columnValue = GetValue(cell, tablePart);的方式來取得單元格值。tablePart就是WorkbookPart.SharedStringTablePart,它存儲了所有以SharedStringTable方式存儲數據的子元素。
再擴展一下如果你想將一個Excel的工作簿以強類型展示,那該如何做呢?列明,每一個行代表一個對象元素,通過反射來來對對象賦值。請注意在工作簿中并不是每個列都能和你的類屬性對應的,所以必須判斷。當然,你也可以通過中間元素來產生映射擴展。
-
//get SharedStringTablePart to get the cell value.
-
-
SharedStringTablePart tablePart = document.WorkbookPart.SharedStringTablePart;
-
-
//Column headers
-
-
String[] cellHeaders = null;
-
-
String[] cellValues = null;
-
-
-
-
//Ignore row header
-
-
IEnumerable<Row> rows = worksheet.Descendants<Row>();
-
-
foreach (Row row in rows)
-
-
{
-
-
if (row.RowIndex == 1)
-
-
{
-
-
cellHeaders = new String[row.Count()];
-
-
}
-
-
cellValues = new String[row.Count()];
-
-
int i = 0;
-
-
-
-
foreach (Cell cell in row)
-
-
{
-
-
String columnValue = GetValue(cell, tablePart);
-
-
-
-
//The first row is header
-
-
if (row.RowIndex == 1)
-
-
{
-
-
cellHeaders = columnValue;
-
-
}
-
-
else
-
-
{
-
-
cellValues = columnValue;
-
-
}
-
-
i++;
-
-
}
-
-
if (row.RowIndex > 1)
-
-
{
-
-
products.Add(ProductConverter.Convert(cellValues, cellHeaders));
-
-
}
-
-
}
復制代碼
對最終的單元格值集合到Product對象的轉換,我們通過ProductConverter類來完成。在這里,你可以通過反射來完成,但枚舉出所有你可能用到的類型是你不得不面對的問題。
-
foreach (PropertyInfo pi in product.GetType().GetProperties())
-
-
{
-
-
for (int i = 0; i < cellHeader.Length; i++)
-
-
{
-
-
if (pi.Name.Equals(cellHeader, StringComparison.OrdinalIgnoreCase))
-
-
{
-
-
//get property type
-
-
String propertyType = pi.PropertyType.Name;
-
-
switch (propertyType)
-
-
{
-
-
case "Int32":
-
-
pi.SetValue(product, int.Parse(cellValues), null);
-
-
break;
-
-
case "DateTime":
-
-
pi.SetValue(product, System.DateTime.Parse(cellValues), null);
-
-
break;
-
-
case "Decimal":
-
-
pi.SetValue(product, Decimal.Parse(cellValues), null);
-
-
break;
-
-
case "Double":
-
-
pi.SetValue(product, Double.Parse(cellValues), null);
-
-
break;
-
-
case "String":
-
-
pi.SetValue(product, cellValues, null);
-
-
break;
-
-
}
-
-
break;
-
-
}
-
-
}
-
-
}
復制代碼
通過將數據展現到
UI
上,你可以驗證你的工作 是否成功:
-
List<Product> products = SpreadSheetFunction.GetProducts(strFileName, "Products");
-
-
this.dataGridView1.DataSource = products;
復制代碼
其實對于Excel中的Table可能更有意思。因為你可以通過它來實現過濾,排序,匯總,你會感覺它特別方便(起碼比Reporting Service來得快多了)。我們會再介紹如何通過更簡單的辦法來對Excel Table操作。(文/
Allan.
)