Office Open XML 文档格式 (转)
Office Open XML 文档格式
Office Open XML文档格式的详细说明以及规格是在2006年欧洲计算机制造商协会批准的一项标准, 编号是Ecma376, 到发稿时为止已经通过了ISO国际标准化组织的评审成为了一项国际标准ISO/IEC 29500:2008. 你可以下载Office Open XML的详细说明文档, 地址在http://openxmldeveloper.com, 当然这个站点上还有许多其他的优秀在线资源. 我们这篇文章的目的是让你了解一下这种文档格式.
Office Open XML 文档格式的初衷
==================
过去, 想要书写或者部署能够读写, 修改, 生成可以被Microsoft Office应用程序套装使用的文档的服务器端应用程序是很有挑战性的. 那是种很老的二进制文档格式, 它是在1997年引入的, 一直到Microsoft Office 2003版本一直是Office默认的文档格式. 经验表明这个二进制文档格式已经被证明对于绝大多数公司都是非常难以直接操作的, 因为一个代码中小小的错误就会使得生成或者修改过的Office文档整个崩溃损坏. 很大一部分的读写2003版Office文档的生产应用程序通过宿主应用程序的对象模型来操纵文档.
使用应用程序对象模型的, 比如Word, Excel, 自定义的应用程序和组件在desktop上运行比在服务器端环境下运行要更好. 任何一个花时间写过这种在服务器端让客户端应用程序平稳运行的额外架构代码的人都会告诉你, 这是一个高难度的活儿, 因为诸如Word, Excel这一类的客户端应用程序从来就没有被设计为在服务器端运行. 每当他们遇到一个需要人机交互的模态对话框时, 他们都需要一个自定义的工具程序来终结和重启这些客户端应用程序.
服务器端应用场景更加需要的是一种能力, 一种可以读写文档而不需要通过宿主应用程序对象模型的能力. Microsoft Office 2000和Microsoft Office 2003引入了一些适度的, 使用XML来为Excel工作簿和Word文档创建内容的能力. 这些进步引入了通过XML解析器来修改文档的一部分的可能性, 这里的XML解析器可以是包含在.NET Framework中的, 由System.XML命名空间提供的哦.
通过2007 Microsoft Office System, 微软通过采用可以被word, excel, 还有powerpoint使用的Office Open XML文档格式的方式, 带着这个想法走的更远了. Office Open XML 文档格式对于WSS和MOSS系统的开发人员来说, 来说是一项令人振奋的进步, 因为这些文档格式提供了在服务器端读, 写, 生成word, excel, powerpoint文档的能力, 而不需要web服务器运行desktop的应用程序.
Word 2007 文档技术内幕
=================
让我们先检查一下一个使用Office Open XML文档格式的简单Word文档的结构吧. Office Open XML文档格式是基于标准ZIP技术之上的. 任何一个顶层水平的文档都被存储为一个ZIP压缩包, 这意味着你可以像打开其他ZIP文件一样来打开Word文档, 然后使用内嵌入Windows Exlorer中的ZIP文件的支持能力来窥探一下文档的内部结构.
你应该注意到2007 Microsoft Office应用程序套装, 比如说Word和Excel, 为使用新文档格式的文档引入了新的文件扩展名. 举个例子, 使用Office Open XML格式存储的Word文档的扩展名为.docx, 而老的大家都比较熟悉的.doc扩展名继续用来描述使用老的二进制格式存储的Word文档身上.
一旦Word2007被安装上, 你就可以开始创建一个新的Word文档, 添加点文字"Hello World". 使用默认的文档格式保存文档, 文件名为Hello.docx, 然后关闭Word. 下一步, 使用Windows Explorer在文件系统中找到Hello.docx. 把它重命名为Hello.zip. 这使得Windows Explorer可以把这个文件识别为ZIP包. 你现在可以打开Hello.zip包了, 然后可以看到有Word创建的文件和文件夹结构. 如下图:
图表1. 一个docx文件被命名为ZIP后, 查看里面包含的部分和文件项
在快速浏览了.docx文件的内部结构之后, 现在是时间介绍一下应用Office Open XML文档格式的文件中涉及到的一些基本概念和术语了. 顶级的文件(比如说Hello.docx)被叫做package(包). 因为包(package)是被实现为一个标准ZIP包的, 它自动地提供了对文档的压缩, 还有供Windows平台和非Windows平台的工具程序和API即时地读取文档中内容的能力.
在包中有两种内部的组件: parts和items. 总的来说, parts包括文档内容和一些含有用来描述parts的元数据的items. Items可以被进一步的细分为relationship items和content-type items. 我们现在就更深入地讨论一下每一种组件的细节吧.
part是包含序列化了的内容的包(package)内组件. 多数的part是些简单的, 根据相关联的XML schema序列化为XML的文本文件. 然而, parts还可以在必要的时候被序列化为二进制数据, 比如说当一个word文档包含一个图片或者媒体文件的时候.
一个part通过统一资源标示符来命名的, 由相对于包的相对路径和part文件的文件名组成. 举例, Word文档的包内首要part叫做/word/document.xml. 下面的列表展现了一些典型的part的名字, 你可以在简单的word文档的包内找到它们.
/docProps/app.xml
/docProps/core.xml
/word/document.xml
/word/fontTable.xml
/word/settings.xml
/word/styles.xml
/word/theme/theme1.xml
Office Open XML文档格式使用relationships来定义一个源part和一个目标part之间的关系. package relationship定义一个part与顶级包之间的关系. part relationship定义一个父part和子part之间的关系.
Relationship很重要, 因为它们使得这些关联关系可以被发现, 且不需要检查和问询parts中的内容. Relationship是独立于具体内容的schema的, 所以处理起来就更快. 额外的好处是你可以建立一种两个part之间的关系, 而不需要修改这两个part中的任何一个.
Relationship是定义在一种内部组件relationship item中的. relation item在包中像一个part一样的被存储, 然而relationship item并不会真正地被看做是一个part. 处于一致性的考虑, relationship items总是被创建在一个命名为_rels的文件夹当中.
For example, a package contains exactly one package relationship item named /_rels/.rels. The package relationship item contains XML elements to define package relationships, such as the one between the top-level package for a .docx file and the internal part /word/document.xml.
举例, 一个包里正是包含一个包的relationship的item, 叫做/_rels/.rels. 这个package relationship item包含用来定义package relationship的XML元素, 诸如.docx文件对应的顶级包与你哥内部part /word/document.xml之间的关系.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="../package/2006/relationships "> <Relationship Id="rId1" Type="../officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/> </Relationships>
正如你所见, 一个Relationship元素定义了一个名字, 类型, 还有一个目标part. 你应该也观察到了relationship的类型的名字是用跟创建XML命名空间一样一样的约定定义的.
除了单个的package relationship item, 一个包还可以包含一个或多个part relationship items. 比如说, 你定义了/word/document.xml与包内的位于URI /word/_rels/document.xml.rels中的child parts之间的关系. 注意, 在一个part relationship item中, relationship的目标属性(target attribute)是一个相对于parent part, 而不是顶级包的URI.
每一个包中的part都被使用具体的content type术语来定义. 不要把这些content type与WSS定义的contet type混淆了, 因为这两个是完全不同的. 包中的content type是定义了part的媒体类型, 子类型, 还有一系列可选参数的元数据. 任何在包中使用的content type都必须被显式地定义在一个叫做content type item的组件之中. 任何的包都有只一个content type item叫做/[Content_Types].xml. 下面就是一个典型的Word文档中, 在/[Content_Types].xml内部定义content type的例子.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="rels" ContentType="application/vnd.openxmlformats package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats officedocument.wordprocessingml.document.main+xml "/> </Types>
Content types被包的使用者(消费者)用来解释如何读取和渲染包里parts的内容. 正如你在前面的列表中看到的, 一个默认的content type典型地与一个文件扩展名联系起来, 比如.rels或者.xml. override content type是用一个不同于默认的与它的文件扩展名关联的content type的方式定义一个具体的part. 比如说, /word/document.xml 与override content type相关联, 而override content type跟默认的content type不同, 默认的content type跟一个.xml扩展名联系在一起.
生成你的第一个.docx文件
=================
尽管已经存在一些程序库可以用来读或者写ZIP档案包, 你还是应该选择与.NET Framework 3.0一起发布的作为WindowsBase.dll一部分的新的打包API, 因为这里的打包API可以识别Office Open XML文档格式. 比如说, 某个方便的方法可以使得添加一个relationship元素到relationship item中更容易, 使得向content type item中添加一个content type元素更容易. 打包API让事情变得更简单, 因为你永远不需要直接地触及relationship item 或者content type item.
对于开发WSS3.0, 这里的打包API有一个优点, 那就是这些API依赖于.NET 3.0 Framework. 你可以肯定WindowsBase程序集和打包API在任何运行着WSS3.0和MOSS的web服务器上都是可用的.
为了开始在Microsoft Visual Studio 2005工程中应用这些打包API编程, 你需要添加对WindowsBase程序集的引用, 如下图所示.
图表2. 添加一个WindowsBase程序集的引用, 以便开始对新的打包API编程
让我们通过创建一个简单的控制台程序, 让它生成一个Office Open XML文档格式的.docx文件作为开始吧.
组成打包API的类都包含在System.IO.Package命名空间下. 当你在包上工作时, 你同时也要经常地与老的, 熟悉的在System.IO和System.Xml命名空间下的类打交道. 看看下面的代码吧, 它呈现了创建一个新的包的骨架.
using System; using System.IO; using System.IO.Packaging; using System.Xml; namespace HelloDocx { class Program { static void Main() { // (1) create a new package Package package = Package.Open(@"c:\Data\Hello.docx", FileMode.Create, FileAccess.ReadWrite); // (2) WRITE CODE HERE TO CREATE PARTS AND ADD CONTENT // (3) close package package.Close(); } } }
System.IO.Packaging命名空间包含Package类, Package类暴露出了一个叫做Open的共享方法, 该方法可以被用来创建新的pakcage或者打开已经存在的package. 如同其他的许多处理文件IO的类一样, 对Open方法的调用永远都应该有一个Close方法来配对作为结束.
一旦您创建了新的包, 下一步就是定位一个或多个parts然后将内容序列化到这些parts中. 在我们的下一个例子中, 我们顺着官方的"hello world"应用程序来操作, 官方的hello world程序还需要创建一个单个的叫做/word/document.xml的part. 你可以通过调用一个打开的Package对象的CreatPart方法创建一个part, 传递的参数是一个URI和一个基于字符串的content type.
// create main document part (document.xml) ... Uri uri = new Uri("/word/document.xml", UriKind.Relative); string partContentType; partContentType = "application/vnd.openxmlformats" + "-officedocument.wordprocessingml.document.main+xml"; PackagePart part = package.CreatePart(uri, partContentType); // get stream for document.xml StreamWriter streamPart; streamPart = new StreamWriter(part.GetStream(FileMode.Create, FileAccess.Write));
对于CreatePart方法的调用, 我们传递了一个URI, 这个URI基于路径/word/document.xml, 还传递了一个content type, 这个传递的content type是Office Open XML文件格式里包含主要内容的文档处理部分所需要的. 一旦你创建了一个part, 你就必须序列化你的内容到part中, 方式是通过标准的基于流的编程技术来完成序列化的工作. 前面的代码通过调用GetStream方法打开了一个part上的流, 并且使用这个刘来初始化了一个StreamWriter对象.
The StreamWriter object is used to serialize the “hello world” XML document into document.xml. However, it’s important that you understand what the resulting XML is going to look like. Examine the following XML that represents the simplest of XML documents that can be serialized into document.xml.
StreamWriter对象被用来序列化"Hello world" XML文档到document.xml中 不管怎样, 你对于结果XML看起来什么样子的了解还是很重要的. 查看一下接下来的XML吧, 它代表了可以被序列化到document.xml的最简单的XML文档.
<?xml version="1.0" encoding="utf-8"?> <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> <w:body> <w:p> <w:r> <w:t>Hello Open XML</w:t> </w:r> </w:p> </w:body> </w:document>
注意, 这个XML文档中所有的元素都是被定义在http://schemas.openxmlformats.org/wordprocessingml/2006/main命名空间下的, 这也是Office Open XML文档格式所要求的. 这个XML文档包含了高层次的文档元素, 文档元素内部是一个body元素, 这个body元素包含了Word文档本身的主要内容(main story).
在body元素之中, 针对每一个段落都有一个<p>元素. 在<p>元素里是一个定义run的<r>元素. run是一个元素的region, 这个region分享相同的特性集. 在run之中, 是一个<t>元素, 它定义了一个范围的文本.
It is now time to generate this XML document with code by using the XmlWriter class from the System.Xml namespace. Examine how the following code creates these elements within the proper structure and by using the appropriate namespace.
现在是通过使用System.Xml命名空间中的XmlWriter类来生成这个XML文档的时候了. 看看下面的代码吧, 它在合适的结构下创建了这些元素, 并且使用了合适的命名空间.
// define string variable for Open XML namespace for nsWP: string nsWP = "http://schemas.openxmlformats.org" + "/wordprocessingml/2006/main"; // write elements into XML document... XmlWriter writer = XmlWriter.Create(streamPart); writer.WriteStartDocument(); writer.WriteStartElement("w", "document", nsWP); writer.WriteStartElement("body", nsWP); writer.WriteStartElement("p", nsWP); writer.WriteStartElement("r", nsWP); writer.WriteStartElement("t", nsWP); // write hello world text into Word Text element writer.WriteValue("Hello Open XML"); // close all elements writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); // close XmlWriter object writer.Close();
我们完成了写XML内容到document.xml的过程. 最后的一步是创建包(package)与document.xml之间的relationship了. 我们可以通过调用Package对象的CreateRelationship方法来创建Relationship. 只要你知道relationship类型的正确的字符串值, 并且能为创建的relationship起一个独一无二的名字, 剩下的就是一个简单的过程了.
// create the relationship part string relationshipType; relationshipType = "http://schemas.openxmlformats.org" + "/officeDocument/2006/relationships/officeDocument"; package.CreateRelationship(uri, TargetMode.Internal, relationshipType, "rId1"); package.Flush();
你可以观察到在调用了CreateRelationship之后, 对于Flush方法的调用. 这个调用强制打包API使用合适的relationship元素去更新package relationship item. 最后调用Package对象的Close方法来完成package的序列化, 并且释放Hello.docx的文件句柄.
到这里, 你已经看到了在一个控制台程序中生成一个简单.docx文件的所有必要步骤了.
处于完整性和读者方便的考虑, 完整列出整个代码如下, 您可以在一个控制台程序中实验一下.
using System; using System.IO; using System.IO.Packaging; using System.Xml; namespace HelloDocx { class Program { static void Main() { Package package = Package.Open(@"c:\Data\Hello.docx", FileMode.Create, FileAccess.ReadWrite); // create main document part (document.xml) ... Uri uri = new Uri("/word/document.xml", UriKind.Relative); string partContentType; partContentType = "application/vnd.openxmlformats" + "-officedocument.wordprocessingml.document.main+xml"; PackagePart part = package.CreatePart(uri, partContentType); // get stream for document.xml StreamWriter streamPart; streamPart = new StreamWriter(part.GetStream(FileMode.Create, FileAccess.Write)); // define string variable for Open XML namespace for nsWP: string nsWP = "http://schemas.openxmlformats.org" + "/wordprocessingml/2006/main"; // create the start part, set up the nested structure ... XmlWriter writer = XmlWriter.Create(streamPart); writer.WriteStartDocument(); writer.WriteStartElement("w", "document", nsWP); writer.WriteStartElement("body", nsWP); writer.WriteStartElement("p", nsWP); writer.WriteStartElement("r", nsWP); writer.WriteStartElement("t", nsWP); writer.WriteValue("My First DOCX File"); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndDocument(); writer.Close(); streamPart.Close(); package.Flush(); // create the relationship part string relationshipType; relationshipType = "http://schemas.openxmlformats.org" + "/officeDocument/2006/relationships/officeDocument"; package.CreateRelationship(uri, TargetMode.Internal, relationshipType, "rId1"); package.Flush(); // close package package.Close(); } } }