改进 RSS Bandit 应用程序
下载 RSSBandit Installer.msi 示例文件。
本页内容
简介
了解 RSS Bandit 的用户界面
RSS Bandit 结构概述
XML 技术和 RSS Bandit
RSS Bandit 的未来规划
简介
在上一篇文章中,我描述了 RSS Bandit 应用程序的内部工作原理,该应用程序通过处理 Web 上的 RSS 提要来收集各种 Web 站点上的信息。正如我在上一篇文章中提到的那样,RSS 是一种 XML 格式,用于组合来自联机新闻源的新闻和类似内容。
RSS 提要是一个定期更新的 XML 文档,其中包含有关新闻源和其中内容的元数据。一个 RSS 提要至少应包含一个代表新闻源的 channel,channel 包含一个标题、链接和描述新闻源的说明。此外,一个 RSS 提要通常会包含一个或多个代表单个新闻项的 item 元素,其中每个元素都应包含一个标题、链接或说明。
自撰写上一篇文章以来,RSS 已被更广泛地用作 Web 上散布新闻的机制。RSS 提要不仅由联机新闻源(如 Yahoo!News、BBC 和 Rolling Stone magazine)使用,而且已经在以开发人员为中心的信息源(如 Microsoft Developer Network (MSDN)、Oracle Technology Network (OTN) 和 Sun Developer Network)之间变得很流行。随着 RSS 提要的迅速增加,桌面新闻集合器已经成为一个功能强大的工具,让那些有兴趣及时了解各种新闻源信息的人不必浏览多个 Web 站点来获取固定的信息。
在过去的几个月中,GotDotNet 上的 RSS Bandit 工作区已经收到了 .NET 框架开发人员社区多个成员(如 Torsten Rendelmann、Michael Earls、Joe Feser)和许多 RSS Bandit 工作区其他成员的踊跃投稿。本文描述在过去几个月内 RSS Bandit 应用程序中各种新增功能的内部原理。
了解 RSS Bandit 的用户界面
RSS Bandit 的用户界面受邮件和新闻阅读器(如 Microsoft Outlook® 和 Microsoft Outlook Express)的启发。在 RSS Bandit 的当前版本和上一篇文章中的版本之间有一个最显著的区别,那就是用户界面得到了改善。RSS Bandit 使用一个魔术库,此库是用户界面控件的框架,它提供的功能比基本 Windows 窗体控件更丰富。
魔术库提供的强大功能之一就是能够创建选项卡式窗格,此功能允许用户在单个应用程序中嵌套多个窗体。下面的图 2 显示魔术库中的选项卡式窗格如何允许用户从 RSS Bandit 内部利用多个 Web 浏览器窗口。
RSS Bandit 结构概述
RSS Bandit 应用程序由两个截然不同的部分组成:图形用户界面组件和 XML 和网络组件。主要的 GUI 类是 WinGuiMain 和 RssBanditApplication 类,而主要的 XML 和网络类是 RssHandler 和 RssLocater。
RssHandler 类按指定间隔时间下载 RSS 提要,并将它们提交给 CacheManager 来存储。CacheManager 使用的存储区与应用程序的耦合程度不紧密,实际上,CacheManager 是一个抽象的类,目前它只有一个具体实现,即用于将文件缓存到本地文件系统上的 FileCacheManager。这种灵活性意味着可以在将来引进新类型的 CacheManager,以便使用更好、更优化的存储区,如数据库管理系统。同样,RssHandler 类与用户界面的耦合程度也不紧密,它可以由需要处理 RSS 提要的其他应用程序重新使用。利用 RssHandler 类的客户端会在实例化该类时注册一个回调(委托)。然后,在下载新提要或更新后的提要时,RssHandler 对象会调用已注册的回调。有关要下载哪些提要的信息以及其他配置数据,可从用 XML 编写的提要订阅列表中获取。RssLocater 将在尝试发现特定 Web 站点的 RSS 提要时使用,并在尝试定位提要时,使用一组定义完善的试探法。
RssBanditApplication 从 ApplicationContext 继承,并控制 WinGuiMain。这是一个 Windows 窗体, 其中包含一个树视图(用于显示所订阅提要的列表,这些提要可按自由定义的类别分组)、一个列表视图(用于以您熟悉的任何 NNTP 阅读器(如 Outlook Express)中的线程模式,来显示有关当前选定提要中项目的信息),以及一个内嵌的 Web 浏览器(用于显示项目内容)。在启动时,RssBanditApplication 会确认是否有正在运行的程序实例。如果有,它会向该实例转发任一命令行参数并自行终止。如果没有正在运行的类实例,则 RssBanditApplication 会用 RssHandler 注册一个委托,RssHandler 可管理 RSS 提要的下载和处理。在下载新提要或更新后的提要后,RssBanditApplication 会使用 Safe, Simple Multithreading in Windows Forms, Part 1 文章(作者是 Chris Sells)中描述的方法,以线程安全方式通过委托进行更新。
RssBanditApplication 类还可充当多种用户界面组件(菜单、工具栏按钮、上下文菜单等)的中介,并可将操作委托给 WinGuiMain 和 RssHandler,或者在用户与应用程序交互时亲自处理它们。每个可代表用户启动操作的主要用户界面组件都可实现 ICommand 接口(命令模式)和 ICommandComponent 接口,以提取多个类的实现细节。
该用户界面还允许用户管理 RssHandler 类各个方面的行为。用户可以在订阅列表中添加和删除提要、配置提要的下载频率、按类别组织提要,以及设置代理服务器信息。RssItemFormatter 类可使用 XslTransform 类来处理新闻项的显示内容,它使用用户定义的 XSLT 样式表,并将实现 IXPathNavigable 接口的 RssItem 转换为 HTML。
XML 技术和 RSS Bandit
RSS Bandit 应用程序充分利用了 .NET 框架中的 XML 技术。RSS Bandit 可使用 XML 序列化在 XML 配置文件和对象之间进行转换;可使用 XSLT 来实现新闻项的可自定义视图;可使用 XPath 来处理 RSS 提要的 HTML 内容并删除可能有恶意的元素;可使用 System.Xml.XmlWriter 类来确保它编写格式正确的 XML,等等。
使用 XSLT 的可自定义主题
在 RSS Bandit 中阅读新闻项时,它们显示在该应用程序右下方的窗格中,此窗格实际上是一个嵌入式 Web 浏览器控件。使用嵌入式 Web 浏览器可以显示新闻提要的内容,初始版本的 RSS Bandit 并没有利用它所带来的灵活性。在当前版本的 RSS Bandit 中,用户可以创建一个 XSLT 样式表,以自定义新闻项在 Web 浏览器窗格中的显示方式。图 3 是一个配置菜单的屏幕快照,在这里,用户可以从 RSS Bandit 应用程序的模板文件夹中选择特定的样式表
下载的每个 RSS 提要都表示为一个包含 RssItem 对象列表的 FeedInfo 对象。RssItem 类可实现 IXPathNavigable 接口,这意味着它是 System.Xml.Xsl.XslTransform 类的 Transform 方法可接受的输入内容。在实现 IXPathNavigable 接口时,会将 RssItem 公开为 RSS 2.0 XML 提要,其中包含一个代表 RssItem 中数据的项目。在 Web 浏览器窗格中显示新闻项时,当前选定的 XSLT 样式表和 RssItem 实例会作为输入内容传递到 XslTransform 类的 Transform 方法中,该方法随后会在浏览器窗格中呈现转换结果。
由于大多数 RSS 提要在其内容中不使用 XHTML,而是喜欢使用纯文本或正规 HTML,因此有必要使用 Chris Lovett 的 SgmlReader 类来处理此类提要,SgmlReader 类可用于将 HTML 内容转换为 XHTML。
用 XSLT 导入提要列表
用于存储用户订阅的 RSS 提要列表的现有 XML 格式有好几种。这些格式包括 OPML、OCS 和我在上一篇文章中为 RSS Bandit 选择的格式。
尽 管 RSS Bandit 在内部适用于我的提要列表格式,但还可以导入 OPML 或 OCS 格式的提要列表。如果导入的提要列表不是 RSS Bandit 格式,则会检查它是否为 OPML 或 OCS 格式。如果提要列表采用这两种格式之一,则会针对导入的提要列表调用一个样式表,以便将特定格式转换为 RSS Bandit 提要列表格式。下面的样式表可将 OCS 文件转换为我的提要订阅列表格式:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.25hoursaday.com/2003/RSSBandit/feeds/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/metadata/dublin_core#"
exclude-result-prefixes="dc rdf">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<feeds>
<xsl:for-each select="/rdf:RDF/rdf:description/rdf:description">
<feed>
<title>
<xsl:choose>
<xsl:when test="dc:title">
<xsl:value-of select="dc:title" />
</xsl:when>
<xsl:otherwise>
<link>No title for RSS feed provided in imported OCS</link>
</xsl:otherwise>
</xsl:choose>
</title>
<link>
<xsl:choose>
<xsl:when test="rdf:description/@about">
<xsl:value-of select="rdf:description/@about" />
</xsl:when>
<xsl:otherwise>
<link>No URL for RSS feed provided in imported OCS</link>
</xsl:otherwise>
</xsl:choose>
</link>
</feed>
</xsl:for-each>
</feeds>
</xsl:template>
</xsl:stylesheet>
将导入的文件转换为 RSS Bandit 提要订阅列表格式之后,它会与在启动时处理的提要订阅列表的内部表示形式合并在一起。
配置文件和 W3C XML 架构
RSS Bandit 提要列表格式是使用 W3C XML 架构定义 (XSD) 文件进行描述的,该文件允许应用程序利用 .NET 框架的 XML 序列化功能将 XML 转换为强类型对象,以便在与提要列表格式的内容交互时提供更自然的编程模型。
RSS Bandit 的集成式搜索 功能也有一个 XML 配置文件格式。用户可以选择使用一个或多个搜索引擎,直接从 RSS Bandit 用户界面搜索 Web。在默认情况下,配置文件包含有关 Google、Feedster 和 MSN Search 的信息。用户还可以使用 XmlSerializer 类来处理搜索配置文件,XmlSerializer 类可将搜索配置文件转换为强类型对象的图形,以提供一个更自然的编程模型来与配置信息进行交互。下面是搜索配置文件的架构。
<xs:schema
targetNamespace='http://www.25hoursaday.com/2003/RSSBandit/searchConfiguration/'
xmlns:xs='http://www.w3.org/2001/XMLSchema' elementFormDefault='qualified'
xmlns:c='http://www.25hoursaday.com/2003/RSSBandit/searchConfiguration/'>
<xs:element name='searchConfiguration'>
<xs:complexType>
<xs:sequence>
<xs:element name='engine' minOccurs='0' maxOccurs='unbounded'>
<xs:complexType>
<xs:sequence>
<xs:element name='title' type='xs:string' />
<xs:element name='search-link' type='xs:anyURI'>
<xs:annotation>
<xs:documentation>
This defines the base URL of the search engine.
The placeholder for the search expression is '[PHRASE]' without
the single quotes but with the brackets!
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name='description' type='xs:string' />
<xs:element name='image-name' type='xs:string' />
</xs:sequence>
<xs:attribute name='active' type='xs:boolean' />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name='open-newtab' type='xs:boolean' use='optional' />
</xs:complexType>
</xs:element>
</xs:schema>
图 4 显示如何在 RSS Bandit 应用程序中使用搜索配置文件中的信息。
生成格式正确的 XML 和导出 OPML 文件
许多新闻集合器都使用 OPML 格式来存储提要列表信息,因此,该格式对于 RSS Bandit 的用户非常有利,因为它能够将其内部的提要列表导出到 OPML 文件中。在初始版本的 RSS Bandit 中,我用以下代码生成了数个可用于几个测试案例的 OPML 文件,但是很多用户在尝试此功能时失败。
StringBuilder sb = new StringBuilder("<opml>"n<body>"n");
if(_feedsTable != null){
foreach(feedsFeed f in _feedsTable.Values){
sb.AppendFormat("<outline title='{0}' xmlUrl='{1}'
/>"n", f.title, f.link);
}
}
sb.Append("</body>"n</opml>");
上述代码的问题在于,它同时处理了构造 XML 和串联文本值,尽管这是一个比较诱人的建议,但却是错误的。当用户在 RSS 提要的标题中使用被 XML 视为特殊的字符(如和符号 (&) 或单引号 ('))时,RSS Bandit 会生成格式有误的 XML。为了解决此问题,我决定使用专门针对编写 XML 而设计的 .NET 框架类 — XmlWriter 类。下面是为了使用 XmlWriter 类而重写的同一任务。
XmlTextWriter writer = new XmlTextWriter(feedStream,System.Text.Encoding.UTF8);
writer.WriteStartElement("opml");
writer.WriteStartElement("body");
if(_feedsTable != null) {
foreach(feedsFeed f in _feedsTable.Values) {
writer.WriteStartElement("outline");
writer.WriteAttributeString("title",f.title);
writer.WriteAttributeString("xmlUrl", f.link);
writer.WriteEndElement();
}
}
writer.WriteEndElement(); //close <body>
writer.WriteEndElement(); //close <opml>
writer.Flush();
writer.Close();
XPath 和 RSS Bandit:自动发现提要
在订阅 Web 站点的 RSS 提要时,主要困难之一就是发现 RSS 提要的位置。在 2002 年 8 月,Mark Pilgrim 曾描述了一个超自由主义 RSS 定位器的算法,该算法由以下几个步骤组成:
-
1给定 Web 站点的主要地址,下载主页并查找指向 RSS 提要的 LINK 元素。如果您找到的话,就使用它们。
-
2如 果该站点不支持通过 LINK 元素自动发现 RSS,请扫描主页上的所有链接,并智能地猜测它们中的哪个(或哪些)指向 RSS 提要。同一服务器上以 .rss、.rdf 或 .xml 结尾的地址链接是主要候选提要。下载其中的每个链接,并通过检查每个文件的初始内容来查看哪个提要实际上是 RSS 提要。
-
3如果不成功,请在同一服务器上查找地址中包含 rss、rdf 或 xml 的地址链接,查看它们是否为 RSS 文件。
-
4如果仍不成功,请按顺序重复以上两个步骤,但需要扩展搜索范围以包括外部服务器上的地址,这是因为许多 weblog 使用第三方服务来为它们的 Web 站点提供 RSS 提要。清除 127.0.0.1 地址,然后查看剩下的链接是否是 RSS 文件。
-
5如果仍不成功,请尝试使用 Syndic8。Syndic8 可跟踪多个站点内的成千上万个 RSS 提要,并提供一个以编程方式与它进行交互的 XML-RPC 接口。
RSS Bandit 使用 RssLocater 类来实现上面的自动发现过程。第一步涉及到下载 Web 站点并在其中搜索链接。作为一个 XML 的狂热支持者,我希望使用 XPath 在文档中搜索链接,但意识到这会非常困难,因为大多数站点都不是用基于 XML 的 XHTML 标记语言编写的,而是用与 XML 不兼容的旧版 HTML 编写的。在这种情况下,可以借助于 Chris Lovett 的 SgmlReader 类。SgmlReader 类可以读入 HTML 文档并将它表示为 XML 文档,随后可以使用 .NET 框架中的传统 XML API 来处理此 XML 文档。以下代码片段显示用户如何使用 XPath 来获取 HTML 文档中引用 RSS 提要的所有 LINK 元素:
SgmlReader reader = new SgmlReader();
reader.InputStream = new StreamReader(GetWebPage(url));
reader.Href = url;
reader.DocType= "HTML";
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.Load(reader);
ArrayList list = new ArrayList();
//<link rel="alternate" type="application/rss+xml" title="RSS" href="url/to/rss/file">
foreach(XmlNode node in doc.SelectNodes("//*[local-name()='link' and
@type='application/rss+xml' and @title='RSS']/@href")){
string url = ConvertToAbsoluteUrl(node.Value, node.BaseURI);
if(LooksLikeRssFeed(url)){
list.Add(url);
}
}
图 5 中的屏幕快照显示一个对话框,若在嵌入式 Web 浏览器中查看的站点是 MSDN 主页,则在单击 RSS Bandit 应用程序上的 Autodiscover Feeds 按钮时,该对话框会弹出。
图 6 中的屏幕快照显示在站点中成功找到 RSS 提要的结果。
使用 XPath 筛选可能有恶意的内容
如上所述,RSS 提要中的 HTML 内容会先转换为 XHTML,然后才显示在浏览器窗格中。如果不注意从 RSS 提要的 HTML 内容中去除可能有恶意的元素(如脚本块),可能会导致安全问题。由于 RSS 提要中的 HTML 内容是使用 Chris Lovett 的 SgmlReader 类转换为 XHTML 的,因此可以方便地使用 XPath 和 XmlDocument 类来去除不需要的标记。以下代码片段显示如何使用 XPath 从 RSS 提要的内容中筛选掉可能有恶意的元素和属性。
//remove potentially malicious tags
string badtagQuery = "//@style | //*[local-name()='script' or local-
name()='object'or local-name()='embed' or local-name()='iframe' or local-
name()='meta' or local-name()='frame'or local-name()='frameset' or local-
name()='link' or local-name()='style']";
foreach(XmlNode badtag in doc.SelectNodes(badtagQuery)){
XmlAttribute badattr = badtag as XmlAttribute;
if(badattr != null){
badattr.OwnerElement.Attributes.Remove(badattr);
}else{
badtag.ParentNode.RemoveChild(badtag);
}
}
从 RSS Bandit 张贴注释
我最初创建 RSS Bandit 的目的是将它用作跟踪经常访问的多个 weblog 的方法。在早些时候,我认为 weblog 的一个有趣方面就是它们的对话特性,特别是用户可以通过 Web 观看讨论场面的方式。RSS Bandit 有许多尝试利用 weblog 对话特性的功能。
如果特定 RSS 提要中的某个新闻项引用 RSS Bandit 中的其他新闻项,或被其他新闻项引用,则这种关系会显示在用户界面中,作为电子邮件和新闻阅读器的线程消息回放。这提供了一个非常直观的机制,用于在用户 订阅的 weblog 上跟踪讨论,这是因为互相引用的帖子会显示在一起。RSS Bandit 还提供了多种方法,与为响应特定新闻项而张贴的注释进行交互,具体方法取决于其 RSS 提要中提供的信息。用于提供有关新闻项注释信息的 RSS 元素有许多,其中包括 comment 元素(它提供一个链接,用户可以在此处于用户界面中张贴新闻项注释)、slash:comments 元素(用于指出为响应新闻项而张贴的注释数量)、wfw:commentRss 元素(为新闻项注释提供 RSS 提要的位置)和 wfw:comment 元素(它提供的 URI 可接受作为新闻项答复来发送的 RSS 项,新闻项使用的是 HTTP POST)。
RSS Bandit 支持上面的元素,这些元素提供有关特定新闻项注释的信息。下面的图 7 是 RSS Bandit 的屏幕快照,它显示全部四个与注释有关的使用中 RSS 元素。
将注释张贴到 RSS Bandit 的机制是 CommentAPI,CommentAPI 指定应用程序如何通过将RSS 项张贴到特定 URI 来发送对 RSS 提要中新闻项的响应。以下代码片段显示 RSS Bandit 如何使用 HTTP POST 来发送对 RSS 提要中新闻项的答复。
public HttpStatusCode PostCommentViaCommentAPI(string url, RssItem item){
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.Timeout = 1 * 60 * 1000; //one minute timeout
request.UserAgent = this.UserAgent;
request.Proxy = this.Proxy;
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "POST";
request.ContentType = "text/xml";
string comment = item.ToString(true);
request.ContentLength = comment.Length;
StreamWriter myWriter = null;
try{
myWriter = new StreamWriter(request.GetRequestStream());
Trace.WriteLine(comment);
myWriter.Write(comment);
} catch(Exception e){
throw new WebException(e.Message, e);
}finally{
if(myWriter != null){
myWriter.Close();
}
}
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
return response.StatusCode;
}
应注意的是,对 CommentAPI 的支持尚不普及。CommentAPI 实现列表中提供了支持 CommentAPI 的 Web 站点列表。一个值得注意的 CommentAPI 支持程序是 .Text blog 引擎,Weblogs @ ASP.NET 和 Weblogs @ DotNetJunkies.com 均使用该引擎。
RSS Bandit 有一个虚拟文件夹,其中存储有通过图 8 所示的 CommentAPI 张贴的所有注释。
插件结构
在最近的一个名为在 CLR 中传递 XML 数据的 MSDN TV 情节中,Don Box 描述了在 .NET 框架的应用程序之间传递 XML 的各种机制。用户可以将许多类型选作 .NET 框架中 XML 文档的表示形式,如 String、IXPathNavigable、XmlDocument 或 XmlReader 类的实例,它们各有利弊。最近,Simon Fell 建议将 IBlogExtension 接口用作基于 .NET 框架构建的新闻集合器的通用机制,以便与插件共享信息,并选择 IXPathNavigable 接口作为在新闻集合器和插件之间传递 XML(特别是 RSS 项)的方法。
RSS Bandit 支持 IBlogExtension 接口,因此允许开发人员构建与 RSS Bandit 集成的插件。图 9 中的屏幕快照显示与 w.bloggar 插件(位于 http://www.sharpreader.net/wBloggarPlugin.zip 上,由 Luke Hutteman 编写)的集成,该插件使用户能够使用流行的 w.bloggar weblog 编辑器在他们的 weblog 中张贴特定新闻项。
RSS Bandit 的未来规划
自从我发表上一篇文章以来,RSS Bandit 已经得到突飞猛进的发展,这主要是由于我和 Torsten Rendelmann 的不懈努力以及 RSS Bandit 工作区中许多人的帮助。我打算继续与 .NET 开发人员社区中的各位成员一起开发 RSS Bandit,并一直将它用作显示 .NET 框架功能的方法,以及构建利用 XML 强大功能的丰富客户端应用程序的平台。
在下一版本的 RSS Bandit 中,我希望看到几个功能,如使用 更新程序块进行自动更新、新闻项选择的类似于报纸的视图,以及从 RSS Bandit 直接编辑用户的 weblog 的功能。如果您愿意协助我们向 RSS Bandit 中添加这些功能或其他功能,欢迎您加入该工作区并提供帮助。您的帮助对我们总是有用的。
Dare Obasanjo 是 Microsoft 的 WebData 组的成员,该小组在 .NET 框架的 System.Xml 和 System.Data 命名空间、Microsoft XML 核心服务 (MSXML) 和 Microsoft 数据访问组件 (MDAC) 中开发组件。
有关本文的任何问题或评论,欢迎张贴到 GotDotNet 上的 Extreme XML 留言板。