我們的目標之一是能以不同格式產生曆史同盟目錄的資訊。我們將產生的最簡單格式是一個年度宴會程式的成員名列表。那可能是一個簡單的無格式文本列表。它將成為建立這個程式的一部分較大文檔,所以,我們所需要的就是可以粘貼到文檔中的一些內容。
對於可顯示的目錄,則需要一種比無格式文本更好的表示方法,原因是我們想把一些內容更精細地格式化。這裡一個合理的選擇為RT F(豐富的文字格式設定Rich Text Format),它是由Microsoft 開發的一種格式,可以由許多文書處理程式來識別。當然, Word 就是這種程式之一,但是許多其他的軟體,如WordPerfect 和A p p l e Work 也是可以識別的。不同的文書處理程式對RTF 的支援程度也有所不同,但是我們將使用由即使對最低層級RT F都確信的任何文書處理程式所支援的全部RTF 規定的一個基本子集。
產生宴會列表和RTF 目錄格式的過程本質上是一樣的:發布查詢來檢索這些項目,然後運行將每個項目提取和格式化的迴圈。給出了基本的相似之處湍芎芎玫乇苊獗嘈戳礁齜摯慕瘧盡K裕頤潛嘈匆桓齙ザ賴慕瘧緂 e n _ d i r,它可以以不同的格式從這個目錄產生輸出。我們可以這樣組織這個指令碼:
1) 在編寫出項目內容之前,完成這個輸出格式可能需要的任何初始化。宴會程式成員列表不需要任何特殊的初始化,但是我們需要為這個RTF 版本編寫一些初始的控制語言。
2) 提取和顯示每個項目,將我們要輸出的類型適當地格式化。
3) 處理完所有的項目之後,還要完成任何必需的清除和終止。除了這個RTF 版本需要的一些關閉控制語言以外,宴會列表不需要特殊的處理。
將來,我們可能想使用這個指令碼以其他格式編寫輸出,所以我們通過設定“轉換盒”——每個輸出格式都有一個元素的散列,使它成為可擴充的。每個元素都指定對給定格式產生適當輸出的函數:初始化函數、編寫項目函數和清除函數如下所示:
由一個格式名(在這種情況下的“ b a n q ue t”和“r t f”)標識轉換盒的每個元素。我們將編寫這個指令碼,以便在運行它時可以在命令列中指定想要的格式:
% gen_dir banquet
% gen_dir rtf
通過以這種方式設定轉換盒,我們可以很容易地增加新格式的效能:
1) 編寫三個格式化函數。
2) 向轉換盒增加一個指向那些函數的新元素。
3) 為了以新的格式產生輸出,調用g e n _ d i r,並在命令列中指定這個格式名。
按照命令列中的第一個參數所選擇的適當轉換盒項目的代碼如下所示。它是由於輸出格式的名稱為%switchbox 散列中關鍵字。如果在轉換盒中不存在這樣的關鍵字,則這個格式是無效的。不需要這個代碼中的硬連線格式;如果向轉換盒增加新的格式,則自動地檢測它。如果在命令列中沒有指定格式名,或者指定了一個無效的名稱,則這個指令碼產生錯誤訊息,並顯示一列允許的名稱:
如果在命令列指定了一個有效格式名,則前述的代碼設定$ f un c _ h a s h r e f。它的值將是指向選擇了格式輸出的編寫函數的散列引用。然後我們可以運行這個選擇項目的查詢。之後,我們調用初始化函數、提取和顯示這些項目,並啟用清除函數:
這個文檔用花括弧‘ {’和‘ }’作為開始和結束。RTF 關鍵字用反斜線符號開始,並且文檔的第一個關鍵字必須為\ r t f n,n為這個文檔對應的RTF 規定的版本號碼。如果按我們的目的,0就比較合適。
在這個文檔的內部,我們指定字型表來說明這些項目所使用的字型。字型表資訊列在組中,由含有前置的\fonttbl 關鍵字和一些字型資訊的花括弧組成。在架構中說明的這個字型表把字型號0 定義為Ti m e s(我們只需要一個字型,但是如果想顯示得更好一些,可以使用多種字型)。
下面的一些指示設定了預設格式風格: \plain 選擇無格式的格式, \f0 選擇字型0(我們已經在字型表中定義為Times ),\fs24 設定字型大小為12個點陣(\fs 後面的數量表示半個點陣的大小)。設定頁邊空白並不是必需的;大多數的文書處理程式將提供合理的預設值。
要想得到一個非常簡單的方法,可以將每個項目顯示為一系列的行,每行上都有一個標號。如果對應於特定輸出行的資訊缺失,則忽略這個行(例如,沒有電子郵件地址的成員沒有顯示“ E m a i l :”行)。一些行(如“ A d d r e s s :”行)由多個列(街道、城市、州、郵遞區號)中的資訊構成,所以這個指令碼必須能夠處理缺失值的各種組合。這裡是我們將使用的輸出格式的範例:
對於顯示的格式化項目,RTF 的表示方法如下所示:
要想使“Name :”行為粗體,則在它的前面加\b (後面有個空格)來開啟粗體,並用\ b 0來關閉粗體。每行在末端都有一個區段標記符( \ p a r)來告訴文書處理程式移到下一行——沒有太複雜的事情。
初始化函數產生前置RTF 控制語言(請注意,兩個反斜線符號獲得輸出中的一個反斜線符號):
類似地,清除函數產生終止控制語言(並不太多!):
sub rtf_cleanup
{
print '}\n";
}
真正的工作與格式化這個項目有關,即使這個任務相對簡單。主要複雜點是將地址字串格式化,並確定應該顯示哪個輸出行:
當然,不用限於這種特殊的格式化風格。可以更改如何顯示任何域的方法,所以通過簡單地更改format_rtf_entry( ),可以幾乎任意地更改顯示的目錄。用它原始格式的目錄(一個文書處理文檔),是多麼不容易做的事情!
gen_dir 指令碼現在完成了。通過運行以下這些命令,我們可以以任意一種輸出格式產生這個目錄:
% gen_dir banquet > name.txt
% gen_dir rtf > directory.rtf
在Windows 中,我可以運行g e n _ d i r,則這些檔案準備從基於Windows 文書處理程式的內部使用。在UNIX 中,我可就以運行上面那些命令,然後將這些輸出檔案以郵件形式發給自己作為附件,以便可以從我的Macintosh 中擷取它們,並將它們載入到文書處理程式中。我偶爾使用mutt 郵寄程式,它允許使用-a 選項從命令列指定附件。可以如下發送給自己一個具有這兩個附加檔案的訊息:
% mutt -a name.txt -a directory.rtf paul0snake.net
其他郵寄程式可能也允許建立附件。或者,可以以其他意思傳輸這些檔案,如F T P。無論如何,在這些檔案被放到想放的地方之後,讀取這個名稱列表,並將它粘貼到年度程式文檔,或者在可識別RTF 的任何文書處理程式中讀取RTF 檔案,這都是較容易的。DBI 使我們從MySQL中抽取想要的資訊很容易, Perl 的文本處理能力使我們將這些資訊放在指定的格式中很容易。MySQL不提供資訊輸出的任何特殊方式,但沒有關係,因為將MySQL的資料庫處理能力整合到如Perl 的語言中並不費力,而這些語言具有極好的文本處理能力。
發送成員資格更新通知
當作為文書處理文檔維護曆史同盟目錄時,確定需要通知哪個成員其成員資格應該更新,這是件耗費時間並且容易出現錯誤的事情。既然我們在資料庫中有資訊,那麼讓我們看看如何自動地處理更新通知。我們想標識需要經過電子郵件更新的成員,這樣我們就不必通過電
話或郵件與他們聯絡了。
我們需要做的事情就是確定哪個成員在某些天以內快到更新的時間了。這個的查詢涉及一個相對簡單的日期計算:
SELECT ... FROM member
WHERE expiration < DATE_ADD (CURRENT_DATE,INTERVAL cutoff DAY)
c ut o ff 表示我們同意的可允許誤差的天數。這個查詢選擇在幾天之內快到更新時間的成員項目。作為特殊情況,終止點值為0,尋找終止日期已過的成員(也就是說,實際上已經終止了的那些成員)。
我們標識了限制通知的這些記錄之後,我們對它們應該怎麼辦呢?一個選擇是直接從同樣的指令碼中發送郵件,但是,首先審閱不發送任何訊息的列表可能有用。由於這個原因,我們將使用一個兩階段的方法:
階段1:運行指令碼need_renewal 來標識需要更新的成員。可檢查這個列表,或者可以使用它作為將更新通知發送到第2 階段的輸入。
階段2:運行指令碼r e n e w a l _ n o t i f y,它通過電子郵件向成員發送“請更新”的通知。這個指令碼應該通知您不具有電子郵件地址的成員,以便可以用其他方式與他們聯絡。
在此任務的第一部分中, need_renewal 指令碼必須標識哪個成員需要更新。它的操作如下所示:
可以觀察到,處於負數天數的那些成員資格需要更新。負數意味著我們已經到期了(當手工地維護記錄時,就可能發生這種情況;有些人從縫隙中滑掉了。既然我們在資料庫中有了這些資訊,那麼我們要尋找在前面丟失的幾個人)!
更新通知任務的第二部分涉及了通過電子郵件發送通知的指令碼r e n e w a l _ n o t i f y。要想使renewal_notify 更容易使用,則我們可以使它支援三類命令列參數:成員關係識別碼,電子郵件地址和檔案名稱。數值的參數表示成員資格ID 值,帶有字元‘@’的參數表示電子郵件的地址。其他任何事情都解釋為應該讀取的檔案名稱,以便找到他們的識別碼或電子郵件地址,可以直接在命令列中這樣做,或者通過將它們在檔案中列出來去做(特別是,可以使用need_renewal 的輸出作為renewal_notify 的輸入)。
對於要發送通知的每個成員,此指令碼尋找相應的member 表項目,抽取電子郵件地址,並向那個地址發送一條訊息。如果此項中沒有電子郵件地址,則renewal_notify 產生一條訊息,通知您需要以一些其他方式與這些成員聯絡。
要想寄送電子郵件, renewal_notify 開啟與sendmail 程式的管道,並將這封郵件推入此管道中(在Windows 下不能這樣操作,Windows 中沒有s e n d m a i l。可能需要尋找發送郵件的模組來代替它使用)。在此指令碼開頭附近,將到sendmail 的路徑名設定為參數。可能需要更改該路徑,因為sendmail 的位置隨系統的變化而變化:
# change path to match your system
my ($sendmail)="/usr/lib/sendmail -t -oi";
主要參數處理迴圈的操作如下所示。如果在命令列沒有指定參數,則我們讀取標準的輸出作為輸入。否則,我們通過將參數傳遞給i n ter p r e t _ a rgument( ),將它分類為識別碼、電子郵件地址或者檔案名稱來處理每個參數:
i n ter p r e t _ a rgument( ) 函數將每個參數分類,以便確定它是識別碼、電子郵件地址還是檔案名稱。對於識別碼和電子郵件地址,它尋找適當的成員項目,並將它傳遞給n o t i f y _ member ()。我們必須注意由電子郵件所指定的成員。兩個成員具有同樣的地址是可能的(例如,丈夫和妻子),並且我們不想將一條訊息發送給不能用這條訊息的人。為了避免這一點,我們尋找了與電子郵件地址相對應的成員的識別碼,來確保內容的正確。如果此地址和一個以上的識別碼匹配,則它是不確定的,我們在顯示一條警告訊息後忽略它。
如果參數看起來不像ID號碼或電子郵件地址,則將它作為檔案名稱讀取為進一步的輸入。在這裡,我們也必須小心——為了避免無窮迴圈的可能性,如果我們已經讀取一個檔案,則我們不想再讀取檔案:
實際上,發送更新通知的notify_member( ) 函數的代碼如下所示。如果得出這個成員沒有電子郵件地址,則什麼也不做,但是notify_member( ) 顯示一條警告訊息,以便知道需要以其他某種方式與該成員聯絡。可以調用具有這條訊息中所顯示的這個成員資格識別碼的s h o w _ member,來查看全部項目—例如,找出這個成員的電話號碼和通訊地址。
edit_member 的問題為它不進行任何輸入值校正。對於member 表中的大多數域,都沒有什麼校正——它們只是字串域。但是對於expiration 列,實際上應該檢查輸入值,以便確保它們看起來像日期。在一般目標的資料輸入應用程式中,可能想抽取有關表的資訊,以便確定它的所有列的類型。然後,可能按照那些類型上的約束條件來校正。那就比我在這裡想探求的內容涉及得更多,所以我只在col_prompt( ) 函數中增加一個快速方法,以便如果列名為“e x p i r a t i o n”,則檢查輸入的格式。最低限度的日期值檢查可以這樣來做:
這個模板測試了非數字字元分隔的三個序列的數字。這隻是檢查的一部分,因為它沒有偵測如“ 1999 - 14 - 2 2”的值為無效。要想使指令碼更好,則應該給它更嚴格的日期檢查以及其他檢查,如需要名和姓的域,就應該給非空值。
一些其他的改進可能是,如果沒有更改列,則跳過這個更新,當使用者正在編輯它時,如果其他一些人已經更改了這條記錄,則通知這個使用者。可以通過儲存成員項目列的未經處理資料來做到這一點,然後,編寫UPDATE語句來只更新那些已經更改的列。如果沒有,則甚至不需要發布這條語句。同樣,對於每個原始列值,可以編寫WHERE 子句來包括A N D col_name = col_val。如果其他一些人已經更改了這條記錄,則這可能導致UPDATE失敗,此時它的反饋為,兩個人要同時更改這個項目。
尋找共同興趣的曆史同盟成員
曆史同盟秘書的責任之一就是處理成員的請求,這些成員可能要求對美國歷史領域內特殊時期或特殊人物(如在大蕭條中或者亞伯拉罕·林肯的生命)感興趣的其他人清單。當在文書處理程式文檔中維護這個目錄時,使用文書處理程式的“ F i n d”功能,可以非常容易地找到這樣的成員。然而,產生一列只含有合格成員的項就要困難一些,因為它涉及大量的拷貝和粘貼。使用MySQL,工作就變得容易得多,因為我們可以只運行如下這樣的查詢:
SELECT * FROM member WHERE interests LIKE "%lincoln%"
ORDER BY last_name,first_name
不幸的是,如果在mysql客戶機程式運行這個查詢,則結果看上去並不是非常好。讓我們把少量的DBI 指令碼和產生較漂亮的輸出的interests 放在一起。首先,檢查一下指令碼,確保在命令列至少有一個命名的參數,因為如果沒有一個命名的參數就沒有內容可以搜尋。然後,對於每個參數,指令碼在member 表的interests 列上運行一個查詢:
在7 . 4節中,我們將開始編寫串連到MySQL伺服器並抽取資訊的指令碼,還要編寫以Web頁面形式在客戶機的網頁瀏覽器中出現的資訊。那些指令碼按照客戶機請求動態地產生了H T M L。在我們到達那一點之前,讓我們通過編寫產生能裝載到Web 服務器文檔樹中的靜態
HTML 文檔的DBI 代碼,開始考慮有關的H T M L。以HTML 格式建立的曆史同盟目錄是最好的選擇,因為我們的目標之一就是無論如何要使目錄聯機。
一般來說,HTML 文檔有點像下面這樣的結構:
現在我們把另一個元素加到轉換盒中,指出編寫HTML 的函數,並且完成對g e n _ d i r的更正:
為了產生HTML 格式的目錄,運行下面的命令並在Web 服務器的文檔樹中安裝結果輸出檔案:
% gen_dir html > directory.html
當更新目錄時,可以再次運行命令來更新聯機版本。另一個方案是建立周期性執行的cron 作業。那就是說,聯機目錄將被自動地更新。例如,我可能使用類似於這個的crontab 項在每天早晨4點運行g e n _ d i r:
04****/u/paul/samp_db/gen_dir>/usr/local/apache/htdocs/directory.html
這個cron 作業所啟動並執行使用者必須允許它們都執行位於samp_db 目錄中的指令碼,並將檔案編寫到Web伺服器的文檔樹中。