用C# 2.0读写MS Office 2007开放式XML文件以及操作 xlst

一、 简介

在Office 2007中,微软决定把以前老式、专利性、封闭式的文件格式(DOC,XLS,PPT)改变成新的、开放式、标准化的XML格式(DOCX,XLSX和PPTX)。新的格式与旧的Office XML格式(WordML,SpreadsheetML)仍存在一些相似之处,而且也与其竞争对手—OpenOffice.org的OpenDocument格式相类似,但仍然存在许多区别。由于新的格式将成为Office 2007的缺省格式而且微软Office是当前主流的办公自动化套件,所以,这些格式注定会流行开来,而且你迟早要使用它们。

本文将介绍开放式XML文件格式的基本知识,并将详细讨论XLSX格式—Excel 2007的新格式。同时,本文还提供了一个简单但完整的演示程序,实现在XLSX文件中读/写表格化数据。

注意,本示例程序使用Visual Studio 2005 C#语言编写,读者可以使用Excel 2007 Beta版本测试其中创建的XLSX文件。

二、 微软开放式XML格式

Office 2007中的每一种开放式XML文件实质上都是一个包含另外其它许多文件的ZIP档案文件,而Office特定的数据则被存储于该档案文件内的多个XML文件中。这与以前的WordML和SpreadsheetML格式形成鲜明对比—它们是单个的非压缩的XML文件。尽管新格式更复杂一些,但是这种新的方法也提供了下列优点:

· 不必再为了提取特定的数据而处理整个文件。

· 现在,图像和多媒体数据以本地格式编码,而不再作为文本流形式编码。

· 由于压缩和本地多媒体存储等特点,文件比以前明显变小。

用微软的术语来说,开放式XML ZIP文件就是一个包。在包内的文件称为部分。值得注意的是,每一部分都有一个定义的内容类型并且不存在基于文件扩展名的缺省的类型假定(以前是这样)。内容类型可以用来描述任何内容—应用程序XML,用户XML,图像,声音,视频或任何其它二进制对象,等等。每一个部分都必须使用一个关系连接到其它部分。包内是具有“.rels”扩展名的特定的XML文件,由它来定义部分间的关系。还存在一个开始部分,有时也称为“根”。因此,整个包文件结构看起来如图1所示。

 

图1.在XLSX文件内的部分与关系。

简言之,为了从一个开放式XML文件中读取数据,你需要:

1. 以一个ZIP文档文件方式打开包—任何标准ZIP库都可胜任此任务。

2. 查找包含你想读取的数据的部分—你可以通过关系图进行导航,或假定某些部分有定义名和路径。

3. 读取你感兴趣的部分—这可以使用标准XML库(如果它们是XML)或其它一些方法(如果它们是图像,声音或其它类型数据的话)来实现。

另一方面,如果你想创建一个新的开放式XML文件,那么你需要:

1. 创建/得到所有必要的部分—通过使用一些标准XML库(如果它们是XML),或使用其它方法实现。

2. 创建所有的关系—创建“.rels”文件。

3. 创建内容类型—创建“[Content_Types].xml”文件。

4. 使用适当的扩展把所有内容都打包到一个ZIP文件(DOCX,XLSX或PPTX)中—这可以使用任何标准ZIP库来实现。

注意:上面所有这些概念—包,部分,内容类型和关系对于所有的开放式XML文件都是一致的;而且微软统称之为“开放式打包惯例”(Open Packaging Convention)。

三、 Excel 2007开放式XML剖析

通过添加自己特定的XML类型,Excel 2007进一步扩展了开放式打包惯例。当前,你可以从在线MSDN上下载相应于所有的Office XML文件的参考模式定义;当然,在正式发行Excel 2007时还有可能作一些改变。

在本示例中,我们只想读/写工作表数据;因此,我们将重点分析一下XLSX文件(所有的工作表都在其中)内的文件夹“\xl\worksheets”。对于每一个工作表,都存在一个单独的XML文件,例如“sheet1.xml”,“sheet2.xml”等等。在打开这样的文件时,你会注意到,所有的工作表数据都位于元素(element)内。相应于每一行,都存在一个元素;对于每一个单元格都存在一个元素。最后,单元格的值被存储在一个元素中。

然而,真实世界中的XML绝对不是那么简单的。例如,你会注意到,数字是按数字类型编码于元素内的:



   100

 

值得注意的是,字符串值(例如“John”),也是以数字方式在元素中编码的:



0



这是因为MS Excel使用了具有唯一字符串值的内部表格机制。上面的“0”对应一个内部字符串表格的索引,而属性t="s"则告诉我们内在类型是一个字符串而不是一个数字。那么,具有唯一字符串值的表格到底位于何处呢?它位于“\xl\sharedStrings.xml”XML文件中,并且包含整个工作簿内使用的所有字符串,而不仅是相应于特定的工作表。

上面这种方法也适用于另外许多内容,例如单元格风格,边界,图表,数字格式等。事实上,在操作XLSX文件(例如更新与维护相应于一些特定Excel对象的各种表格)时,这是我们要解决的主要的编程问题。在本文中,我们将只读/写数据值;但是,如果你想实现一些更复杂的格式处理的话,那么,你最好使用一些商业组件。

四、 编程示例

我们的示例是一个Windows表单应用程序(见图2),并使用Visual Studio 2005 C#语言编写。由于.NET框架2.0中没有提供直接的ZIP文件支持(仅提供了ZIP算法),所以在我们的演示程序中使用了一个称为SharpZipLib的开源ZIP库。为了说明问题,我们将把整个ZIP文件提取到TEMP文件夹下;这样以来,在调试演示应用程序时,我们就可以分析该文件夹及其下面文件的内容。注意:在实际开发环境下,我们往往不是把数据提取到TEMP文件夹下,而是从ZIP文件中直接读写数据。

对于XML处理,应该是很简单的。我们使用XmlTextReader类来读取XML文件,而使用XmlTextWriter类来完成写操作。这两个类都在.NET框架中提供;当然,你也可以选择使用任何其它的XML工具库。

 

图片2.示例程序快照。

读取数据

我们想读取一个简单的“In.xlsx”文件(位于“Input”文件夹下),并且把它的内容复制到DataTable中。该文件仅包含一个简单的人员(FirstName:string类型;LastName:string类型;ID字段:数字类型)列表。当点击上面的“Read input.xlsx file”按钮时,将执行下列代码:

private void buttonReadInput_Click(object sender, EventArgs e)

{

    //从文本框中取得输入文件名

    string fileName = this.textBoxInput.Text; 

     //删除临时目录中的内容

    ExcelRW.DeleteDirectoryContents(ExcelRWForm.tempDir);

     //把输入XLSX文件解压到临时目录下

    ExcelRW.UnzipFile(fileName,ExcelRWForm.tempDir);

     //使用位于工作簿内的表格(所有唯一的字符串)打开XML文件

    FileStream fs=newFileStream(ExcelRWForm.tempDir + @"\xl\sharedStrings.xml", FileMode.Open,FileAccess.Read);

    //调用助理方法来分析该XML,并且返回一个字符串数组

    ArrayList stringTable = ExcelRW.ReadStringTable(fs);

     //使用工作单数据打开XML文件

fs=newFileStream(ExcelRWForm.tempDir+@"\xl\worksheets\sheet1.xml",FileMode.Open,FileAccess.Read);

    //调用助理方法来分析该XML,并且使用数据填充DataTable

    ExcelRW.ReadWorksheet(fs, stringTable, this.data);

}

没有什么特别之处。其中,XLSX文件被解压到TEMP文件夹,然后处理必要的XML部分(当前文件)。文件sharedStrings.xml包含全局表格(包含所有唯一的字符串),而文件sheet1.xml包含相应于第一个工作表的数据,而其中所用的助理方法也是直接读取XML的代码。

如果一切顺利,点击上面的按钮后,所有的数据将显示于DataGridView控件中。

写数据

现在,我们想把一个DataTable中的数据写入一个位于“Output”文件夹下的Out.xlsx文件中。在本示例中,你可以在DataGridView控件中改变一些数据或添加一些新行。当点击“Write output .xlsx file”按钮时,将执行下列代码:

private void buttonWriteOutput_Click(object sender,EventArgs e) {

    //从文本框中取得输入文件名

    string fileName = this.textBoxOutput.Text; 

     //删除临时目录中的内容

    ExcelRW.DeleteDirectoryContents(ExcelRWForm.tempDir);

     //把模板XLSX文件解压到临时目录下

    ExcelRW.UnzipFile(ExcelRWForm.templateFile,ExcelRWForm.tempDir);

     //注意,我们需要两个字符串表:一个用于快速查询的哈希表,

//另一个是普通的ArrayList(其中的项按索引排序)

    Hashtable lookupTable;

     //调用助理方法以便由输入数据创建两个表格

    ArrayList stringTable = ExcelRW.CreateStringTables(this.data,out lookupTable);

     //创建XML文件

    FileStream fs = newFileStream(ExcelRWForm.tempDir +@"\xl\sharedStrings.xml", FileMode.Create);

    //使用工作簿中提供的唯一字符串填充该文件

    ExcelRW.WriteStringTable(fs, stringTable); 

     //创建XML文件

    fs = newFileStream(ExcelRWForm.tempDir +@"\xl\worksheets\sheet1.xml", FileMode.Create);

    //使用DataTable中的行/列数据填充它

    ExcelRW.WriteWorksheet(fs,this.data,lookupTable); 

     //把临时目录压缩到XLSX文件

    ExcelRW.ZipDirectory(ExcelRWForm.tempDir,fileName);

     //如果选定复选框,则在Excel 2007中显示XLSX文件

    if (this.checkBoxOpenFile.Checked)

        System.Diagnostics.Process.Start(fileName);

}

这部代码稍复杂一些。为了为XLSX文件生成所有必需的部分,我们决定使用一个模板文件。我们把模板文件提取到临时文件夹下,然后只改变包含共享字符串表格和工作表数据的XML部分。所有的其它部分—关系及内容类型都维护不变;因此,我们不需要生成任何其它内容。注意,我们使用了两个字符串表格:一个是用于快速查询的哈希表—Hashtable,另一个是普通的ArrayList—其中的项按索引存储。另外,助理方法CreateStringTables()负责构建这两个字符串表格,而助理方法WriteStringTable()则实现写字符串表格XML,最后的WriteWorksheet()方法则负责写工作表数据XML。

五、 小结

本文仅对Office 2007文件格式作了入门性编程介绍。另外,我们也能觉察到开放世界对于微软帝国战略观念的影响。另一方面,既然微软Office是当前主流的办公自动化套件,而在不久的将来也未免不如此;所以,其相应的文件格式也注定会流行开来。因此,作为Office相关技术的开发人员益早下手为强。
posted @ 2008-11-17 11:19  海底的鱼  阅读(1808)  评论(0编辑  收藏  举报