用JavaMail和XSLT管理e-zine系列之二

2002-8-20 15:23:47【作者】 畅享网 【进入论坛】
广告

用JavaMail和XSLT管理e-zine系列之二

--使用XML和XSLT自动生成纯文本和HTML格式的时事通讯


Benoit Marchal (
bmarchal@pineapplesoft.com)

咨询人员,Pineapplesoft

2001 年 4 月

在本系列的开篇中,Benoit Marchal 演示了如何用 Java 和 XML 实现电子邮件发布的自动化。这个具体的 XML 和 XSLT 应用演示了一个电子邮件时事通讯 ezine 发布应用程序,该程序既输出 HTML 格式的电子邮件消息,又输出纯文本格式的电子邮件消息。本文中的五个可重用代码样本包括一个使用 JavaMail 发送电子邮件的 Java 程序、一个将第 1 部分中介绍的 DocBook 样本转换成 HTML 的 XSLT 样式表、一个 Java 配置处理器(SAX ContentHandler 形式)以及将所有这些集成在多步骤变换中的 Java 代码。

这是有关如何使用 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. 解决方案中的组件如何交互

SMTP 主机

JavaMail 新手常常对 SMTP 主机(也称为 SMTP 中继、邮件主机或邮件发送服务器)感到困惑。

其实,概念很简单:ISP 为您提供一个电子邮件服务器,您的电子邮件客户机(Eudora、Outlook 或 Netscape)使用该服务器来发送电子邮件。因为在本应用中,JavaMail 取代电子邮件客户机,所以,它需要使用电子邮件服务器。

请仔细查看您的电子邮件客户机配置,以确定您的 SMTP 主机。为保险起见,还可以咨询 ISP 或系统管理员。

最后警告一句,您可能直接无法访问某些公司网络上的 SMTP 主机。在那种情况下,因为 JavaMail 确实需要 SMTP 主机,所以您的唯一希望就是诱使系统管理员给予您特殊权限。

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 部分中介绍的文本变换结果,然而,还可以做得更好。特别是,还可以使用 多部分/备用电子邮件。

多部分/备用

多部分/备用电子邮件是一种由因特网电子邮件标准定义的功效强大的概念。这些电子邮件包括几个消息副本,这些副本通常具有不同的格式,以便电子邮件客户机可以选取最佳格式呈示给用户。

支持 HTML 的电子邮件客户机使用 HTML 版本(如果有 HTML 版本的话),而基于文本的客户机则使用文本版本。这一概念对 e-zine 发布很有用:以多部分/备用电子邮件形式发送 e-zine 的 HTML 和文本版本可以确保与所有电子邮件客户机兼容。

至少在理论上是这样。实际上,大多数基于文本的电子邮件客户机在引入 多部分/备用电子邮件之前就已出现,它们将为用户显示文本和 HTML 版本。不幸的是,对于大多数订户,HTML 只显示为不可读的无用信息。因此,聪明的 e-zine 发布人员都确保文本主体在电子邮件中首先出现。

我选择将电子邮件信息存储在 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 发布。请研究代码,看看它如何在您的领域内帮助您。

 参考资料

可以下载本项目的源代码,包括一个 ANT 构建文件。

Ralph Wilson 博士完成了一个电子邮件客户机调查。他报告了用户对 HTML 电子邮件的支持。

如果需要邮递列表服务器承运商,请查看以下两个流行的供应商:TopicaSparkLIST,它们负责管理列表,包括订阅和取消订阅。另外,请参阅 David Strom 的 Web Informant,其中包括了他最近的邮递列表服务的比较。

JAXP,Java API for XML,集成了 SAX(XML 语法分析)和 TrAX(XSLT 变换)。该站点包括一篇 Stuart Halloway 所著的 SAX 教程

有关 SAX 的详细信息,请转至 David Megginson 的网站。Megginson 是 SAX API 的维护人员。

JavaMail 是用于电子邮件的标准 Java API。(它独立于 Sun JDK 单独下载。)

这个两部分系列的第一部分讨论了从 XML 到文本的变换。它还描述了这个应用的体系结构。

如果您喜欢这个示例应用,还可以参考本文作者所著的 Applied XML Solutions 中其它八个高质量的示例。

有关 XML 的 Java 编程的基本介绍,请参阅同名的 developerWorks 教程。

关于作者

Benoit Marchal 是比利时 Namur 的咨询人员和作家。他编写了 XML by ExampleApplied XML Solutions。他是 Gamelan 的专栏作家。

当 Ben 在 1998 年创建了 Pineapplesoft Link 时,他率先学习了 e-zine 发布。可以在 www.marchal.com 订阅他的 e-zine 并找到有关他最近项目的详细信息。

如果您希望与本文章的作者或其所在机构,进一步交流,请联系:畅享网 姜小姐
jill.jiang@amt.com.cn | 021-51096826-112 | 在线联系
张红霞——能源信息化专栏激战2008,软件市场谁主沉浮?..

纵观近几年的中国能源管理软件市场,基本都被国外产品瓜分,国外的产品真的如此强大,如此完美,国产软件不能取代吗?

吕建伟 专栏CRM下午茶(二):从SNS谈起

不要认为简单的客户关系管理一实施,销售就比现在上去了。没有这样的事情,没有人能随随便便成功,一分付出才有一份收获。

第二届中国管理软件与IT服务年会—2..

“第二届中国管理软件与IT服务年会”于2008年7月23日-25日举行,由AMT集团与畅享网共同主办,无锡扬名高新技术产业园特别赞助支持。

CIO职场,强者生存?

在2008年,我们将继续看到CIO向商业运营方向发展。与此同时,我们也会看到商业管理人员将与技术管理人员一起竞争CIO岗位。 IT领导者的就职机会虽有不少,但其难度将会大幅提高。2……