天天看點

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建立的檔案和檔案夾結構. 如下圖:

Office Open XML 文檔格式

圖表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      

.csharpcode, .csharpcode pre

{

font-size: small;

color: black;

font-family: consolas, “Courier New”, courier, monospace;

background-color: #ffffff;

/white-space: pre;/

}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #006080; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt

{

background-color: #f4f4f4;

width: 100%;

margin: 0em;

}

.csharpcode .lnum { color: #606060; }

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程式集的引用, 如下圖所示.

Office Open XML 文檔格式

圖表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();
    }
  }
}      

譯自<inside Microsoft Windows SharePoint Services 3.0>