這是有關如何使用 JavaMail 和 XSLT 來實現 e-zine 發布自動化的文章系列的第二部分。在 第 1 部分中,您已經學到了如何將 DocBook XML 文檔轉換成滿足電子郵件發布苛刻需求的文本格式。這需要三個步驟:
使用 XSLT 將 DocBook 轉換成臨時的文本標記語言
使用定制的 Java 應用將文本標記語言重新格式化成純文本
在各種 SAX 過濾器的幫助下整理文本
這種方法的一大好處是:將復雜的過程拆分成幾個獨立的步驟。XSLT 樣式表為您帶來極大的靈活性: 如果要發布的文檔不用 DocBook、而用其它詞匯表編寫,則只更改樣式表即可。而且,SAX 過濾器還幫助使轉換組件模塊化,這就使組件更易讀和更易維護。
在第 2 部分中,我將演示如何用 JavaMail,這個標准 Java 語言電子郵件 API,來通過網絡發送 e-zine,從而完成這個過程。在該過程中,我將重述 SAX 事件處理。這個結論演示了如何在大型應用中集成 XSLT 處理。第 1 部分中的圖 1 演示了組件是如何協同工作的。
圖 1. 解決方案中的組件如何交互
JavaMail 101
在繼續進行之前,我們先回顧 JavaMail。如果您已熟悉 JavaMail,則可以 跳到下一節。
電子郵件始終是最流行的因特網應用程序之一。雖然我們往往將電子郵件與 Eudora、Outlook 或 Netscape 這樣的電子郵件客戶機聯系起來,但是很多應用程序可以自動發送或檢索電子郵件。回想上一次的聯機購物。在完成訂單後的幾分鐘之內,您很可能會受到一封確認電子郵件。它是由電子商店自動發送的。沒有人工參與,並且不需要如 Eudora 這樣的電子郵件客戶機。
認識到 Java 應用程序經常需要發送或接受電子郵件,Sun 開發了 JavaMail 作為標准 API,以用於電子郵件服務。通過 JavaMail,Java 應用程序可以直接發送電子郵件或查看郵箱(無需通過電子郵件客戶機)。請注意,JavaMail 獨立於 Sun JDK 單獨下載(請參閱 參考資料)。
清單 1 中的 SendMessage.java 清單是一個演示 JavaMail 的簡單應用程序。請注意,它導入 javax.mail 和 Javax.mail.internet 包。
要發送電子郵件,第一步是通過 Session.getDefaultInstance() 請求 Session 對象。 Session.getDefaultInstance() 需要一個 PropertIEs 對象,該對象必須至少包含 mail.smtp.host 特性。這個特性必須指向您的 SMTP 主機,否則將無法工作(請參閱側欄 SMTP 主機)。
下一步是創建一個 Message 並設置其各種特性,包括發件人和收件人(請注意,可以有多個收件人,但只能有一個發件人)地址、主題、日期和消息主題。
最後一步是使用 Transport.send() 通過網絡發送消息。SendMessage.Java 只創建一個簡單的文本電子郵件。下一節,我將講述如何創建所謂的多部分電子郵件。
清單 1. SendMessage.Java
package com.psol.xslist;
import Java.util.*;
import Javax.mail.*;
import Javax.mail.internet.*;
public class SendMessage
{
public static final void main(String[] args)
{
try
{
if(args.length < 5)
{
System.out.println("Java com.psol.xslist.SendMessage" +
" from@domain.com to@domain.com mailhost.domain.com" +
" subject "mail content"");
return;
}
Properties props = System.getPropertIEs();
props.put("mail.smtp.host",args[2]);
Session session = Session.getDefaultInstance(props);
Message message = new MimeMessage(session);
InternetAddress from = new InternetAddress(args[0]);
InternetAddress to[] = InternetAddress.parse(args[1]);
message.setFrom(from);
message.setRecipients(Message.RecipIEntType.TO,to);
message.setSubject(args[3]);
message.setSentDate(new Date());
message.setText(args[4]);
Transport.send(message);
}
catch(MessagingException e)
{
System.err.println(e.getMessage());
}
}
}
多部分電子郵件和配置文件
有了 JavaMail 和 第 1 部分中介紹的文本格式化器,就可以發送 e-zine 了。可以更新 SendMessage.Java 中的 setText() ,以使用第 1 部分中介紹的文本變換結果,然而,還可以做得更好。特別是,還可以使用 多部分/備用 電子郵件(請參閱側欄 多部分/備用)。
我選擇將電子郵件信息存儲在 XML 配置文件(如 清單 2 中的 config.XML )中。該文件的結構如下所述:
cfg:email 是配置文件的根。它有一個屬性 smtp ,指明 SMTP 主機。在測試之前,必須更改這個參數,因此,我在清單中用粗體標記這個屬性(回想一下側欄 SMTP 主機)。
cfg:header 包含三個屬性:發件人和收件人地址以及電子郵件主題(分別是 from 、 to 和 subject )。在測試時,請確保使用您自己的電子郵件地址。
cfg:body 指向其 source 屬性中的源 XML 文檔。
cfg:body 中還包括 cfg:text 和 cfg:part ,它們控制如何創建不同的消息主體部分。 cfg:text 使用第 1 部分中介紹的文本轉換來創建文本主體,而 cfg:body 則應用簡單的 XSLT 樣式表來創建主體的 Html 版本。
所有這些元素都位於 http://www.psol.com/xns/xslist/config 名稱空間中。請記住,雖然 XML 名稱空間的形式是 URL,但它只用作標識。換句話說,如果將浏覽器指向那個名稱空間,將找不到任何網站。
最後請注意,該配置文件只包含一個收件人的地址。那是因為大多數人都通過特殊的服務器(如 Topica 或 SparkLIST,請參閱 參考資料)分發郵遞列表,這些服務器將管理有關訂閱和取消訂閱的事項。將 e-zine 發送到郵遞列表服務器將把 e-zine 發往所有訂戶。
請確保在嘗試這段樣本代碼之前將 smtp 、 from 和 to 屬性改成您自己的值。
清單 2. 假想郵遞的 config.XML
<?XML version="1.0"?>
<cfg:email XMLns:cfg="http://www.psol.com/xns/xslist/config"
smtp="
mailandnews.com">
<cfg:header from="username@mailandnews.com"
to="nobody@example.com"
subject="XSL -- First Step in Learning XML"/>
<cfg:body source="article.XML">
<cfg:text styleSheet="text.xsl" contentType="text/plain"/>
<cfg:part styleSheet="html.xsl" contentType="text/Html"/>
</cfg:body>
</cfg:email>
讀取配置文件
處理配置文件的工作由 清單 3 中的 ConfigHandler.Java 執行。它繼承 SAX DefaultHandler ,並用語法分析器實現接口。正如 第 1 部分中所說的那樣,本節將回顧 SAX 事件處理。如果您熟悉 SAX 事件處理,則可以 跳過本“回顧”部分。
SAX 是基於事件的 API,這意味著,語法分析器在處理 XML 文件時會將事件-類似於 AWT 的事件-發送到應用程序。應用程序可以忽略這些事件,也可以處理它們。大多數事件處理器都至少處理以下事件:
startElement() ,語法分析器讀取到開始標記時調用它
endElement() ,語法分析器遇到結束標記時調用它
characters() ,語法分析器遇到字符數據時調用它。ConfigHandler.Java 的不尋常之處在於它忽略 characters() 事件。
SAX 還定義很多其它事件,但這三個是最主要的。有關其它 SAX 事件的詳細信息,請參閱 參考資料。
要想理解 SAX,必須記住 XML 文檔是一種元素的層次結構。換句話說,它是一棵樹。語法分析器讀取這棵樹,並通過事件為應用程序描述它。通過調用 startElement() ,語法分析器通知應用程序:找到了一個新的分支。 endElement() 用於標記當前分支的結束。
編寫 SAX 處理器的困難在於:要將處理一個特定元素(例如 cfg:body )的代碼拆分成幾個事件(例如 startElement() 和 endElement() )。
而且,語法分析器不提供上下文信息,因此,應用程序必須負責匹配各個事件。ConfigHandler.Java 使用 state 變量來跟蹤在配置中讀取的內容。
在讀取文檔的過程中,ConfigHandler.java 構建一個多部分的 Message ,並通過應用適當的樣式表來創建不同版本的消息(文本和 Html)。當讀取到文檔末尾時,ConfigHandler.Java 發送消息。
這種機制被證明是很靈活的,因為您可以通過使用不同的樣式表來創建任意多的消息主體版本。如果還想使用第三種版本,(比如 WML(無線標記語言)),只需再提供一個樣式表並相應改寫配置文件即可。
集成在一起
清單 2 中的 config.xml 引用 清單 4 中的 html.xsl 樣式表。樣式表 html.xsl 是一個典型的將第 1 部分中介紹的 article.XML轉換成 Html 的樣式表示例。
清單 4. html.xsl 將 article.XML(在本系列的第 1 部分中)轉換成 Html
<?XML version="1.0"?>
<xsl:stylesheet
XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="Html"/>
<xsl:template match="/">
<Html>
<head>
<title><xsl:value-of
select="article/articleinfo/title"/></title>
</head>
<xsl:apply-templates/>
</Html>
</xsl:template>
<xsl:template match="article">
<body>
<xsl:apply-templates/>
</body>
</xsl:template>
<xsl:template match="articleinfo/title">
<h1><xsl:apply-templates/></h1>
</xsl:template>
<xsl:template match="sect1/title">
<h2><xsl:apply-templates/></h2>
</xsl:template>
<xsl:template match="ulink">
<a href="{@url}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="emphasis">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="author">
<p>by <xsl:value-of select="firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of select="surname"/></p>
</xsl:template>
</xsl:stylesheet>
最後需要的代碼是 main() 方法。它位於 清單 5 XslList.java 中。因為 ConfigHandler.java 負責創建和發送電子郵件,所以, main() 非常簡單:它創建一個 SAX 語法分析器,將 ConfigHandler.Java 注冊為事件處理器,然後通過調用 parse() 開始語法分析。
當然,在語法分析器分析文檔語法時發生很多事。語法分析器一直向 ConfigHandler.java 發送事件,很多重要的處理(例如創建和發送消息)在 ConfigHandler.Java 中進行。當 parse() 返回時,已經應用了兩個樣式表並且電子郵件已經被發送。
清單 5. XslList.Java
package com.psol.xslist;
import Java.io.*;
import org.XML.sax.*;
import org.XML.sax.helpers.*;
public class XsList
{
protected static final String
PARSER_NAME = "org.apache.xerces.parsers.SAXParser";
public static void main(String[] args)
{
try
{
if(args.length < 1)
{
System.out.println("Java com.psol.xslist.XsList input.XML");
return;
}
ConfigHandler configHandler = new ConfigHandler();
XMLReader parser =
XMLReaderFactory.createXMLReader(PARSER_NAME);
parser.setFeature("http://XML.org/sax/features/namespaces",
true);
parser.setContentHandler(configHandler);
parser.parse(args[0]);
}
catch(IOException e)
{
System.err.println(e.getMessage());
}
catch(SAXException e)
{
System.err.println(e.getMessage());
}
}
}
現在該您了
我准備本文的目的有三個。我想構建一個不使用浏覽器的 XML 應用。我認為,在浏覽器中使用 XML 與使用 Java 進行小應用程序設計的意義沒什麼不同。希望本文清楚地表明了這樣一個觀點:有用的 XML 不需要浏覽器。
我還想演示 XSLT 和 SAX 事件處理的組合為何比只使用某一技術本身要好。在 第 1 部分中,您已經看到了如何使用 XSLT 來簡化為 SAX 處理器提供的標記。
最後,因為我知道大多數程序員都是通過研究示例來學習更多知識的,所以,我為您演示了一個完整的應用。順便提一句,如果您喜歡這種方式,那麼,您還會喜歡我寫的 ApplIEd XML Solutions一書(請參閱 參考資源),這本書包含了其它八個示例。
現在該您了。本文中的應用只用於 e-zine 發布這個特定領域,但是它所演示的 XML 技術(XSLT、DocBook、SAX 過濾器、JavaMail 以及其它技術)並不限於 e-zine 發布。請研究代碼,看看它如何在您的領域內幫助您。