级别: 中级 Nicholas Chase (nicholas@nicholaschase.com)), 总裁, Chase & Chase, Inc.
2003 年 3 月 01 日 本文中,Nick 介绍了如何检索连锁内容并转换成网站上的标题。因为这类提示没有正式的格式,聚集器经常面临支持多种格式的困难,因此 Nick 还介绍了如何用 XSL 转换来更简单地处理多重连锁文件格式。
随着 Weblog 的普及,信息过载越来越严重。现在读者要比以前浏览更多的站点,按常规的方式全部访问基本上是不可能的。部分问题可以通过内容连锁来解决,即站点把它的标题和基本信息放在单独的
提示中。当今,多数提示都使用一种称为
RSS 的 XML 格式,尽管在使用中有不同的变体,甚至还有一种潜在的竞争格式。
本文介绍了如何利用 Java 技术来检索连锁提示的内容,确定其类型并把它转换成 HTML 格式以显示在 Web 站点上。这个过程包括五个步骤:
- 检索 XML 提示
- 分析提示
- 确定正确的转换
- 执行转换
- 显示结果
本文记录了一个 Java Server Page (JSP)的创建过程,它检索远程提示并使用 Java bean 和 XSLT 进行转换,然后把新转换的信息合并到 JSP 页面中。但是,这个概念实际上适用于任何 Web 环境。
源文件
根据您询问的对象不同,RSS 可能代表 RDF 站点摘要、丰富站点摘要或者其他不那么贴切的缩写词。无论如何,通常使用的 RSS 不少于四种版本,从相当简单的
0.91 版(不包括命名空间,并且对内容进行了某些限制)到 2.0 版(它包含了所有以前的版本,包括 0.91 版——因此有效的 0.91
版文件也是有效的 2.0 版文件,并且允许使用命名空间)。因为支持命名空间,所以 2.0 版允许连锁出版商向提示中增加元素,只要这些元素属于不同的命名空间即可。有些连锁出版商利用这种能力增加使用资源定义格式(RDF)的信息。
一个简单的 RSS 2.0 文件看起来类似于下面这个提示,它取自 Adam Curry 的 weblog (请参阅
参考资料):
清单 1. RSS 2.0 消息示例
<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Adam Curry: Adam Curry's Weblog</title>
<link>http://www.blognewsnetwork.com/members/0000001/</link>
<description>News and Views from Adam Curry</description>
<language>en-us</language>
<copyright>Copyright 2003 Adam Curry</copyright>
<lastBuildDate>Thu, 24 Jul 2003 09:26:48 GMT</lastBuildDate>
<docs>http://backend.userland.com/rss</docs>
<generator>Radio UserLand v8.0.9b2</generator>
<managingEditor>adam@curry.com</managingEditor>
<webMaster>adam@curry.com</webMaster>
<item>
<title>weblog at work again</title>
<link>
http://www.blognewsnetwork.com/members/0000001/2003/07/24.html#a4158
</link>
<description><a href="http://radio.weblogs.com/0001014/images/2003/07/24/ad
amwheely.jpg"><img src="http://radio.weblogs.com/0001014/images/2003/07/24/
adamwheely.jpg" width="250" height="187.5" border="0" align="right" hspace="15" v
space="5" alt="A picture named adamwheely.jpg"></a>A few days ago I aske
d if anyone had taken pictures of me at the annual ...</description>
<guid>
http://www.blognewsnetwork.com/members/0000001/2003/07/24.html#a4158
</guid>
<pubDate>Thu, 24 Jul 2003 09:21:25 GMT</pubDate>
</item>
<item>
<title>teens trouble with web</title>
<link>
http://www.blognewsnetwork.com/members/0000001/2003/07/23.html#a4156
</link>
<description>According to a report from Northumbria University, most teenagers
lack the <a href="http://www.web-user.co.uk/news/news.php?id=33621">inform
ation gathering skills</a> needed for using the internet efficiently. This
sounds like it shouldn't be happening in ...</description>
<guid>
http://www.blognewsnetwork.com/members/0000001/2003/07/23.html#a4156
</guid>
<pubDate>Wed, 23 Jul 2003 17:36:23 GMT</pubDate>
</item>
...
</channel>
</rss> |
要把这个提示转化成 HTML,可以使用 XSL 转换来处理它。
基本的样式表
最终目标是生成 HTML 文本,以便在另一个信息页面内以有组织的方式显示信息,如链接列表。实际的 HTML 输出应该类似于:
清单 2. 输出的 HTML
<h2>Adam Curry: Adam Curry's Weblog
</h2>
<h3>News and Views from Adam Curry
</h3>
<ul>
<li>
<a
href="http://www.blognewsnetwork.com/members/0000001/2003/07/24.html#a4158">weblog
at work again</a>
<p><a href="http://radio.weblogs.com/0001014/images/2003/07/24/adamwheely.jpg">
<img src="http://radio.weblogs.com/0001014/images/2003/07/24/adamwheely.jpg"
width="250" height="187.5" border="0" align="right" hspace="15" vspace="5" alt="A
picture named adamwheely.jpg"></a>A few days ago I asked if anyone had taken
pictures of me at the annual ...
</li>
<li>
<a
href="http://www.blognewsnetwork.com/members/0000001/2003/07/23.html#a4156">teens
trouble with web</a>
<p>According to a report from Northumbria University, most teenagers lack the
<a href="http://www.web-user.co.uk/news/news.php?id=33621">information gathering
skills</a> needed for using the internet efficiently. This sounds like it
shouldn't be happening in ...
</li>
...
</ul>
|
创建 XML 的该 HTML 输出需要一个 XSLT 样式表:
清单 3. 简单的样式表
<?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="/">
<xsl:apply-templates select="//channel"/>
<ul>
<xsl:apply-templates select="//item"/>
</ul>
</xsl:template>
<xsl:template match="channel">
<xsl:apply-templates select="../image"/>
<h2><xsl:value-of select="title"/></h2>
<h3><xsl:value-of select="description"/></h3>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:element name="a">
<xsl:attribute name="href"><xsl:value-of select="link"/></xsl:attribute>
<xsl:value-of select="title" />
</xsl:element>
<p><xsl:value-of disable-output-escaping="yes" select="description" /></p>
</li>
</xsl:template>
<xsl:template match="image">
<xsl:element name="img">
<xsl:attribute name="src"><xsl:value-of select="url"/></xsl:attribute>
<xsl:attribute name="style">float:left; padding: 10px;</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="language">
</xsl:template>
</xsl:stylesheet>
<?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="/">
<xsl:apply-templates select="//channel"/>
<ul>
<xsl:apply-templates select="//item"/>
</ul>
</xsl:template>
<xsl:template match="channel">
<xsl:apply-templates select="../image"/>
<h2><xsl:value-of select="title"/></h2>
<h3><xsl:value-of select="description"/></h3>
</xsl:template>
<xsl:template match="item">
<li>
<xsl:element name="a">
<xsl:attribute name="href"><xsl:value-of select="link"/></xsl:attribute>
<xsl:value-of select="title" />
</xsl:element>
<p><xsl:value-of disable-output-escaping="yes" select="description" /></p>
</li>
</xsl:template>
<xsl:template match="image">
<xsl:element name="img">
<xsl:attribute name="src"><xsl:value-of select="url"/></xsl:attribute>
<xsl:attribute name="style">float:left; padding: 10px;</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="language">
</xsl:template>
</xsl:stylesheet> |
页面的实际形式完全取决您所选择的要包括的数据。在这个例子中,只是创建了一个项目符号列表,带有链接回到最初源点的标题(如果有的话)和每个起源的描述。
要实际执行转换,需要创建一个 JSP 页面。
基本的 JSP 页面
存在无数种转换 XML 数据的方法。本文中将说明如何创建一个 JSP 页面,让它把一个提示交给 Java bean 进行转换。这个 bean
创建一个静态文件,JSP 页面把它结合到页面中。(到下一节“缓冲”就会明白使用静态文件的原因了。)
页面本身相当简单:
清单 4. JSP 页面
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<jsp:useBean id="rssBean" scope="request" class="RSSProcessor">
<%
rssBean.setRSSFile(
"http://wolk.datashed.net/users/adam@curry.com/curryCom.xml");
%>
</jsp:useBean>
<html>
<head>
<title>Syndicated Feeds</TITLE>
</head>
<body>
<jsp:include page="headlines.html" flush="true"/>
</body>
</html>
|
这里只需要创建一个
RSSProcessor 类的实例。因为已经把它包含在了
useBean 元素中,当创建该对象时将执行
setRSSFile() 方法。这个方法创建
headlines.html 页面,然后由
JSP 页面把它结合到输出中。
接下来,要创建执行转换的 bean。
转换文件
Java bean 只不过是带有
get和
set 方法的 Java 类。在这个例子中,set 方法,
setRSSFile() 也包括对那个文件执行转换的代码:
清单 5. 转换提示
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileOutputStream;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
public class RSSProcessor {
public RSSProcessor(){ }
String _RSSFile;
public String getRSSFile(){
return _RSSFile;
}
public void setRSSFile(String fileName){
try {
StreamSource source = new StreamSource(fileName);
StreamSource finalStyle = new StreamSource("final.xsl");
String outputURL = "headlines.html";
StreamResult result = new StreamResult(new
FileOutputStream(outputURL));
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer(finalStyle);
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
这个方法只需要一个输入源——恰好是远程 RSS 提示,使用
final.xsl 样式表把它转换成
headlines.html
文件。
事情的主要步骤是:检索文件、转换、显示结果。但实际应用中还有其他的问题需要考虑。
适应多重格式
如果所有的 RSS 文件都像这个例子一样,就万事大吉了。不幸的是,情况并非如此。不同的提供商和工具包可能产生另外的信息,或者用 RDF 信息或者其他命名空间化的模块代替核心信息,由于这种种的变化导致人们抱怨支持 RSS 太复杂了。但是通过利用 XSL 转换,不一定非要如此。
比方说,一个 RSS 2.0 提示可能还包括 RDF 信息,像下面这个取自 Typographica 的提示那样:
清单 6. 摘自带有 RDF 的 RSS 2.0 消息示例
<?xml version="1.0" encoding="iso-8859-1"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:admin="http://webns.net/mvcb/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Typographica</title>
<link>http://typographi.ca/</link>
<description>A daily journal of typography featuring news, observations,
and open commentary on fonts and typographic design.</description>
<dc:language>en-us</dc:language>
<dc:creator>Stephen Coles</dc:creator>
<dc:rights>Copyright 2003</dc:rights>
<dc:date>2003-07-24T00:00:52-08:00</dc:date>
<admin:generatorAgent rdf:resource="http://www.movabletype.org/?v=2.63" />
<admin:errorReportsTo rdf:resource="mailto:scoles@gomakecontact.com" />
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<sy:updateBase>2000-01-01T12:00+00:00</sy:updateBase>
<item>
<title>Hot and Cold Fonts</title>
<link>http://typographi.ca/000643.php</link>
<description>LettError have developed a multiple master font
for the Design Institute of the University of Minnesota that varies
along three...</description>
<guid isPermaLink="false">643@http://typographi.ca/</guid>
<content:encoded><![CDATA[<p><a href="http://www.letterror.com/">
LettError</a> have developed a multiple master font for the
<a href="http://design.umn.edu/">Design Institute</a> of the University of
Minnesota that varies along three dimensions: formality, informality, and
"weirdness." (It's apparently possible to be 100% formal and 100% informal at
the same time.) As the New York Times...]]></content:encoded>
<dc:subject></dc:subject>
<dc:date>2003-07-24T00:00:52-08:00</dc:date>
</item>
<item>
<title>Textura Digita</title>
<link>http://typographi.ca/000642.php</link>
<description>CNN reports that the Gutenberg Bible is now available
on the web via the Ransom Center at the University of...</description>
<guid isPermaLink="false">642@http://typographi.ca/</guid>
<content:encoded><![CDATA[<p><a href=
"http://www.cnn.com/2003/TECH/internet/07/23/digital.scripture.ap/index.html">
CNN reports</a> that the Gutenberg Bible is now available on the web via the
<a href="http://www.hrc.utexas.edu/exhibitions/permanent/gutenberg/">Ransom
Center</a> at the University of Texas.</p>
...]]></content:encoded>
<dc:subject></dc:subject>
<dc:date>2003-07-23T13:16:15-08:00</dc:date>
</item>
<item>
<title>Fight! Fight! Fight!</title>
<link>http://typographi.ca/000640.php</link>
<description>Angry because you had to miss TypeCon ’03?
Work out that aggression with Helvetica vs. Arial....</description>
<guid isPermaLink="false">640@http://typographi.ca/</guid>
<content:encoded><![CDATA[<p>Angry because you had to miss
<a href="http://www.typecon2003.com/">TypeCon ’03</a>? Work out that
aggression with <a href="http://www.engagestudio.com/helvetica/">Helvetica vs.
Arial</a>.</p>]]></content:encoded>
<dc:subject></dc:subject>
<dc:date>2003-07-22T08:52:36-08:00</dc:date>
</item>
...
</channel>
</rss>
|
注意,这个提示实际上包含两种不同的内容描述。第一个在
description 元素中,第二个在
encoded
元素中,
encoded 元素是
http://purl.org/rss/1.0/modules/content/
命名空间的一部分。这里可以看出,不同提示处理信息的方式不同。Adam Curry 的 blog 只对链接这样的信息编码并放到
description
元素中,而 Typographica(或者更准确地说是生成 Typographica 提示的工具包)在
description
元素中提供无标记的版本,并使用
CDATA 构造在
encoded 元素中提供完整的版本。
尽管为了利用各种额外的信息,为每种提示类型创建定制的表示是可取的,但从应用程序开发的观点来看是不实际的。但这并不意味着必须放弃。相反,可以创建针对不同提示的转换把它们转换成标准格式,然后交给最终的转换。
比如,可以创建一个以 RSS 2.0 样式表为参数的样式表,如果发现
encoded 元素,则用它代替所有的
description
元素:
清单 7. 转换 RDF 信息
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<rss>
<channel>
<xsl:apply-templates select="rss/channel" />
</channel>
</rss>
</xsl:template>
<xsl:template match="title|link|/rss/channel/description|image|text()">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="item" >
<item>
<title><xsl:value-of select="title" /></title>
<link><xsl:value-of select="link" /></link>
<description><xsl:value-of select="description" /></description>
</item>
</xsl:template>
<xsl:template match="item[encoded]" >
<item>
<title><xsl:value-of select="title" /></title>
<link><xsl:value-of select="link" /></link>
<description><xsl:value-of select="encoded" /></description>
</item>
</xsl:template>
</xsl:stylesheet> |
这个样式表制作最终样式表需要的副本,比如通道的标题和描述,并复制带有相应描述信息的
item 。
现在只需要把这个新文档交给最终转换:
清单 8. 串接转换
...
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class RSSProcessor {
...
public void setRSSFile(String fileName){
try {
StreamSource
interimSource = new StreamSource(fileName);
String XSLSheetName = "2.0.xsl";
StreamSource style = new StreamSource(XSLSheetName);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document interimDoc = db.newDocument();
DOMResult interimResult = new DOMResult(interimDoc);
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer interimTransformer = null;
interimTransformer = transFactory.newTransformer(style);
interimTransformer.transform(interimSource, interimResult);
DOMSource source = new DOMSource(interimDoc);
StreamSource finalStyle = new StreamSource("final.xsl");
String outputURL = "headlines.html";
StreamResult result = new StreamResult(new
FileOutputStream(outputURL));
Transformer transformer = transFactory.newTransformer(finalStyle);
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
花点时间看看这个步骤。首先,创建了一个过渡转换,它接受最初的提示并按照
清单
7 中 名为
2.0.xsl 的过渡样式表转换它。第一次转换的结果没有进入文件,而是进入一个 DOM
Document
对象,然后把它作为源传递给第二次转换。
过渡样式表的名称
2.0.xsl 是经过深思熟虑的。根据版本号命名可以创建更灵活的系统。
选择版本
只要允许不同的格式,实际上就可以创建一个在处理之前检查提示版本的系统。毕竟只有 RSS 1.0 和 2.0 提示可以包含 RDF 元素,因此没有必要处理其他的提示。但是如何知道所用的版本呢?
要解决这个问题,可以装载真正的提示并分析,然后利用分析的结果设置正确的样式表。
清单 9. 选择样式表
...
import org.xml.sax.InputSource;
import org.w3c.dom.Element;
public class RSSProcessor {
...
public void setRSSFile(String fileName){
try {
InputSource docFile = new InputSource (fileName);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document inputDoc = db.parse(docFile);
Element rss = inputDoc.getDocumentElement();
String version = null;
if (rss.getNodeName().equals("rss")){
version = rss.getAttribute("version");
if (version == null) {
version = "0.91";
}
} else if (rss.getNodeName().equals("feed")){
version = "echo";
}
String XSLSheetName =
version+".xsl";
StreamSource style = new StreamSource(XSLSheetName);
DOMSource interimSource = new DOMSource(inputDoc);
Document interimDoc = db.newDocument();
DOMResult interimResult = new DOMResult(interimDoc);
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer interimTransformer =
null;
if (version.equals("0.91")){
interimTransformer = transFactory.newTransformer();
} else {
interimTransformer = transFactory.newTransformer(style);
}
interimTransformer.transform(interimSource, interimResult);
DOMSource source = new DOMSource(interimDoc);
StreamSource finalStyle = new StreamSource("final.xsl");
String outputURL = "headlines.html";
StreamResult result = new StreamResult(new
FileOutputStream(outputURL));
Transformer transformer = transFactory.newTransformer(finalStyle);
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
在这个例子中,加载了提示并检查它的 RSS 版本,然后使用这个版本号作为文件名。好处是如果发布了新的 RSS 版本,就可以通过添加新的样式表来扩展应用程序。注意,我已经增加了对
Echo、Atom 或其他任何可能被调用的 RSS 竞争者的检查,您可以根据变化调整对它的支持,只需修改
echo.xsl
样式表就可以了。
好处是这个过渡样式表是完全通用的。"2.0 - .91" 的样式表可以用于任何提示类型、任何地方,无论支持一个版本还是一百个版本,只要编辑
final.xsl 就可以改变最后的输出。
final.xsl 样式表是为简单的 0.91 版提示而设计的,因此如果处理的是这一版本的提示,就可以忽略过渡转换中的样式表。这就形成了
恒等转换,文档按原来的样子输出。
上述方法解决了多重版本的问题,但是还有一个问题需要考虑:并发。
缓存提示
这个系统将在个人服务器上工作得很好,因为只有您访问它,但在现实世界中,每当有人需要阅读提示时就把它拖走是不实际的(也是粗暴的)。相反,需要构造一个带有某种时间延迟机制的系统,如果提示最近被取走了,则使用已有的
headlines.html 文件。
为此,可以利用 Java 应用程序的性质。表示提示最后被取走的时间的
static 变量应该对
RSSProcessor
类的所有实例是一个常数,因此可以在实际拖走提示之前将当前时间与这个变量进行比较:
清单 10. 选择样式表
import java.util.Date;
public class RSSProcessor {
...
static Date _LastUpdated = new Date();
public Date getLastUpdated(){
return _LastUpdated;
}
public void setRSSFile(String fileName){
Date now = new Date();
long diff = now.getTime() - _LastUpdated.getTime();
double interval = .5;
if ((diff == 0) || (diff > (interval * 60 * 1000))){
_LastUpdated = now;
try {
InputSource docFile = new InputSource (fileName);
...
Transformer transformer = transFactory.newTransformer(finalStyle);
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
|
服务器第一次实例化
RSSProcessor 时,
_LastUpdated 使用当前日期实例化。(本质上)在同一时刻,服务器执行
setRSSFile() 方法,因为当前时间和
_LastUpdated 时间的差是0,于是进行转换。
下一次有人调用该页面时,将会创建
RSSProcessor 的一个新实例,但是因为
_LastUpdated
是静态的,所以新实例看到的是
_LastUpdated 的现有值而不是初始化这个变量。
interval
用分钟计量,而
_LastUpdated 和当前时间的差以毫秒计算。如果经过的时间数小于 interval,则什么也不发生。
headlines.html
文件没有更新,因此服务器还是使用原来的那一个文件。
另一方面,如果超过了 interval,
_LastUpdated 取得当前时间,并传递给后续的任何
RSSProcessor
对象,bean 取一个提示的新副本进行转换。
结束语
本文说明了如何创建连锁提示的阅读器,以检索单个远程提示,使用 XSLT 进行转换,并作为 Web 页的一部分来显示。通过使用 XSLT 样式表,该系统还可以适应多重提示类型。
应用程序使用 DOM
Document 分析提示并确定适当的样式表,还可以通过把部分逻辑移到外部样式表中进一步扩展。也可以调整系统使它获取多个提示——可能根据用户的选择,为每个提示创建单独的缓冲文件。类似地,可以让用户决定提示检索的时间间隔。
参考资料
关于作者  | |  | Nicholas Chase,
Studio
B 的作者,参与了包括 Lucent Technologies、Sun Microsystems、Oracle 和 Tampa
Bay Buccaneers 在内的多家公司的网站开发。Nick 曾是一名高中物理教师、低级放射性废物设施管理员、在线科幻小说杂志编辑、多媒体工程师和
Oracle 讲师。最近,他是佛罗里达州 Clearwater 的 Site Dynamics Interactive Communications
的首席技术官。他写了四本有关 Web 开发的书,包括
XML
Primer Plus(Sams)。他乐于倾听读者的意见,可以通过
nicholas@nicholaschase.com
与他联系。
|
对本文的评价
|