為什麼微軟 Office 的檔案格式如此複雜?(以及一些解決方案) Why are the Microsoft Office file formats so complicated? (And some workarounds)

作者:周思博 (Joel Spolsky)
February 19, 2008
屬於 Joel on Software, https://www.joelonsoftware.com/2008/02/19/why-are-the-microsoft-office-file-formats-so-complicated-and-some-workarounds/

上個禮拜,微軟公開了他們 Office 軟體的二進位檔案格式。這些格式看起來真是太瘋狂了。Excel 97-2003 的檔案格式是一份 349 頁的 PDF 檔。等一下,這還不是全部喔!這份文件中有下面這句耐人尋味的註解:

每一份 Excel 工作簿都被存在一份複合檔案 (compound file) 中。

你看,Excel 97-2003 的檔案其實是 OLE 複合文件;這意味著:基本上,每個檔案中都存在著一份具體而微的檔案系統。你得把另外的九份文件都讀完才能全盤掌握,夠複雜了吧?而且,這些「規格書」的內容看起來比較像是一堆 C 語言的資料結構,而不是我們憑經驗所想像的那種規格書。它是一個完整的階層式檔案系統。

如果你真的開始閱讀這些文件,並且幻想著能利用週末的時間寫些很炫的程式碼(像是把 Word 檔匯入你的部落格中,或是把你的個人帳簿輸出成 Excel 檔案格式),這些又臭又長的規格書可能很快就會讓你心灰意冷。面對這些 Office 檔案格式,一個普通的程式員可能會有以下的結論:

  • 一定是故意搞得這麼複雜,不想讓人看懂的
  • 是一個神經錯亂的柏格人搞出來的玩意兒
  • 當初是由一群瘋狂的程式設計師制定的
  • 絕對不可能讀取、建立這些檔案而完全不出錯

以上四點全是錯的。讓我帶你挖掘一些事實,告訴你為什麼這些檔案格式會複雜到令人難以想像,為什麼它們不是「微軟的程式寫得很爛」的證明,以及你該如何面對這些事實的方法。

首先我們必須要了解的是:這些二進位檔案格式的設計目的和其他的檔案格式(像是 HTML)是完全不同的。

它們是為了能在老電腦上快速處理而設計的。對早期版本的 Windows 版 Excel 來說,記憶體的合理使用量是 1 MB,而且在 80386 20MHz 的電腦上應該要能跑得夠順。這些檔案格式中有許多設計,是為了能更快速開啟、儲存檔案而做的最佳化:

  • 這些檔案是以二進位格式儲存的,因此讀取一筆記錄通常只是單純地「把一個範圍的位元資料從磁碟片上搬(複製)到記憶體中」而已。所以最好的做法就是把檔案格式訂得跟 C 語言的資料結構一樣,這樣在讀取檔案的時候就完全不需要什麼語法分析。通常使用語法分析所花的時間可能會比直接複製多上幾個數量級
  • 當有需要的時候,這些檔案格式會使用一些非正常的手段,讓常用的動作能快些。舉例來說:Excel 95 和 Excel 97 有時會利用到一種叫做「簡單儲存」的功能,可以把檔案快速地存成某種變種的 OLE 複合檔案格式——因為原本的檔案格式在實戰中實在是不夠快。Word 也有種東西叫做「快速儲存」,在儲存的時候只把有變動到的地方附加到檔案末端,而不是重新寫回整個檔案。這麼做可以讓儲存加快 14、15 倍。拿以前的硬碟來說,這意味著原本存一個大檔案得花 13 秒,現在只要不到一秒就解決了。(這也意味著已經被刪除的資料還是會存在檔案之中。最後這成了人們不想要的功能

這些設計是以既存的函式庫為基礎的。如果你打算從頭開始寫一套可以讀取二進位格式的程式的話,你得要能支援 Windows Metafile Format(才能顯示繪圖圖型)和 OLE 複合儲存。如果你是在 Windows 下開發的話,這些功能都有函式庫可以使用,你可以很輕鬆地完成這些功能.... 使用這些功能是微軟團隊的捷徑。但如果你想要獨力完成每一個功能的話,那你就得全部重寫一次了。

Office 大規模地支援複合文件格式。舉例來說,你可以把試算表嵌到一份 Word 檔案裡面。一個合格的 Word 檔案處理程式要能夠很聰明地處理內嵌的試算表。

它們不是為了你的腦袋瓜子設計的。這些格式設計的前題(以那個時間點來說是相當合理的)是:Word 的檔案格式只需要能夠被 Word 讀取、寫入,就夠了。這意味著,每當 Word 開發團隊的程式員要去決定怎麼修改檔案格式的時候,他只需要考慮的事情只有 (a)這樣做夠快嗎? (b)要怎麼做才能在現有的 Word 程式碼中做最小幅度的修改就能達成目的。像是 SGML 和 HTML 那種追求「互換性、標準化」的檔案格式,在那個 Internet 尚未興起、檔案互換性並不高的年代,並不是設計檔案格式時的首要考量——Office 二進位檔案格式的誕生時間可比他們早上十年。在那個時候的假設是:如果你需要做文件互換的話,你可以使用「匯入/匯出」功能。事實上 Word 也真的有一種為了能輕鬆達成互換的檔案格式,稱做 RTF。它從一開始就存在了,而且現在的 Word 還是 100% 支援這種格式。

它們必須反應出應用軟體的複雜度。每一個 checkbox、每一個格式選項,以及每一項微軟 Office 中的功能,都必須反應在檔案格式中的某個角落。那個在 Word 的「段落」選單中,讓一個段落在必要的時候能移動到下一頁、好讓它能和下一段出現在同一頁,叫做「與下段同頁」的 checkbox?這得出現在檔案格式中。而這也就是說,如果你想要實作一個能完美正確地讀取 Word 文件的仿製品,你得要實作這個功能。如果你正在寫一個足以和 Word 競爭、並且可以讀取 Word 文件的文件處理程式,「從檔案格式中讀取這個設定」的程式碼也許只花你不到幾分鐘的時間,但你可能得要再花上幾個禮拜的時間去調整你的頁面規劃演算法,好讓結果看起來和 Word 一樣。否則,你的顧客會用你的程式打開 Word 文件,然後發現所有的頁面都亂七八糟。

它們必須要能反應出應用程式的沿革。這些檔案格式如此錯綜複雜,有很大一部份是為了反應那些老舊的、複雜的、討人厭的,以及很少人用的功能。為了能向前相容,它們依然存在於檔案格式中——反正留著那些程式碼又不會增加微軟什麼負擔。但如果你真的想要完整透徹地解譯、寫入這些檔案格式,你得要重做一些 15 年前微軟的實習生們做過的工作。我們得認清一件事實:最新版的 Word 和 Excel 是數千個開發人年的成果。如果你真的想要完完全全地複製這些應用程式的功能,你一樣得花數千人年的工夫。檔案格式只是簡明扼要地列舉了應用程式所支援的所有功能。

來點有趣的吧!讓我們深入一點看個小例子。Excel 的工作表是由一堆不同型別的 BIFF(譯注:Binary Interchange File Format)記錄組成的。我想要看規格書中的第一個 BIFF 記錄的定義。它叫做 1904 記錄。

Excel 的檔案格式規格對這個記錄的說明很明顯地含糊不清。它只提到 1904 記錄代表了「是否使用了 1904 日期系統」。唉,又是一個典型的無用規格說明。要是你正在做 Excel 檔案相關的程式開發工作,然後你在規格書中發現這個東西,你可能會斷定微軟又藏了一手。這短短的說明文字沒辦法給你足夠的資訊。你還需要一些額外的知識,我就在這兒說明一下吧。Execl 的工作表可以分為兩種:其中一種的日期記算是以 1900 年 1 月 1 日為紀元(包括一個為了和 1-2-3 相容而存在的閏年錯誤,不過現在去談它沒啥意思),另一種則是以 1904 年 1 月 1 日為紀元。Excel 兩種日期格式都支援,因為第一版的 Excel(Mac 版)直接使用作業系統的紀元系統(這樣比較簡單);但是 Windows 版的 Excel 必須要能匯入使用 1900 紀元系統的 1-2-3 檔案。這就足夠讓你欲哭無淚了。無論是過去還是現在,沒有程式員不想依正道而行的;但有時候,你就是得屈服於現實。

不管是 1900 還是 1904 的檔案都很常見,通常是端看那個檔案是在 Windows 還是 Mac 上產生出來的。如果我們在使用者不知情的情況下自動轉換格式,那麼資料很可能會被損毀。因此,Excel 不會自動幫你轉換。這不只是個把一個 bit 從檔案中讀取出來的問題。這意味著你得把你的日期顯示、處理程式碼整個翻修一遍,好同時支援這兩種紀元系統。我想,這大概得花掉你好幾天的時間去實作吧。

沒錯,當你在寫 Excel 仿製品的時候,你會發現各式各樣處理日期的微妙細節。Excel 在什麼情況下會把數字轉換成日期?這種格式化是怎麼做的?為什麼 1/31 會被解譯成「1 月 31 日」,而 1/50 又會被解譯成「1950 年 1 月 1 日」?這些微妙的動作沒辦法在文件上三言兩語講清楚;真的要詳細地寫成文件的話,那文件的內容大概就跟 Excel 的原始碼沒兩樣了。

別忘了這只是幾百個 BIFF 記錄中的第一個而已,而且還是最簡單的一個。大部份的記錄都是複雜到會讓合格的程式員崩潰的程度。

唯一可能的結論是:微軟釋出他們的檔案格式對大家是很有幫助的,但這並不代表匯入或是儲存 Office 檔案就會變得比以前簡單。這些應用軟體的功能極為眾多而複雜。你也沒辦法只實作出 20% 的功能,然後預期 80% 的使用者會滿意。這些二進位檔案的規格書,頂多只會幫你省下對這套複雜的系統做逆向工程的幾分鐘罷了。

好吧!我答應要給你一些可行方案。好消息是:在絕大部份的一般情況下,想要去讀些 Office 的二進位檔案都是錯誤的選擇。你可以認真地考慮採行另外的兩種可行方案:讓 Office 自己處理這些問題,或是使用比較容易讀寫的檔案格式。

讓 Office 幫你處理這些繁複的工作。透過 COM Automation 機制,Word 和 Excel 提供了極為完整的物件模型。這個物件模型可以讓你用寫程式的方式完成任何事情。在許多情況下,你應該要重覆利用 Office 內部的功能,而不是試著重新實作一次。以下是一些範例。

  1. 你有一個需要把現有的 Word 檔案輸出成 PDF 格式的網頁介面應用程式。如果是我的話,我會這麼做:寫幾行的 Word VBA 程式,讀取一個 Word 檔案,再利用 Word 2007 內建的 PDF 轉出功能把文件存成 PDF 格式。你可以直接執行這幾行程式——即使是從在 IIS 系統下執行的 ASP 或是 ASP.NET 程式中呼叫也沒問題。它做得到的。第一次把 Word 喚起可能會花上幾秒鐘的時間。然後 COM 子系統會把 Word 留在記憶體中幾分鐘,以便你再需要它。所以,第二次執行會快得多。這樣的速度對於一個網頁介面應用程式來說是夠快的了。
  2. 需求和前一點相同,但網頁伺服器得用 Linux。買一台 Windows 2003 伺服器,安裝一套完全符合授權的 Word,然後寫個小的網頁服務程式做這件事。使用 C# 和 ASP.NET,大約是半天的工作量吧。
  3. 需求和前面相同,但規模非常大。丟一台負載分配伺服器在一堆你在第 2 點中建立起來的伺服器前面。一行程式碼也不用寫。

這種方法適用於所有你可能伺服器上處理的一般 Office 應用。舉例來說:

  • 打開一份 Excel 工作簿,排序一些輸入格中的資料,重新計算,然後把一些結果放在輸出格中。
  • 使用 Excel 產生 GIF 格式的圖表。
  • 不需花時間去思考檔案格式,把各式各樣的資料從任意種類的 Excel 工作表中捉出來。
  • 把 Excel 格式的檔案轉成 CSV 表格資料(另一種做法是透過 Excel ODBC 驅動程式,利用 SQL 查詢把資料吸出來)。
  • 編輯 Word 文件。
  • 填寫 Word 格式的表單。
  • 把檔案在 Office 支援的各種格式間互轉(你可以找到一大票文字處理程式和試算表檔案的匯入器)。

在所有的這些情況裡,你都有辦法讓 Office 物件在非互動模式執行,因此它們不會出現在螢幕上,也不會要求使用者輸入任何東西。對了,如果你想用這個方法的話,要注意有一些容易發生問題的地方,而且微軟對此並沒有正式支援,所以在動手之前請先閱讀他們提供的這篇 knowledge base 文件

使用比較簡單寫入的檔案格式。如果你只是想要利用程式產生 Office 文件,你幾乎一定能找到比 Office 二位進檔更好的格式可用,而且可以讓 Word 和 Excel 順利開啟,沒有任何問題。

  • 如果你只是想要產生給 Excel 使用的表格資料,請考慮使用 CSV。
  • 如果你真的需要 CSV 不支援的試算表計算功能,WK1 格式(Lotus 1-2-3 的檔案格式)比 Excel 簡單太多了,而且 Excel 也能無誤地打開它。
  • 如果你真的、真的必須產生 Excel 原生檔案,請試試古早版本的 Excel... Excel 3.0 是個不錯的選擇。沒有什麼複合文件的鬼東西,而且只儲存你想用的最少功能。使用這樣的檔案格式,可以將你所需要輸出的 BIFF 記錄降到最低,然後你只要專心研究規格書的這些部份就好。
  • 至於 Word 文件,請考慮使用 HTML。Word 也能無誤地開啟。
  • 如果你真的希望在產生出來的 Word 文件裡使用很炫的格式,你最好的賭注是產生 RTF 文件。Word 能做到的所有事情,都能用 RTF 表現出來。但 RTF 是以文字格式儲存的,不是二進位格式,所以你可以在 RTF 文件中做些變動,然後 Word 依然讀得出來。你可以用 Word 產生一份格式很漂亮的文件,在將來要修改的地方放些占位文字,存成 RTF 格式,然後再利用簡單的文字替代,即時地把那些占位文字換成想要的文字。這樣一來,你就有一份無論哪個版本的 Word 都可以順利開啟的 RTF 檔案了。

反正,除非你打算要寫一套和 Office 競爭的軟體,並且得要能完美地開啟 Office 的檔案,否則你大可把這幾千個人年的工夫省下來。不管你打算解決什麼樣的問題,直接去讀寫 Office 的二進位檔案是最浪費人力的一種方法。

這些網頁的內容為表達個人意見。
All contents Copyright © 1999-2006 by Joel Spolsky. All Rights Reserved.