使用 Python 避免文件成本
介紹
本文展示了我如何將 Python、COM、DocBook、OpenJade 和 Word 整合在一起,為 BEACON(一個視覺化程式設計環境)建立一個文件工具。這個文件工具用於我公司軟體開發方法中的程式碼審查,並帶來了顯著的(超過 100 萬美元)成本節約。
在開始這個專案之前,我沒有任何使用 SGML、XML 或其他文件標記語言的經驗。只有當我透過改編 Mark Hammond 的書《Win32 上的 Python 程式設計》中的直接 PythonCOM 到 Word 介面,幾乎要重新發明標記語言的概念時,我才意識到從設計和維護的角度來看,應該有更好的方法來做這件事。
一次網路搜尋為我提供了關於 XML、HTML 和 SGML 等標記語言的速成課程。我的搜尋還讓我深入瞭解了 DocBook SGML(一種流行的開放標準)和 OpenJade(一個可以將 DocBook SGML 轉換為 Word 富文字的開源軟體包)。
這種安排並不完美,但我很快意識到,我可以用它來節省一年的開發和維護成本,並更快地響應新的任務。
核心資料流
我的主要任務是將從我組織中分散的各種來源挖掘的任意資料轉換為看起來合理的 Microsoft Word 97 報告。
我決定最好透過一個核心應用程式管道來處理,這些應用程式使用通用的資料約定相互協作。這個管道將由一個 Python 生成器應用程式控制,該應用程式將驅動一組前端轉換器、一個內容插入器和一個後處理格式化程式來生成報告。Python 最近被選為我們部門用於自動化測試指令碼的後繼語言。我注意到了 Python 在測試之外的潛力,所以我獲得了我的經理的許可,將其用於此任務。
此應用程式中的前端轉換器從各種資料來源收集內容(圖片、表格、段落)並將其放入字典中。內容插入器建立一個 Word 文件並將字典中的值插入其中。後處理格式化程式獲取結果並根據最新的公司 Word 格式樣式模板對其進行修改。
此流程旨在應對我們部門內不同團隊和公司級標準的需求變化。例如,報告的佈局由生成器應用程式中的佈局類確定,該佈局類可以用其他類替換以支援新型報告。
我需要建立的第一個前端轉換器是從一個名為 BEACON 的航空航天行業軟體視覺化程式設計工具構建的遞迴屬性列表中獲取圖片、表格和資料。透過這個轉換器,我能夠獲得大量的樣本資料,適合測試 Word 內容插入器。
插入器的問題
第一個版本的內容插入器基於《Win32 上的 Python 程式設計》中演示的原則,其中詳細描述了 Python 如何使用 Word 97 COM 物件模型建立和操作 Word 文件。此實現透過直接透過 COM 介面驅動 Word 將內容插入到 Word 文件中。
不幸的是,COM 介面太慢,無法處理從 BEACON 原始碼中提取的大量表格單元格。
更糟糕的是,我為處理不同部分、標題、段落和短語的不同樣式要求而編寫的類的重用問題。
為了解決這些問題,我考慮編寫 ASCII 文字檔案來為標準表單指定邊距、字型、標題級別和插入點。但是,發明我自己的文字指定排版標準將耗時且成本高昂,無論是在開發還是維護方面。
我真正需要的是一個 Python API,可以快速為有限的一組文件在 Word 97 中生成排版精美的副本,但在 2001 年,沒有可用的東西可以做到這一點。我唯一的選擇是找到一個開放的排版標準,該標準可以在文件生成後轉換為 Word 97 格式。
尋找標準
為了找到解決方案,我花了一些時間調查可用的開源排版解決方案。最流行的兩個是 TeX 和 DocBook,兩者都由用 C 編寫的開源實現支援。
選擇 DocBook 是因為它具有更明確定義和記錄的排版元素生產規則。《DocBook,權威指南》在詳細解釋這些規則方面確實是一個寶藏。
DocBook 提供了一組以 DSSSL(文件樣式語義和規範語言)編寫的文件型別定義 (.DTD) 和文件樣式表 (.DSL) 檔案。DocBook 分為兩種版本,一種用於 SGML,另一種用於 XML 文件編碼。SGML 和 XML 都具有類似的標籤巢狀結構以及 DTD 和 DSL 的相同邏輯結構。但是,XML 規則在 2001 年仍在開發中,因此我選擇了更可靠和成熟的 SGML 規則集。
DocBook 還提供了編寫本地文件型別定義和樣式表的能力,稱為Local.DTD和Local.DSL分別。這些允許在 DocBook 提供的基礎上引入額外的文件元素及其呈現。
例如,我公司的一些團隊希望最終 Word 文件中的功能相當於 SGML 中任意的 Word 欄位程式碼支援。
為了支援這一點,我編寫了一個本地 DocBook 樣式表和定義檔案對(Local.DSL, Local.DTD)以發出 RTF 中對應於所需欄位程式碼的序列。RTF 需要未轉義的字元{, }, 和\\, 所以我修改了 OpenJade,以將樣式表中的 Unicode 對映到這些未轉義的字元。0xFFFD, 0xFFFE, 和0xFFFF在樣式表中對映到這些未轉義的字元。
我還找到了 docbook-apps 郵件列表中的一篇 存檔帖子,這對於將 DocBook 下載元件的內容對齊到可行的層次結構中非常有幫助。
DocBook 的 Python API 示例
為了支援使用 DocBook 開發必要的內容插入器,我需要一個 Python API,該 API 可用於快速生成 SGML 格式的文件。為此 API 選擇的設計為 DocBook Python 模組中的每個文件元素型別提供抽象類。這些抽象類可以在定義特定文件結構的程式碼中繼承,並且可以任意巢狀,以便每個類都對映到輸出文件結構的不同級別或部分。
例如,假設我們想生成以下表格作為 Word 文件的一部分
名稱 型別 statex 整數 statey 長整型
用於此表格的 SGML 文字以Local.DSL和Local.DTD的形式編寫
<!DOCTYPE informaltable SYSTEM "C:\Local.dtd"> <informaltable frame='all'> <tgroup cols='2' colsep='1' rowsep='1' align='center'> <colspec colname='Name' colwidth='75' align='left'></colspec> <colspec colname='Type' colwidth='64' align='center'></colspec> <thead> <row> <entry><emphasis role='bold'>Name</emphasis></entry> <entry><emphasis role='bold'>Type</emphasis></entry> </row> </thead> <tbody> <row> <entry><phrase role='xe' condition='italic'>statex</phrase></entry> <entry>Integer</entry> </row> <row> <entry><phrase role='xe' condition='italic'>statey</phrase></entry> <entry>Long</entry> </row> </tbody> </tgroup> </informaltable>
這是透過基於 DocBook 類樹生成上述 SGML 的 Python 列表
from DocBook import DocBook class ItalicIndexPhrase (DocBook.Rules.Phrase): "italic indexible text phrase" TITLE = DocBook.Rules.Phrase def __init__ (self, text): DocBook.Rules.Phrase.__init__ (self, 'xe', 'italic') self.data = [ text ] class NameCell (DocBook.Rules.Entry): "table row cell describing name of identifier (italic and indexible text!)" TITLE = DocBook.Rules.Entry def __init__ (self, text): DocBook.Rules.Entry.__init__ (self) self.data = [ ItalicIndexPhrase (text) ] class StorageCell (DocBook.Rules.Entry): "table row cell describing storage type of identifier (ordinary text)" TITLE = DocBook.Rules.Entry def __init__ (self, text): DocBook.Rules.Entry.__init__ (self) self.data = text class TRow (DocBook.Rules.Row): "each row in application's informal table body" TITLE = DocBook.Rules.Row def __init__ (self, binding): (identifier, storage) = binding DocBook.Rules.Row.__init__ (self, [ NameCell (identifier), StorageCell (storage) ]) class TBody (DocBook.Rules.TBody): "application's informal table body" TITLE = DocBook.Rules.TBody def __init__ (self, items): DocBook.Rules.TBody.__init__ (self, map (TRow, items)) class TGroup (DocBook.Rules.TGroup): "application's informal table group" COLSPECS = [ DocBook.Rules.ColSpec ('Name', 75, 'left'), DocBook.Rules.ColSpec ('Type', 64, 'center') ] SHAPE = [ '2', '1', '1', 'center' ] TBODY = TBody class InformalTable (DocBook.Rules.InformalTable): "application's informal table" TGROUP = TGroup class Example (DocBook): 'example application of DocBook formatting class' SECTION = str (InformalTable) def __call__ (self): self.data = [ InformalTable ()(self.data) ] return DocBook.__call__ (self) if __name__ == '__main__': print Example ([('statex', 'Integer'), ('statey', 'Long')]) ()
OpenJade 介面
OpenJade 是一個開源產品,它提供了一種從 SGML 編碼文件獲取 Microsoft 富文字格式 (RTF) 的方法。它讀取 DocBook DSSSL 樣式表和使用者的本地 DSSSL 樣式表(如果有)。DSSSL 在使用者的 SGML 源文字上執行,以編寫一個最終文件以載入到使用者的文字處理器中。
對於這個專案,我們希望自動生成 Microsoft Word 可讀的檔案,因此 OpenJade 被設定為發出 Microsoft Word 富文字檔案。OpenJade 作為命令列應用程式執行,因此可以使用 Python 程式碼透過Popen4Python 標準庫呼叫輕鬆控制。
使用 PythonCOM 的 Word 自動化進行後處理
OpenJade 建立的 Microsoft 富文字格式檔案在整體外觀上非常吸引人。但是,它們不符合公司格式化 Word 文件檔案的許多標準。
編寫了一個本地 DSSSL 樣式表 (Local.DSL) 來覆蓋幾個預設的 DocBook DSSSL 設定,並使它們與公司標準保持一致。但是,這並沒有解決在文件中設定標準化的 Word 樣式識別符號名稱的需求。
為了解決這個問題,在文件管道的最後階段需要一個重新格式化程式。它以 COM 物件的形式訪問 Word,以便遍歷生成的 RTF 文件物件模型中各個級別的表格、圖形、標題和節級樣式識別符號。在遍歷過程中,它會重新命名樣式識別符號,以符合我們本地製圖部門作為標準分發的 Microsoft Word 文件模板 (.DOT) 檔案中提供的識別符號。
完成此轉換後,後處理器將完成的文件以 Microsoft Word 文件格式儲存。
所有後處理任務都不是特別困難。一旦充分理解了 Win32 應用程式的 COM 介面,該應用程式就會在 Python 開發人員手中退化為另一個庫。
投資回報
用於推導此處提出的投資回報 (ROI) 數字的假設是保守的。
我花費了 2001 年的大部分時間使用本文中的想法開發了一個系統,以自動將 BEACON 視覺化程式語言檔案中的內容直接轉換為 Word 文件。2002 年,我還對軟體進行了重大修訂。我在開發、維護和支援方面的總努力大約是兩年期間的一半時間。
在 2002 年至 2003 年間,我的部門有 5 個正在進行的專案,這些專案處於不同的開發階段,複雜度各異,從 30 個視覺化程式設計檔案到多達 150 個,平均可能為 75 個。
在這兩年中,對於每個專案,至少有 2 次主要的強制釋出,其中每個檔案的重要內容都必須經過同行評審:由不少於 3 位工程師(主持人、作者和檢查員)同時進行詳細檢查。
每次釋出都需要將每個視覺化程式設計檔案渲染為可檢視的硬複製格式,其中包含其所有圖表;以及一個交叉引用的表格,其中包含每個圖表中的所有識別符號,包括儲存類、範圍、初始值、文件和其他欄位。
視覺化程式語言 GUI 應用程式 BEACON 沒有全面的硬複製生成器。相反,需要一名入門級工程師在適度監督下,透過在 UNIX 上執行 BEACON 並透過 UNIX 到 Win32 X 終端模擬器檢查檔案,手動將文字從 X 終端傳輸到開啟的 Word 文件中。
最不復雜的檔案(約佔 20%)需要半天時間。大部分檔案(60%)平均需要一整天時間。最複雜的檔案(約佔 20%)至少需要 2 天時間。
這浪費了大量的工程勞動力,這些勞動力本可以更好地用於提高我部門的軟體產品質量。
Each project release: 1/5 * 75 * 4 hours = 60 hours 3/5 * 75 * 8 hours = 360 hours 1/5 * 75 * 16 hours = 240 hours ------------- 660 hours Two major releases per year: * 2 = 1 320 hours Five projects needing releases: * 5 = 6 600 hours Two year period (2002-2003) * 2 = 13 200 hours Total effort avoided: 13 200 hours Automated releases over 2 year period: 160 hours My effort (12 * 140 hours per labor month): 1 680 hours Total investment: 1 840 hours Net effort avoided, 2002-3: 11 360 hours Net cost avoided by customers 2002-3 at $100/hour 1 136 000 dollars Net labor years avoided at 1680 hours/year: 6.76 years Head count avoided per year: 3.38 people ROI (Total effort avoided / total invested) 2002-3: 7.17
從上表可以看出,僅為正式釋出給客戶生成文件的自動化所帶來的投資回報,顯然幫助我的部門避免了大量的人工成本。
Python 和 DocBook 結合在一起,被證明是消除現實業務流程瓶頸的強大組合。
結論
我的部門決定採用 Python 並允許我將其與另一個開放標準 DocBook 一起使用,這已被中長期內的鉅額投資回報所證實,即使僅僅在避免的文件成本方面也是如此。