OpenXML学习笔记(一)从Office 2007 到 Office 2010
最近工作中遇到一个项目,需要在Excel中做一个插件,使得客户端通过插件从远程服务器端(SharePoint Excel Services)下载Excel并显示在本地,用户编辑后再上传到服务器端。我以前没有接触过Office开发,连VBA都没怎么用过,临时查了些资料,最终用VSTO + Excel COM接口完成了。项目虽然是完成了,不过我还是对期间处理Excel COM接口的繁琐过程心有余悸。以前稍微了解一点OpenXML,项目准备阶段我也动了用OpenXML的念头,不过限于工期和自身的OpenXML水平,最终还是放弃了这种打算。这次闲下来决定对OpenXML进行一次系统的学习,结合VSTO的强大功能,研究一下。
本系列所有示例代码均在 Visual Studio 2010 Beta 2 + Office 2010 Beta 下测试通过
一、OpenXML的由来
OpenXML(OOXML)是微软在Office 2007中提出的一种新的文档格式,Office 2007中的Word、Excel、PowerPoint默认均采用OpenXML格式 。OpenXML在2006年12月成为了ECMA规范的一部分,编号为ECMA 376;并于2008年4月通过国际标准化组织的表决,并于两个月后公布为ISO/IEC 29500 国际标准。
二、为什么要使用OpenXML
随着20世纪90年代XML的出现,企业计算客户开始逐渐认识到,在他们所依赖的计算机产品和应用中采用开放的格式和标准所带来的商业价值。IT专业人员将从通用的数据格式中受益匪浅,这种格式可能是XML,因为它拥有被应用程序、平台和Internet浏览器读取的能力。同样,随着在Microsoft Office 2000中对于XML格式的支持与采用,开发人员开始认识到,他们需要将以前的Microsoft Office版本中的二进制文件格式转换为XML格式。二进制文件(.doc,.dot,.xls,以及.ppt文件)在过去几年中一直肩负着存储和转换数据的重任,而现在它们无法满足新的市场需求的挑战,其中包括轻松地在异构应用之间传递数据,以及允许用户从这些数据中搜集商业信息。
OpenXML的出现解决了上述需求,同时改变了基于Microsoft Office文档建立解决方案的方式。在技术底层,OpenXML基于一种约定:开放打包约定(Open Packaging Conventions)。这些约定描述了如何将内容组织到"软件包"中。一些内容示例包括文档、媒体集合以及应用程序库。软件包将内容的所有组件集成为单个对象。可以说OpenXML使用了两种朴实无华却又锋利无比的技术:ZIP和XML,这两种技术都是跨平台的,在使用和推广上没有障碍,如今OpenXML又通过了国际标准,不会像IE那样成为"垄断就是标准",更加使得开发人员对OpenXML产生足够的信心。
三、Office的历史变迁
借用了MSDN网络广播中的一副插图,可以大概看出Office在过去的10年中的演变,从Office 97中的二进制格式,到Office 2007中推出OpenXML,Office已然走过了十年历程。如今Office 2010的测试版已经开放下载,那么在Office 2010中对OpenXML做了哪些改进呢?我看了下Office开发团队的博客,主要有如下几个方面:
- 支持读取新的百分比和度量语法;
- 支持图形上的标题以提高可访问性;
- 支持更多的命名颜色和更长的颜色 MRU 列表;
- 支持新的 contentPart 以持久保持墨迹。
这好像和我的标题Office 2007到Office 2010有些词不达意,我指的是OpenXML出现以来的Office版本:)
四、Office 2007的文档格式
可以看出,加载了宏的文档后缀多了个"m"。
五、OpenXML的架构
OpenXML对于最终用户体验并又没有太大的提升,反而遭到了不少抱怨:XXX按钮在哪?为什么找不到了?
对于开发人员来讲,最关注的还是OpenXML的架构,如何以编程的方式来操作OpenXML文档。
这是OpenXML的组成架构图,可以看出OpenXML由标记语言、矢量标记语言、开放打包约定和核心技术四部分组成。在动手开发OpenXML之前,了解一下这些架构的细节还是很有必要的,后续篇章将会逐一介绍这些架构的细节。
六、怎样开发OpenXML
1、XML
1)System.XML
2)Linq to XML
2、Zip
1)Open Packaging Conventions
2)第三方Zip类库
3)System.IO.Packaging(内置于.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、一个颇具特色的类库LtxOpenXml,可以像操作DataTable一样操作Excel,可以在这里下载。具体用法参见作者博客。
八、开发工具
1、Visual Studio 2008 Team System With SP1或更新版本(推荐)
2、LinqPad
3、Altova XMLSpy 2010 Enterprise Edition
九、OpenXML资源
4、微软 MSDN Webcast OpenXML开发系列课程
十、OpenXML Hello World
说了这么多理论,以Excel为例,让我们马上开始动手做几个简单示例,体验一下OpenXML的强大与快捷。
首先添加程序集引用:
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))//以只读方式打开一个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("//default:sheets/default:sheet", name);//通过XPath表达式获取Worksheet节点
string sheetName = string.Empty;
foreach (XmlNode node in list)
{
sheetName = node.Attributes["name"].Value;//遍历节点,取出属性名
sheets.Add(sheetName);
}
}
return sheets;
}
2、以DataTable的方式操作Excel
注意:要用这种方法必须先在Excel中定义一个表格,具体用法参见作者博客。
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,比第一种方法简洁了不少。
3、对Excel进行"联接"运算
这个示例稍显复杂,是对同一个Excel文件中的不同工作表展开的联结操作:
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();
}
}
主函数调用:
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();
}
小结:
本次主要对Office以及OpenXML进行了简单的整体介绍,重在了解OpenXML整体架构。OpenXML的整体架构还是非常庞大的,牵扯到了方方面面,不过有很多内容是相似的,可以类比记忆。示例代码通过OpenXML SDK 2.0和一个第三方组件实现了比原有COM接口方法简洁的多的操作,这只是冰山一角,后续篇章会以OpenXML SDK为中心,研究OpenXML与其他技术的互操作。
附注:
OpenXML SDK 2.0中附带了一个工具:OpenXMLSDKTool,位于"../OpenXML SDK/v2.0/tool",可以查看OpenXML的结构,并可以对OpenXML进行反编译: