OpenXML學(xué)習(xí)筆記(一)從Office 2007 到 Office 2010
最近工作中遇到一個(gè)項(xiàng)目,需要在Excel中做一個(gè)插件,使得客戶端通過(guò)插件從遠(yuǎn)程服務(wù)器端(SharePoint Excel Services)下載Excel并顯示在本地,用戶編輯后再上傳到服務(wù)器端。我以前沒(méi)有接觸過(guò)Office開(kāi)發(fā),連VBA都沒(méi)怎么用過(guò),臨時(shí)查了些資料,最終用VSTO + Excel COM接口完成了。項(xiàng)目雖然是完成了,不過(guò)我還是對(duì)期間處理Excel COM接口的繁瑣過(guò)程心有余悸。以前稍微了解一點(diǎn)OpenXML,項(xiàng)目準(zhǔn)備階段我也動(dòng)了用OpenXML的念頭,不過(guò)限于工期和自身的OpenXML水平,最終還是放棄了這種打算。這次閑下來(lái)決定對(duì)OpenXML進(jìn)行一次系統(tǒng)的學(xué)習(xí),結(jié)合VSTO的強(qiáng)大功能,研究一下。
本系列所有示例代碼均在 Visual Studio 2010 Beta 2 + Office 2010 Beta 下測(cè)試通過(guò)
一、OpenXML的由來(lái)
OpenXML(OOXML)是微軟在Office 2007中提出的一種新的文檔格式,Office 2007中的Word、Excel、PowerPoint默認(rèn)均采用OpenXML格式 。OpenXML在2006年12月成為了ECMA規(guī)范的一部分,編號(hào)為ECMA 376;并于2008年4月通過(guò)國(guó)際標(biāo)準(zhǔn)化組織的表決,并于兩個(gè)月后公布為ISO/IEC 29500 國(guó)際標(biāo)準(zhǔn)。
二、為什么要使用OpenXML
隨著20世紀(jì)90年代XML的出現(xiàn),企業(yè)計(jì)算客戶開(kāi)始逐漸認(rèn)識(shí)到,在他們所依賴的計(jì)算機(jī)產(chǎn)品和應(yīng)用中采用開(kāi)放的格式和標(biāo)準(zhǔn)所帶來(lái)的商業(yè)價(jià)值。IT專業(yè)人員將從通用的數(shù)據(jù)格式中受益匪淺,這種格式可能是XML,因?yàn)樗鼡碛斜粦?yīng)用程序、平臺(tái)和Internet瀏覽器讀取的能力。同樣,隨著在Microsoft Office 2000中對(duì)于XML格式的支持與采用,開(kāi)發(fā)人員開(kāi)始認(rèn)識(shí)到,他們需要將以前的Microsoft Office版本中的二進(jìn)制文件格式轉(zhuǎn)換為XML格式。二進(jìn)制文件(.doc,.dot,.xls,以及.ppt文件)在過(guò)去幾年中一直肩負(fù)著存儲(chǔ)和轉(zhuǎn)換數(shù)據(jù)的重任,而現(xiàn)在它們無(wú)法滿足新的市場(chǎng)需求的挑戰(zhàn),其中包括輕松地在異構(gòu)應(yīng)用之間傳遞數(shù)據(jù),以及允許用戶從這些數(shù)據(jù)中搜集商業(yè)信息。
OpenXML的出現(xiàn)解決了上述需求,同時(shí)改變了基于Microsoft Office文檔建立解決方案的方式。在技術(shù)底層,OpenXML基于一種約定:開(kāi)放打包約定(Open Packaging Conventions)。這些約定描述了如何將內(nèi)容組織到"軟件包"中。一些內(nèi)容示例包括文檔、媒體集合以及應(yīng)用程序庫(kù)。軟件包將內(nèi)容的所有組件集成為單個(gè)對(duì)象。可以說(shuō)OpenXML使用了兩種樸實(shí)無(wú)華卻又鋒利無(wú)比的技術(shù):ZIP和XML,這兩種技術(shù)都是跨平臺(tái)的,在使用和推廣上沒(méi)有障礙,如今OpenXML又通過(guò)了國(guó)際標(biāo)準(zhǔn),不會(huì)像IE那樣成為"壟斷就是標(biāo)準(zhǔn)",更加使得開(kāi)發(fā)人員對(duì)OpenXML產(chǎn)生足夠的信心。
三、Office的歷史變遷
借用了MSDN網(wǎng)絡(luò)廣播中的一副插圖,可以大概看出Office在過(guò)去的10年中的演變,從Office 97中的二進(jìn)制格式,到Office 2007中推出OpenXML,Office已然走過(guò)了十年歷程。如今Office 2010的測(cè)試版已經(jīng)開(kāi)放下載,那么在Office 2010中對(duì)OpenXML做了哪些改進(jìn)呢?我看了下Office開(kāi)發(fā)團(tuán)隊(duì)的博客,主要有如下幾個(gè)方面:
- 支持讀取新的百分比和度量語(yǔ)法;
- 支持圖形上的標(biāo)題以提高可訪問(wèn)性;
- 支持更多的命名顏色和更長(zhǎng)的顏色 MRU 列表;
- 支持新的 contentPart 以持久保持墨跡。
這好像和我的標(biāo)題Office 2007到Office 2010有些詞不達(dá)意,我指的是OpenXML出現(xiàn)以來(lái)的Office版本:)
四、Office 2007的文檔格式
可以看出,加載了宏的文檔后綴多了個(gè)"m"。
五、OpenXML的架構(gòu)
OpenXML對(duì)于最終用戶體驗(yàn)并又沒(méi)有太大的提升,反而遭到了不少抱怨:XXX按鈕在哪?為什么找不到了?
對(duì)于開(kāi)發(fā)人員來(lái)講,最關(guān)注的還是OpenXML的架構(gòu),如何以編程的方式來(lái)操作OpenXML文檔。
這是OpenXML的組成架構(gòu)圖,可以看出OpenXML由標(biāo)記語(yǔ)言、矢量標(biāo)記語(yǔ)言、開(kāi)放打包約定和核心技術(shù)四部分組成。在動(dòng)手開(kāi)發(fā)OpenXML之前,了解一下這些架構(gòu)的細(xì)節(jié)還是很有必要的,后續(xù)篇章將會(huì)逐一介紹這些架構(gòu)的細(xì)節(jié)。
六、怎樣開(kāi)發(fā)OpenXML
1、XML
1)System.XML
2)Linq to XML
2、Zip
1)Open Packaging Conventions
2)第三方Zip類庫(kù)
3)System.IO.Packaging(內(nèi)置于.Net Framework 3.0)
3、OpenXML SDK(推薦)
七、OpenXML組件
1、OpenXML SDK 2.0,可以在這里下載。
2、Open XML Format SDK 2.0 Code Snippets for Visual Studio 2008,可以在這里下載。
3、一個(gè)頗具特色的類庫(kù)LtxOpenXml,可以像操作DataTable一樣操作Excel,可以在這里下載。具體用法參見(jiàn)作者博客。
八、開(kāi)發(fā)工具
1、Visual Studio 2008 Team System With SP1或更新版本(推薦)
2、LinqPad
3、Altova XMLSpy 2010 Enterprise Edition
九、OpenXML資源
4、微軟 MSDN Webcast OpenXML開(kāi)發(fā)系列課程
十、OpenXML Hello World
說(shuō)了這么多理論,以Excel為例,讓我們馬上開(kāi)始動(dòng)手做幾個(gè)簡(jiǎn)單示例,體驗(yàn)一下OpenXML的強(qiáng)大與快捷。
首先添加程序集引用:
DocumentFormat.OpenXml.dll
LtxOpenXml.dll
Windowsbase
然后添加:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using Microsoft.Examples.LtxOpenXml;
using System.IO;
using System.Xml;
1、獲取指定Excel的工作表名集合:
private List<string> fnGetSheet(string v_strPath)
{
List<string> sheets = new List<string>();
using (SpreadsheetDocument xlPackage = SpreadsheetDocument.Open(v_strPath, false))//以只讀方式打開(kāi)一個(gè)Excel文件
{
WorkbookPart part = xlPackage.WorkbookPart;//獲取Workbook
Stream stream = part.GetStream();
XmlDocument doc = new XmlDocument();
doc.Load(stream);//加載流
XmlNamespaceManager name = new XmlNamespaceManager(doc.NameTable);
name.AddNamespace("default", doc.DocumentElement.NamespaceURI);
XmlNodeList list = doc.SelectNodes("http://default:sheets/default:sheet", name);//通過(guò)XPath表達(dá)式獲取Worksheet節(jié)點(diǎn)
string sheetName = string.Empty;
foreach (XmlNode node in list)
{
sheetName = node.Attributes["name"].Value;//遍歷節(jié)點(diǎn),取出屬性名
sheets.Add(sheetName);
}
}
return sheets;
}
2、以DataTable的方式操作Excel
注意:要用這種方法必須先在Excel中定義一個(gè)表格,具體用法參見(jiàn)作者博客。
private void fnExcelTable(string v_strPath)
{
using (SpreadsheetDocument package = SpreadsheetDocument.Open(v_strPath, false))
{
var query = from t in package.Table("Inventory").TableRows()
where (int)t["Qty"] > 2
select t;
foreach (var s in query)
{
Console.WriteLine(s["Item"]);
Console.WriteLine(s["Qty"]);
Console.WriteLine(s["Price"]);
Console.WriteLine(s["Extension"]);
}
}
}
使用Linq操作Excel,比第一種方法簡(jiǎn)潔了不少。
3、對(duì)Excel進(jìn)行"聯(lián)接"運(yùn)算
這個(gè)示例稍顯復(fù)雜,是對(duì)同一個(gè)Excel文件中的不同工作表展開(kāi)的聯(lián)結(jié)操作:
private void fnJoin(string v_strPath)
{
using (SpreadsheetDocument package = SpreadsheetDocument.Open(v_strPath, false))
{
var query = from c in package.Table("Customer").TableRows()
where (string)c["City"] == "London"
select new
{
CustomerID = c["CustomerID"],
ContactName = c["ContactName"],
CompanyName = c["CompanyName"],
Orders = from o in package.Table("Order").TableRows()
where (string)o["CustomerID"] == (string)c["CustomerID"]
select new
{
CustomerID = o["CustomerID"],
OrderID = o["OrderID"]
}
};
// print the results of the query
int[] tabs = new[] { 20, 25, 30 };
Console.WriteLine("{0}{1}{2}",
"CustomerID".PadRight(tabs[0]),
"CompanyName".PadRight(tabs[1]),
"ContactName".PadRight(tabs[2]));
Console.WriteLine("{0} {1} {2} ", new string('-', tabs[0] - 1),
new string('-', tabs[1] - 1), new string('-', tabs[2] - 1));
foreach (var v in query)
{
Console.WriteLine("{0}{1}{2}",
v.CustomerID.Value.PadRight(tabs[0]),
v.CompanyName.Value.PadRight(tabs[1]),
v.ContactName.Value.PadRight(tabs[2]));
foreach (var v2 in v.Orders)
{
Console.WriteLine(" CustomerID:{0} OrderID:{1}", v2.CustomerID, v2.OrderID);
}
Console.WriteLine();
}
}
主函數(shù)調(diào)用:
static void Main(string[] args)
{
OpenXMLHelloWorld ooxml = new OpenXMLHelloWorld();
List<string> list = ooxml.fnGetSheet(@"../Excel/Book1.xlsx");
foreach (string s in list)
{
Console.WriteLine(s);
}
Console.WriteLine();
ooxml.fnExcelTable(@"../Excel/Inventory.xlsx");
Console.WriteLine();
ooxml.fnJoin(@"../Excel/Northwind.xlsx");
Console.ReadLine();
}
小結(jié):
本次主要對(duì)Office以及OpenXML進(jìn)行了簡(jiǎn)單的整體介紹,重在了解OpenXML整體架構(gòu)。OpenXML的整體架構(gòu)還是非常龐大的,牽扯到了方方面面,不過(guò)有很多內(nèi)容是相似的,可以類比記憶。示例代碼通過(guò)OpenXML SDK 2.0和一個(gè)第三方組件實(shí)現(xiàn)了比原有COM接口方法簡(jiǎn)潔的多的操作,這只是冰山一角,后續(xù)篇章會(huì)以OpenXML SDK為中心,研究OpenXML與其他技術(shù)的互操作。
附注:
OpenXML SDK 2.0中附帶了一個(gè)工具:OpenXMLSDKTool,位于"../OpenXML SDK/v2.0/tool",可以查看OpenXML的結(jié)構(gòu),并可以對(duì)OpenXML進(jìn)行反編譯:

浙公網(wǎng)安備 33010602011771號(hào)