代码下载位置: BasicInstincts2006_11.exe (170 KB)
Browse the Code Online
目录
直到现在,编写和部署服务器端的能够读取、修改和生成 Microsoft® Office 应用程序所使用的文档的应用程序仍然是个挑战。Microsoft Word、Excel® 和 PowerPoint® 所用的较早的二进制格式是在 1997 年开始使用的,直到在 Office 2003 版中仍然将其作为默认文件格式。然而,这种二进制的文件格式已被证明因过于棘手而不便使用。大多数读写 Office 文档的生产应用程序都通过承载 Office 应用程序的对象模型来达到此目的。
使用应用程序(如 Word 或 Excel)的对象模型的应用程序和组件,在桌面机上的运行状况远远好于在服务器端环境中。任何曾花费时间编写额外的基础代码来让 Office 桌面应用程序在服务器上可靠运行的人都会告诉您,那绝对不是理想的解决方案。这是因为,Word 和 Excel 之类的 Office 桌面应用程序的设计初衷从来就不是使其在服务器上运行,每当遇到需要人工干预的频繁的对话时,就需要用一个自定义实用程序来终止并重新启动它们。
对于服务器端而言,无需采用承载 Office 应用程序的对象模型的方法即可读写 Office 文档的能力已变为极需要的趋势。Office 2000 和 Office 2003 都引入了一些模型化的功能,可使用 XML 来创建 Excel 工作簿和 Word 文档。这一先进特性使得用 XML 库来编写 Office 文档的某些部分成为可能,比如使用通过 System.Xml 命名空间提供的 Microsoft .NET Framework 中包含的 XML 库。
在 2007 Microsoft Office 系统中,Microsoft 进一步贯彻了这一理念,将 Office Open XML 文件格式用于 Microsoft Office Word、Excel 和 PowerPoint 2007 的文档。Office Open XML 文件格式受到了 ASP.NET 和 SharePoint® 开发人员的热烈欢迎,因为它们能够做到不需要系统在服务器上运行 Office 桌面应用程序,就能在服务器上读取、编写和生成 Word 文档、Excel 工作簿或 PowerPoint 演示文稿。
Office Open XML 文件格式正逐渐成为欧洲计算机制造商协会 (ECMA) 发布的标准。您可以在
openxmldeveloper.com 了解到不断发展的标准化过程的详细信息,下载 Office Open XML 文件格式规范的最新草案和查找其他重要的在线资源。本月专栏的目标在于介绍可使得从 ASP.NET 应用程序中创建简单的 Word 文档并将文档发回给用户及能够让用户使用 Word 2007 直接打开这些文档所需的编程技术。
Word 2007 文档内部结构
首先先观察基于 Office Open XML 文件格式的简单 Word 文档的结构。如您所见,Office Open XML 文件格式基于标准 ZIP 文件技术。每个顶层文件都保存为 ZIP 存档。这意味着您能像对其他任何 ZIP 文件那样,使用 Windows® 资源管理器中内置的 ZIP 文件支持功能打开 Word 文档并查看其内容。
请注意,Word 和 Excel 等 2007 Microsoft Office 系统应用程序已经引入了采用新格式的文件的扩展名。例如,.docx 扩展名用于以 Office Open XML 文件格式存储的 Word 文档,而更熟悉的 .doc 扩展名继续用于以二进制格式存储的 Word 文档。
为理解我所讲的内容,请在 Word 2007 中创建一个新文档,然后添加“Hello Word”作为正文。使用默认的格式将该文档保存为新文档并命名为 Hello.docx ,然后关闭 Word。接着,在 Windows 资源管理器中找到 Hello.docx,将其更名为 Hello.zip。打开 Hello.zip 并查看 Word 在内部创建的文件夹和文件的结构(参见图 1)。
图 1 DOCX 文档是一个 ZIP 存档 (单击该图像获得较大视图)
顶层文件 (Hello.docx) 被称为一个包。由于包是以标准 ZIP 存档实现的,所以它能自动实现压缩,并能在 Windows 平台和其他类似平台上用现有的许多实用程序和 API 即时使用其内容。
每个包内部有两种内部组件:部件和项。一般来讲,部件包含内容,项包含描述部件的元数据。项可以进一步再分为关系项和内容类型的项。一个部件就是一个内部组件,它包含包内始终存在的内容。大多数部件都是简单的文本文件,它们被序列化为具有相关 XML 架构的 XML。不过,如果需要,例如当 Word 文档包含图形图像或媒体文件时,也可以将部件序列化为二进制数据。
部件使用统一资源标识符 (URI) 来命名,URI 包含该部件在包文件内的相对路径,以及部件文件名。例如,一个 Word 文档包内的主要部件是 /word/document.xml。以下是一些在简单 Word 文档包内能找到的几个比较典型的部件名称:
- /[Content_Types].xml
- /_rels/.rels
- /docProps/app.xml
- /docProps/core.xml
- /word/_rels/document.xml.rels
- /word/document.xml
- /word/fontTable.xml
- /word/settings.xml
- /word/styles.xml
- /word/theme/theme1.xml
Office Open XML 文件格式使用关系来定义源和目标部件之间的关联。一个顶层包和一个部件之间的关联构成了一个包关系。一个父部件和一个子部件之间的关联构成了一个部件关系。由于关系能够充分体现这些关联而无需检查特定部件中的内容,因而关系很重要。这意味着关系与特定内容的架构无关,因此能够更快地进行解析。此外还有一个优点,即在两个部件之间建立关系时,不需要修改其中任何一个部件。
关系是在称为关系项的内部组件中被定义的。虽然关系项并不被真正视为一个部件,但仍然与部件一样存储在包内。为保持一致,关系项始终创建在名为 _rels 的文件夹中。
例如,一个包中仅包含一个名为 /_rels/.rels 的包关系项。包关系项包含用于定义包的关系的 XML 元素,如,定义一个 .docx 文件的顶层包和内部部件 /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(关系)元素用于定义名称、类型和目标部件。您还会注意到,关系的类型名称是用创建 XML 命名空间所采用的规范来定义的。
除单个的包关系项外,包中还可以包含一个或多个部件关系项。例如,您可以在位于 URI /word/_rels/document.xml.rels 的包关系项中为 /word/document.xml 和子部件定义关系。注意,部件关系项中的关系的 Target 属性是一个相对于父部件而不是相对于顶层包的 URI。
包内的每个部件都按照特定的内容类型进行定义。内容类型是定义部件的媒体名称、子类型和一组可选参数的元数据。包内使用的任何内容类型都必须在称为内容类型项的组件内显式定义。每个包内都仅有一个名为 /[Content_Types].xml 的内容类型项。在 /[Content_Types].xml 内定义的内容类型定义的示例如图 2 所示。您会发现,在该图中,关系项都是相似的部件,这是因为它们也是用一个关联的内容类型定义的。
Figure 2 Content Types Define Package
<?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>
内容类型供包的使用者用来解释如何读取和转换包的部件内的内容。正如您在图 2 中所看到的,默认设定的内容类型通常关联着一个文件扩展名,如 .rels 或 .xml。当按照与文件扩展名关联的默认内容类型不同的内容类型定义特定部件时,需要重写内容类型。例如,图 2 显示了 /word/document.xml 如何与扩展名为 .xml 的文件所使用的默认内容类型之外的内容类型相关联。
生成您的第一个 DOCX 文件
由于打包的 API 与 Office Open XML 文件格式兼容,所以,尽管您可以使用现有的一些库来读写 ZIP 文件,但您应尽量使用新的打包的 API,它属于 .NET Framework 3.0 附带的 WindowsBase.dll 程序集的一部分。例如,有便利的方法可轻松地将关系元素添加到关系项,也可将内容类型元素添加到内容类型项。由于无需直接操作这些元素和项,所以使用打包的 API 能够简化操作。
这个月的代码使用了随 WinFX® Runtime Components 二月社区技术预览 (CTP) 一起安装的 WindowsBase.dll 版本。请注意,在六月发布的 CTP 中,WinFX Runtime Components 已更名为 .NET Framework 3.0。您可以使用这两个版本中的任何一个,也可以使用更新的 .NET Framework 3.0 版本。为了测试我们将在本专栏中所写的代码,您还需要安装 Word 2007 Beta 2 或更新版本。(变更为 Beta 2 之后的 Office 版本可能要求对这里所写的代码进行一些修改。)
.NET Framework 3.0 包含 WindowsBase.dll,当您安装了 .NET Framework 3.0 后,只要添加一个引用(如图 3 所示),您就可以开始在 Visual Studio® 2005 项目中对打包的 API 编程。
图 3 添加引用至 WindowsBase.dll (单击该图像获得较大视图)
组成打包的 API 的类包含在命名空间 System.IO.Package 中。当使用包时,您还可以经常利用 System.IO 命名空间和 System.Xml 命名空间中较早和熟悉(希望如此)的类编写程序。观察图 4 中的代码,这些代码显示了创建新包的框架。
Figure 4 Creating a New Package
Imports System
Imports System.IO
Imports System.IO.Packaging
Imports System.Xml
Class GenerateDOCX
Shared Sub Main()
‘*** Create a new package
Dim pack As Package = Package.Open("c:\Data\Hello.docx", _
FileMode.Create, _
FileAccess.ReadWrite)
‘*** Write code here to create parts and add content
‘*** Close package
pack.Close()
End Sub
End Class
System.IO.Packaging 命名空间包含 Package 类,它提供了一个名为 Open 的共享方法,可用于创建新包和打开现有包。与其他需要处理文件 I/O 的类一样,调用 Open 后始终都要调用 Close。
创建了新包后,接下来就要创建一个或多个部件并将内容序列化到其中。在本月专栏的这个示例中,我们将遵循“Hello World”应用程序的指导原则,按要求创建一个名为 \word\document.xml 的部件。创建部件的方法为,调用打开的 Package 对象的 CreatePart 方法,然后传递一个 URI 参数和一个基于字符串的内容类型:
'*** Create main document part (document.xml) ...
Dim uri As Uri = New Uri("/word/document.xml", UriKind.Relative)
Dim partContentType As String = "application/vnd.openxmlformats" & _
"-officedocument.wordprocessingml.document.main+xml"
Dim part As PackagePart = pack.CreatePart(uri, partContentType)
'*** Get stream for document.xml
Dim streamPart As New StreamWriter(part.GetStream(FileMode.Create, _
FileAccess.Write))
调用 CreatePart 将传递一个基于 path /word/document.xml 的 URI 和内容类型,该类型是 Office Open XML 文件格式所需的,用作文字处理文档中包含主要内容的那部分。创建了部件后,我们必须使用标准的基于流的程序设计技术来将内容序列化到其中。您刚才看到的代码会在部件上打开一个流,方法是调用 GetStream 方法,然后使用得到的 Stream 对象来初始化 StreamWriter 对象。
StreamWriter 对象将被用来将“Hello World”XML 文档序列化到 document.xml 中。请看下面的 XML;它表示可序列化到 document.xml 中的最简单的 XML 文档:
<?xml version="1.0" encoding="utf-8"?>
<w:document xmlns:w=
"http://schemas.openxmlformats.org/wordprocessingml/2006/3/main">
<w:body>
<w:p>
<w:r>
<w:t>Hello Word 2007</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
请注意,这个 XML 文档中的所有元素都是在 Office Open XML 文件格式所要求的 schemas.openxmlformats.org/wordprocessingml/2006/3/main 命名空间中定义的。这个 XML 文档包含一个高级文档元素。在这个文档元素中,有一个包含主要内容的主体元素。
在这个主体元素内,每个段落会有一个 <p> 元素。<p> 元素内有一个 <r>,它定义了一个运行区。运行区就是具有同样的一组特征的元素的区域。在运行区内有一个 <t> 元素,用于定义一段文本,在本例中为“Hello Word 2007”。
现在,我们要通过一段代码,使用 System.Xml 命名空间中的 XmlDocument 类来生成这个 XML 文档。如果您注意观察一下图 5,您就会发现这些代码如何在正确的结构内使用相应的命名空间来创建这些元素。您还会注意到,这些代码如何使用 StreamWriter 对象将 XML 文档写入流,然后关闭流,再将这些序列化内容注入到 Package 对象中。
Figure 5 Generating the XML Document
‘*** Define string variable for Open XML namespace for nsWP:
Dim nsWP As String = "http://schemas.openxmlformats.org" & _
"/wordprocessingml/2006/3/main"
‘*** Create the start part, set up the nested structure ...
Dim xmlPart As XmlDocument = New XmlDocument()
Dim tagDocument As XmlElement
tagDocument = xmlPart.CreateElement("w:document", nsWP)
xmlPart.AppendChild(tagDocument)
Dim tagBody As XmlElement
tagBody = xmlPart.CreateElement("w:body", nsWP)
tagDocument.AppendChild(tagBody)
Dim tagParagraph As XmlElement
tagParagraph = xmlPart.CreateElement("w:p", nsWP)
tagBody.AppendChild(tagParagraph)
Dim tagRun As XmlElement
tagRun = xmlPart.CreateElement("w:r", nsWP)
tagParagraph.AppendChild(tagRun)
Dim tagText As XmlElement
tagText = xmlPart.CreateElement("w:t", nsWP)
tagRun.AppendChild(tagText)
‘*** Insert text into part as a Text node
Dim nodeText As XmlNode
nodeText = xmlPart.CreateNode(XmlNodeType.Text, "w:t", nsWP)
nodeText.Value = "Hello Word 2007"
tagText.AppendChild(nodeText)
‘*** Write XML to part and close stream
xmlPart.Save(streamPart)
‘*** Close stream and flush XML content into package
streamPart.Close()
pack.Flush()
现在我们就要完成将 XML 内容写入 document.xml 的操作了。最后一步是通过调用 Package 对象的 CreateRelationship 方法在包和 document.xml 之间建立关系。只要您知道该关系类型的确切的字符串值,就可以很容易地完成该操作。您可以为要创建的关系指定唯一的名称(如 rId1):
'*** Create the relationship part
Dim relationshipType As String = _
"http://schemas.openxmlformats.org" & _
"/officeDocument/2006/relationships/officeDocument"
pack.CreateRelationship(uri, TargetMode.Internal, _
relationshipType, "rId1")
pack.Flush()
'*** Close package
pack.Close()
调用 CreateRelationship 后,您应按规定接着调用 Flush。这样将强制打包的 API 用正确的 Relationship 元素更新包关系项。最后调用 Package 对象的 Close 方法完成包的序列化并释放 Hello.docx 的文件句柄。
至此,您已经了解了利用 Visual Basic
® 2005 编写的控制台应用程序来生成一个简单 .docx 文件的所有步骤,所有这些内容都可从
此处获得。现在让我们编写代码,以便通过 ASP.NET 应用程序在服务器端生成 .docx 文件。
在服务器上生成 DOCX 文件
要修改控制台应用程序的代码以使其在 Web 服务器上运行,首先要考虑的是,您可能希望避免必须将包保存为宿主计算机文件系统中的物理文件。要知道,仅仅在 ASP.NET 工作进程的内存流中创建包的速度会更快。然后,您可以使用 ASP.NET Response 对象里的 OutputStream 对象将其写回到客户端。
首先我们将代码更改为使用 MemoryStream 对象而不是使用实质的文件。观察图 6 中的代码,它们已经被添加到 ASP.NET 2.0 页面中的服务器端事件处理程序中。请注意,Package.Open 的第一个参数已由基于字符串的文件路径更改为 MemoryStream 对象。这种方法允许您再次使用同样的代码,像我们前面的操作那样创建包及其部件。不过,您无需担心如何创建和命名 OS 层文件。在高流量环境下,这种方法还具有更快的响应时间和更出色的吞吐量。
Figure 6 Using a MemoryStream Object
‘*** Create in-memory stream as buffer
Dim bufferStream As New MemoryStream()
‘*** Create new package in memory stream
Dim pack As Package = Package.Open( _
bufferStream, FileMode.Create, FileAccess.ReadWrite)
‘*** This calls same code shown in Hello World example
WriteContentToPackage(pack)
‘*** Save/close package object leaving DOCX file in MemoryStream
pack.Close()
‘*** (1) SET UP HTTP HEADERS FOR RESPONSE
‘*** (2) WRITE PACKAGE CONTENT INTO RESPONSE BODY
您刚才看到的代码将创建一个 MemoryStream 对象,然后将一个 .docx 文件序列化到其中,操作与将 .docx 文件序列化到物理文件中一样。自定义的 WriteContentToPackage 方法中的代码直接从上文中的控制台应用程序代码中获得。但是,它此时要创建一个包并将其序列化到内存中的一个缓冲区而不是一个物理文件中。
当您将包写入 MemoryStream 对象并对包对象调用 Close 后,即已完成对打包的 API 的操作。剩下要做的事情就是设置正确的 HTTP 标头,然后将包内容写入将发回到客户端的响应的正文中。
首先我们来设置 HTTP 标头。您应对 ASP.NET 响应对象调用各种方法来清除现有的任何标头,然后添加一个内容放置标头来指定该响应包含一个文件名为 Hello.docx 的附件:
Response.ClearHeaders()
Response.AddHeader("content-disposition", _
"attachment; filename=Hello.docx")
接下来,您必须设置 HTTP 响应的编码和 MIME 内容类型,然后将包的二进制内容写入 HTTP 响应的正文中。使用图 7 中的代码即可实现此目的。请注意,最后必须调用 Response.Flush 和 Response.Close 才能确保整个包被完整写入 HTTP 响应。
Figure 7 Response.ClearContent
Response.ClearContent()
Response.ContentEncoding = System.Text.Encoding.UTF8
Response.ContentType = "application/vnd.ms-word.document.12"
‘*** Write package to response stream
bufferStream.Position = 0
Dim writer As New BinaryWriter(Response.OutputStream)
Dim reader As New Bina NO RESPONSE YET | ryReader(bufferStream)
writer.Write(reader.ReadBytes(bufferStream.Length))
reader.Close()
writer.Close()
bufferStream.Close()
‘*** flush and close the response object
Response.Flush()
Response.Close()
只要客户机经过配置,能够理解 .docx 文件关联的 MIME 内容类型,系统就会为用户提供以下常规选项:在 Word 中打开文档或将其保存到本地硬盘。
如果用户单击“打开”按钮,则该 .docx 文件将自动在 Word 中打开,如图 8 所示。请注意,直到此时,一直都未将这个包保存为文件系统的物理文件,这一点很有趣。而只是将它存储在了 Web 服务器和客户端桌面机的内存中。如果用户没有保存就关闭文档,文档即会消失,仿佛从来就没有存在过。鉴于这个特点,该方法成为了为信函、备忘录、客户列表以及任何您能够想到的文档类型创建模板的理想之选。
图 8 文档于 Word 2007 里开启 (单击该图像获得较大视图)
http://www.cnblogs.com/Dragonpro/archive/2005/09/01/228144.html