使用 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,將樣式表中的 Unicode0xFFFD, 0xFFFE,以及0xFFFF對映到這些未轉義的字元。
我還在 docbook-apps 郵件列表中找到了一個存檔帖子,該帖子對於將 DocBook 下載元件的內容對齊為可用的層次結構非常有幫助。
DocBook 的 Python API 示例
為了支援使用 DocBook 開發必要的內容插入器,我需要一個 Python 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 作為命令列應用程式執行,因此可以使用Popen4Python 標準庫呼叫,輕鬆地從 Python 程式碼中控制。
使用 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%)至少需要兩天。
這浪費了大量的工程勞動力,而這些勞動力本可以更好地用於提高我部門軟體產品的質量。
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 一起使用,即使僅從避免的文件成本來看,也已在中期獲得了可觀的投資回報,從而證明了這一決定的正確性。
