本文中,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 页面。
|
存在无数种转换 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
分析提示并确定适当的样式表,还可以通过把部分逻辑移到外部样式表中进一步扩展。也可以调整系统使它获取多个提示——可能根据用户的选择,为每个提示创建单独的缓冲文件。类似地,可以让用户决定提示检索的时间间隔。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 看看 Syndic8, 在那里能找到成千上万的 RSS 提示,可以根据类型和工具包搜索。它还包含了一个很好的带有说明文档的 参考资料专栏 。
- 阅读 James Lewin 的 “ An introduction to RSS feeds” ( developerWorks, 2000 年 11 月)。
- 关于另外一种观点,请参阅 Mike Olson 和 Uche Ogbuji 所写的“ 用于 Python 的 RSS”一文 ( developerWorks, 2002 年 11 月)。
- 阅读 Michael Kay 的文章“ XSLT 是什么类型的语言?” ( developerWorks,2001 年 2 月)。
- RSS 2.0最近 改为 由哈佛大学的 Berkman Center 负责。这可能会也可能不会影响 (Not)Echo/(Not)Atom/WhateverTheyVoteToCallIt 项目。
- 访问 Adam Curry's Weblog。
- 阅读 XSLT 1.0 Recommendation,在 W3C 的 XSL页面 获取 XSLT 2.0 的最高指示。
- 在 developerWorks XML和 Web 服务专区查找更多资源。
- IBM 的 DB2 数据库不仅提供了关系数据库存储,还包括与 XML 相关的工具,如 DB2 XML Extender,提供了 XML 和关系系统之间的桥梁。访问 DB2 开发者园地进一步了解 DB2。
- 了解如何成为一名 IBM 认证的 XML 及相关技术的开发人员。
| 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 与他联系。 |