在撰寫本 developerWorks 系列文章的過程中,我曾遇到過以最佳格式進行撰寫的問題。文字處理程序格式都是專用的,在格式之間轉(zhuǎn)換總不能盡如人意,也很麻煩(而且每種格式都會各自將文檔綁定到不同的專用工具,這種情況又與開放源碼的精神相違背)。HTML 還算中立 -- 也許您現(xiàn)在閱讀的文章正是這種格式 -- 但它也添加了標記,而這些標記很容易引起誤輸入(或者使人束縛于 HTML 增強型編輯器)。DocBook 是一種有趣的 XML 格式,它可以轉(zhuǎn)換成許多目標格式,并且它擁有技術(shù)文章(或書籍)的正確語義;但就像 HTML 一樣,在撰寫過程中會擔心許多標記。LaTeX 特別適合復雜的印刷格式;但它也有許多標記,而這些文章并不需要復雜的印刷格式。
為了在寫作時能真正省心 -- 特別是要具有平臺和工具的中立性 -- 無格式 ASCII 正是最好的選擇。但是,因特網(wǎng)(特別是 Usenet)建議在完全無格式文本的基礎(chǔ)上,開發(fā)一種“智能 ASCII”文檔的非正式標準(請參閱 參考資料 )。“智能 ASCII”只添加了一點額外的語義內(nèi)容和上下文,而且它們在文本顯示中看起來是那么“自然”。電子郵件、新聞組郵件、FAQ、項目自述文件 (README) 和其它電子文檔通常包括一些印刷/語義元素,如強調(diào)字前后的星號、標題下的下劃線、描述文本關(guān)系的豎直和水平空格、有選擇性的全大寫和其它一些信息。Project Gutenberg(請參閱 參考資料 )是一種驚人的成果,它將許多想法加入其自身格式構(gòu)思中,并認為“智能 ASCII”是長時間保存和分發(fā)好書的最佳選擇。即使這些文章不會像文學名著那樣經(jīng)久不衰,仍決定將它們寫成“智能 ASCII”格式,并用方便的 Python 腳本將它們自動轉(zhuǎn)換成其它格式。
介紹 Txt2Html
Txt2Html 最初是一個簡單的文件轉(zhuǎn)換器,從其名稱上就可以看出。但因特網(wǎng)建議在工具上添加幾個明顯的增強功能。因為有許多讀者想以“HTML 化”格式查看的文檔都在 http: 或 ftp: 鏈接的后面,所以工具應(yīng)該真正直接處理這樣的遠程文檔(而不需要下載/轉(zhuǎn)換/查看循環(huán)周期)。因為轉(zhuǎn)換的目標最終是 HTML,通常我們要做的就是在 Web 瀏覽器中查看轉(zhuǎn)換后的目標文檔。
將這些放到一起后,Txt2Html 就成了“基于 Web 的過濾代理”。這個詞很奇特,也許恰好能“完全表達其含義”。它們體現(xiàn)了以下想法:程序代表您閱讀 Web 頁面(或其它資源),以某種方式處理內(nèi)容,然后以某種比原始頁面 更好 的形式(至少能滿足某些特殊目的)向您顯示該頁面。這種工具的一個很好例子就是 Babelfish 翻譯服務(wù)(請參閱 參考資料 )。在通過 Babelfish 運行了 URL 之后,您看到的 Web 頁面與原始頁面非常相似,但它顯示了您能夠讀懂的文字,而不是您不理解的語言。在某種程度上,所有顯示搜索結(jié)果頁面摘要的搜索引擎所做的都是相同的事情。但那些搜索引擎(按設(shè)計)在目標頁面的格式以及外觀方面有更多的自由度,同時會去掉許多內(nèi)容。當然,Txt2Html 并不如 Babelfish 那樣功能強大;但概念上,它們很大程度上完成相同的事情。請參閱 參考資料以獲取更多的例子,其中一些還很幽默。
Txt2Html 最大的優(yōu)點是使用許多編程技巧,而這些技巧對于不同面向 Web 使用 Python 是共通的。本文將介紹那些技巧,并會說明編碼技巧和一些 Python 模塊的作用域。請注意:Txt2Html 中的實際模塊叫做 dmTxt2Html,以避免與其他人編寫的模塊名稱發(fā)生沖突。
使用 cgi 模塊
Python 標準發(fā)行版中的 cgi 模塊對于任何用 Python 開發(fā)“公共網(wǎng)關(guān)接口”應(yīng)用程序的人來說都是意外驚喜??梢圆皇褂盟鼇韯?chuàng)建 CGI,但您不會這樣做。
最通常的情況下,您會通過 HTML 表單與 CGI 應(yīng)用程序進行交互。要填寫調(diào)用 CGI 的表單以執(zhí)行使用規(guī)范的操作。例如,Txt2Html 文檔使用這個例子來調(diào)用 HTML 表單(Txt2Html 自身生成的表單比較復雜,并且可能會改變,但示例將會很好地工作,甚至是在您自己的 Web 頁面中):
調(diào)用 'Txt2Html' 的 HTML 表單
<form method="get" action="">
URL: <input type="text" name="source" size=40>
<input type="submit" name="go" value="Display!">
</form>
可以在 HTML 表單中包括許多輸入字段,而字段可以是許多不同類型中的一種(文本、復選框、單選列表、單選按鈕等)。任何講述 HTML 的好書可以幫助初學者創(chuàng)建定制 HTML 表單。此處最需要牢記的是每個字段都有名稱屬性,而以后會在 CGI 腳本中使用該名稱來引用該字段。另一個需要了解的細節(jié)是表單可以使用以下兩種方法中的一種:"get" 和 "post"。它們的基本差異是 "get" 在 URL 中包括了查詢信息,并且這個方法使用戶易于保存特定查詢以便日后重復使用。另一方面,如果您不希望用戶保存查詢,請使用 "post" 方法。
以上表單調(diào)用的 Python 腳本將導入 cgi 以使整理其調(diào)用表單變得更容易。這個模塊所做的一件事就是隱藏 CGI 腳本中 "get" 和 "post" 方法之間差異的任何細節(jié)。在進行調(diào)用之前,這并不是 CGI 創(chuàng)建程序需要操心的細節(jié)。該 CGI 模塊的主要作用是處理類似于字典樣式的調(diào)用 HTML 表單中的所有字段。您所得到的并不是 真正 的 Python 字典,但它們的使用方式極其相似:
使用 Python [cgi] 模塊
?1234567891011121314 import cgi, sys cfg_dict = { 'target': '<STDOUT>'} sys.stderr = sys.stdout form = cgi.FieldStorage() if form.has_key( 'source'): cfg_dict[ 'source'] = form[ 'source'].value
在以上幾行中,要注意幾處細節(jié)。我們所使用的一個技巧是設(shè)置 sys.stderr = sys.stdout。如果我們的腳本遇到了一個未捕獲的錯誤,通過這個操作,追溯就顯示回到客戶機瀏覽器。這可以節(jié)省許多調(diào)試 CGI 應(yīng)用程序的時間。但您也許不想讓用戶看到這些(或者如果它們可能向您報告問題詳細信息,您也許會向用戶顯示這些內(nèi)容)。接著,我們將 HTML 表單值讀取到類似于字典的 form 實例中。表單有一個 .has_key() 方法,這與真正的 Python 字典非常相似。但是,與 Python 字典不同,要真正獲取鍵中的值,我們必須查看鍵的 .value 屬性。
至此,HTML 表單中的所有東西都成了純 Python 變量,而且我們可以在其它任何 Python 程序中處理它們。
使用 urllib 模塊
就像大多數(shù) Python 模塊一樣,urllib 以直觀且簡單的方法處理許多復雜事物。urllib 中的 urlopen() 函數(shù)可以處理任何遠程資源 -- 無論是 http:、ftp: 還是 gopher: --就將它當作是本地文件。如果使用 urlopen() 抓取了一個遠程(偽)文件對象,可以將它當作本地(只讀)文件的文件對象來進行處理:
使用 Python [urllib] 模塊
?123456789101112131415161718192021222324252627282930313233343536373839 from urllib import urlopen import string source = cfg_dict[ 'source'] if source == '<STDIN>': fhin = sys.stdin else : try : fhin = urlopen(source) except : ErrReport(source+ ' could not be opened!', cfg_dict) return doc = '' for line in fhin.readlines(): # Need to normalize line endings! doc = doc+string.rstrip(line)+ '\n'
我曾遇到過一個小問題,由于生成資源的平臺和您的平臺使用不同的行結(jié)束約定,生成的文本中可能會發(fā)生某些奇怪的事(這似乎是 urllib 中的錯誤)。這個問題的解決方法是在以上代碼中執(zhí)行小小的 .readlines() 循環(huán)。不管該資源原來是什么樣子,這個操作會給您一個字符串,它都有所使用平臺的正確行結(jié)束約定(估計可能合理)。
使用 re 模塊
由于本文篇幅限制,這里只討論一部分規(guī)則表達式。參考資料中列出了許多關(guān)于該主題的參考書籍。 re 模塊在 Txt2Html 中的使用很廣泛,用于標識源文本中的各種文本模式。我們查看一個比較復雜的例子:
使用 Python [re] 模塊
?12345678910111213 import re def URLify (txt): txt = re.sub( '((?:http|ftp|gopher|file)://(?:[^ \n\r<\)]+))(\s)', '<a href=", txt) return txt
URLify() 是一個小巧函數(shù),它的功能正如其名稱所示。如果在“智能 ASCII”文件中遇到了類似于 URL 的字符串,它將轉(zhuǎn)換成 HTML 輸出中同一個 URL 真正的熱鏈接。讓我們看看 re.sub() 的作用。首先,從最主要的角度來看,函數(shù)的目的是“查找與第一個模式中的值匹配的字符串,然后通過將第三個變量作為要操作的字符串,以第二個模式替換匹配結(jié)果”。非常好,從這些角度來看,它與 string.replace() 沒有什么區(qū)別。
第一個模式有幾個元素。首先,請注意括號:最高層次由兩對括號組成:(\S) 前復雜的字符串。括號對與可能組成部分替換模式的“子表達式”匹配。第二個子表達式 (\s) 只表示“查找與任何空格字符匹配的字符串,讓我們回過來看什么與之匹配”。因此,讓我們研究一下第一個子表達式。
Python 規(guī)則表達式有它自己的一些技巧。其中一個技巧是子表達式開始處的 ?: 運算符。這表示“查找子模式的匹配字符串,但不包括反向引用中的匹配結(jié)果”。那么,讓我們檢查一下這個子表達式:
((?:http|ftp|gopher|file)://(?:[^ \n\r<\)]+))。
首先,請注意這個子表達式本身由兩個子表達式組成,兩者之間的字符并不屬于任何一方。但是,每個子表達式都由 ?: 開頭,它表示這兩者匹配,但不是為了滿足引用目的。第一個“非引用”子表達式只表示“查找類似于 http 或ftp 或 其它值的字符串”。接著,我們看到字符串 :// ,它表示查找與它完全相似的字符串(簡單嗎?)。最后,我們看到第二個子表達式,它除了有由方括號組成的“不引用”運算符外,還有一個加號。
在規(guī)則表達式中,方括號只表示“查找與方括號中任何字符的匹配字符”。但是,如果第一個字符是插入記號 (^),含義則相反,它表示“查找任何與后面的字符 不匹配的字符”。因此,我們正在查找 不 是空格、CR、LF、"<" 或 ")" 的字符(還請注意,可以通過在字符前面加 "\",避開對于規(guī)則表達式有特殊意義的字符)。結(jié)尾處的加號表示“查找最后字符串的一個或多個匹配”(星號表示“零個或多個”,問號表示“零個或一個”)。
這個規(guī)則表達式有許多內(nèi)容有待理解,但如果多看幾遍,您就會明白這就是 URL 的格式。
接著就是替換部分。這就更簡單了??瓷先ヮ愃?和(或者 、 等,如果需要的話)的部分就是剛談到的“反向引用”。(或 )表示模式與匹配表達式的第一個(或第二個)子表達式相匹配。替換部分的其余組成部分沒有什么特別含義:只是一些容易識別為 HTML 代碼的字符。有一件事比較麻煩,那就是匹配 -- 它看上去就是一個空格字符。也許有人會問:“有什么麻煩的?為什么不直接插入一個空格字符?”問得好,實際上我們不 需要 執(zhí)行對 HTML 的操作。但從美學角度來看,最好讓 HTML 輸出盡可能保留轉(zhuǎn)換成 HTML 標記之前的源文本文件外觀。尤其是,讓我們將換行符保留成換行符,空格就是空格(Tab 鍵就是 Tab 鍵)。
更多信息請查看IT技術(shù)專欄