修改 RSS Bandit 应用程序

 

Dare Obasanjo
Microsoft Corporation

2003 年 9 月 15 日

总结:Dare Obasanjo 重新访问他的 RSS Bandit 项目,该项目是一个 C# 应用程序,用于从各种网站检索和显示新闻源,并使用.NET Framework的各种 XML 功能对其进行改进,以生成丰富的 .NET 客户端应用程序。 (16 个打印页)

下载 RSSBandit Installer.msi示例文件

简介

上一篇文章 中,我介绍了 RSS Bandit 应用程序的内部工作原理,该应用程序通过处理 Web 上的 RSS 源来聚合来自各种网站的信息。 如上一篇文章中所述,RSS 是一种 XML 格式,用于从在线新闻源联合新闻和类似内容。

RSS 源是一个定期更新的 XML 文档,其中包含有关新闻源和其中内容的元数据。 RSS 源至少由表示新闻源的频道组成,该 频道 具有描述新闻源的标题、链接和说明。 此外,RSS 源通常包含一个或多个 项目 元素,这些元素表示单个新闻项,其中每个项目应具有标题、链接或说明。

自从撰写我的文章以来,RSS 作为一种在网络上传播新闻的机制,传播范围越来越广。 RSS 源不仅被在线新闻来源(如 雅虎新闻BBC滚石杂志)使用,而且在以开发人员为中心的信息源(如 Microsoft 开发人员网络 (MSDN) Oracle 技术网络 (OTN) 太阳开发人员网络)中也广受欢迎。 随着 RSS 源的激增,桌面新闻聚合器是一个功能强大的工具,适合有兴趣随时了解各种新闻源的信息,而无需浏览多个网站来获取其信息修复。

在过去的几个月里,GotDotNet 上的 RSS Bandit 工作区非常活跃,.NET Framework开发人员社区的各个成员(如 Torsten RendelmannMichael EarlsJoe FeserRSS Bandit 工作区的其他成员)做出了贡献。 本文介绍过去几个月 RSS Bandit 应用程序的各种新增内容的内部工作原理。

查看 RSS Bandit 用户界面

RSS Bandit 的用户界面受到邮件和新闻阅读器(如 Microsoft Outlook 和 Microsoft Outlook® Express)的启发。 改进的用户界面是当前版本的 RSS Bandit 和我上一篇文章中的版本之间最明显的区别。 RSS Bandit 使用 Magic 库,这是用户界面控件的框架,提供比基本Windows 窗体控件更丰富的功能。

单击获取较大图像

图 1. 使用 RSS 强盗阅读新闻

Magic 库提供的强大功能之一是能够创建选项卡式窗格,允许在单个应用程序中嵌套多个窗体。 下面的图 2 显示了 Magic 库中的选项卡式窗格如何使用户能够利用 RSS 强盗中的多个 Web 浏览器窗口。

单击获取较大图像

图 2. 使用 RSS Bandit 浏览 Web

RSS Bandit 体系结构概述

RSS Bandit 应用程序由两个不同的部分组成: 图形用户界面组件 以及 XML 和网络组件。 主要 GUI 类是 WinGuiMainRssBanditApplication 类,而主 XML 和网络类是 RssHandlerRssLocater

RssHandler 类按指定的间隔下载 RSS 源,并将其交给存储它们的 CacheManagerCacheManager 使用的存储不与应用程序紧密耦合,事实上,CacheManager 是一个抽象类,它当前具有一个具体实现 FileCacheManager,用于在本地文件系统上缓存文件。 这种灵活性意味着,将来可以引入使用更好、更优化的存储(例如数据库管理系统)的新类型的 CacheManager 。 同样, RssHandler 类不与用户界面紧密耦合,可由需要处理 RSS 源的其他应用程序重复使用。 使用 RssHandler 类的客户端在实例化类时注册回调 (委托) 。 然后,当下载新的或更新的源时, RssHandler 对象会调用已注册的回调。 要下载的源和其他配置数据的信息是从用 XML 编写的源订阅列表获取的。 在尝试发现特定网站的 RSS 源时,将使用 RssLocater ,并在尝试查找源时使用一组定义完善的启发式方法。

RssBanditApplication 继承自 ApplicationContext 并控制 WinGuiMain。 这是一个 Windows 窗体 ,其中包含用于显示按免费可定义类别分组的订阅源列表的树视图、一个列表视图,用于以您熟悉的任何 NNTP 阅读器 ((如 Outlook Express) )的线程方式显示当前所选源中有关项目的信息的列表视图,以及用于显示项目内容的嵌入式 Web 浏览器。 启动时, RssBanditApplication 会确定程序是否有正在运行的实例。 如果是这样,它将任何命令行参数转发到该实例并终止自身。 如果类的实例未运行,则 RssBanditApplication 会向 RssHandler 注册委托,后者管理 RSS 源的下载和处理。 每当下载新的或更新的源时,RssBanditApplication 都会使用 Chris Sells 的《Windows 窗体中的安全、简单多线程处理》第 1 部分文章中所述的技术,以线程安全的方式通过委托进行更新。

RssBanditApplication 类还充当各种用户界面组件的中介程序, (菜单、工具栏按钮、上下文菜单等) 并将操作委托给 WinGuiMainRssHandler,或在用户与应用程序交互时自行处理它们。 可以代表用户启动操作的每个主用户界面组件实现 ICommand 接口 (命令模式) 和 ICommandComponent 接口,以抽象化各种类的实现详细信息

用户界面还允许用户管理 RssHandler 类行为的各个方面。 用户可以在订阅列表中添加和删除源、配置应下载源的频率、按类别组织源,以及设置代理服务器信息。 RssItemFormatter 类使用 XslTransform 类处理显示新闻项的内容。 它采用用户定义的 XSLT 样式表,并将实现 IXPathNavigable 接口的 RssItem 转换为 HTML。

XML 技术和 RSS Bandit

RSS Bandit 应用程序在.NET Framework中大量使用 XML 技术。 RSS Bandit 使用 XML 序列化将 XML 配置文件转换为对象,反之亦然,XSLT 用于启用可自定义的新闻项目视图,使用 XPath 处理 RSS 源的 HTML 内容并删除潜在的恶意元素,以及 System.Xml。XmlWriter 类,以确保它写出格式正确的 XML 等。

使用 XSLT 的可自定义主题

在 RSS Bandit 中读取新闻项目时,它们将显示在应用程序右下角的窗格中,该窗格实际上是一个嵌入的 Web 浏览器控件。 RSS Bandit 的原始版本没有利用通过使用嵌入式 Web 浏览器来显示新闻源内容而获得的灵活性。 在当前版本的 RSS Bandit 中,可以创建一个 XSLT 样式表 ,用于自定义新闻项在 Web 浏览器窗格中的显示方式。 图 3 是配置菜单的屏幕截图,可在其中从 RSS Bandit 应用程序的 templates 文件夹中选择特定的样式表

图 3. 选择自定义样式表

每个下载的 RSS 源都表示为包含 RssItem 对象列表的 FeedInfo 对象。 RssItem 类实现 IXPathNavigable 接口,这意味着它是System.Xml Transform 方法的可接受输入。Xsl.XslTransform 类IXPathNavigable 接口的实现将 RssItem 公开为 RSS 2.0 XML 源,其中包含表示 RssItem 内数据的单个项。 在 Web 浏览器窗格中显示新闻项时,当前选择的 XSLT 样式表和 RssItem 实例将作为输入传递到 XslTransform 类的 Transform 方法,后者随后在浏览器窗格中呈现转换结果。

由于大多数 RSS 源在其内容中使用 XHTML,而是倾向于纯文本或常规 HTML,因此有必要使用 Chris Lovett 的 SgmlReader 类来处理此类源,该类可用于将 HTML 内容转换为 XHTML。

使用 XSLT 导入源列表

有几种现有的 XML 格式用于存储用户订阅的 RSS 源列表。 这些格式包括 OPMLOCS 以及我在 上一篇文章中为 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 Framework的 XML 序列化功能将 XML 转换为强类型化对象,从而在与源列表格式的内容交互时提供更自然的编程模型。

RSS Bandit 的 集成搜索 功能还有一种 XML 配置文件格式。 可以直接从 RSS Bandit 用户界面使用一个或多个搜索引擎来搜索 Web。 默认情况下,配置文件包含有关 GoogleFeedsterMSN 搜索的信息。 搜索配置文件也使用 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 应用程序中使用搜索配置文件中的信息。

单击获取较大图像

图 4。 从 RSS Bandit 搜索 Web

生成格式正确的 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 Bandit 在 RSS 源的标题中使用被视为 XML 特殊字符(如与 () & 或单引号 (') 时,会生成格式不正确的 XML。 为了解决此问题,我决定使用专为写出 XML 而设计的 .NET Framework 类 - 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:自动发现源

订阅网站的 RSS 源的主要困难之一是发现其 RSS 源的位置。 2002 年 8 月,Mark Pilgrim 描述了 超自由 RSS 定位器的算法 ,包括以下步骤:

  1. 给定网站的main地址,请下载主页并查找指向 RSS 源 的 LINK 元素。 如果找到任何,请使用它们。
  2. 如果站点不支持通过 LINK 元素自动发现 RSS,请扫描页面上的所有链接,并智能地猜测其中哪一个 () 指向 RSS 源。 指向以 .rss、.rdf 或 .xml 结尾的同一服务器上的地址的链接是作为源的主要候选项。 下载其中的每一个,并通过检查每个文件的初始内容来查看哪些是 RSS 源。
  3. 如果不成功,请在地址中的任何位置查找指向同一服务器上包含 rss、rdf 或 xml 的地址的链接。 检查其中是否有一个是 RSS 文件。
  4. 如果仍然不成功,请按顺序重复前两个步骤,但扩展搜索以包括外部服务器上的地址,因为许多 Weblog 使用第三方服务为其网站提供 RSS 源。 清除了 127.0.0.1 个地址,然后检查,看看是否有剩余的 RSS 文件。
  5. 如果仍然不成功,请查看 Syndic8。 Syndic8 跟踪各种站点的数千个 RSS 源,并提供用于以编程方式与其交互的 XML-RPC 接口

RSS Bandit 使用 RssLocater 类实现上述自动发现过程。 第一步涉及下载网站并搜索链接。 作为 XML 爱好者,我想使用 XPath 在文档中搜索链接,但意识到这很困难,因为大多数网站不是使用基于 XML 的 XHTML 标记语言编写的,而是使用与 XML 不兼容的 HTML 早期版本。 这是 克里斯·洛维特的SgmlReader类 来拯救的地方。 SgmlReader 类可以在 HTML 文档中读取并将其呈现为 XML 文档,然后可以使用.NET Framework中的传统 XML API 进行处理。 以下代码片段演示如何使用 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 应用程序上的 “自动发现源 ”按钮时弹出的对话框。

图 5。 启动源自动发现

图 6 中的屏幕截图显示了成功尝试查找站点的 RSS 源的结果。

图 6。 源 () 位置

使用 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 强盗的评论

我最初创建了 RSS 强盗, 作为跟踪我定期访问的各种博客的方法。 早些时候,我意识到,网络博客的有趣方面之一是它们的对话性质,尤其是人们watch讨论的方式波及整个网络。 RSS Bandit 具有许多功能,试图利用网络博客的对话性质。

如果来自特定 RSS 源的新闻项引用或由 RSS Bandit 中的其他新闻项引用,则此类关系在用户界面中显示为线程消息,让人想起电子邮件和新闻阅读器。 这提供了一个很好的视觉机制来跟踪订阅的博客中的讨论,因为相互引用的帖子一起显示。 RSS Bandit 还提供各种方法来与为响应特定新闻项而发布的评论进行交互,具体取决于其 RSS 源中提供的信息。 有大量 RSS 元素用于向新闻项提供有关评论的信息- comment 元素提供一个链接,指向用户可以在用户界面中向新闻项发布评论的链接, slash:comments 元素用于指示为响应新闻项而发布的评论数, wfw:commentRss 元素为新闻项的评论提供 RSS 源的位置,而 wfw:comment 元素提供 URI,该 URI 接受使用 HTTP post 作为对新闻项目的回复发送的 RSS 项目。

RSS Bandit 支持上述元素,这些元素提供有关特定新闻项的评论信息。 下面的图 7 是 RSS Bandit 的屏幕截图,其中显示了正在使用的所有四个与注释相关的 RSS 元素。

单击以获取更大的图像

图 7。 阅读和发布 RSS 强盗的评论

将评论发布到 RSS Bandit 的机制是 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 的网站 列表CommentAPI 的一个值得注意的支持者是 Weblog @ ASP.NETweblogs @ DotNetJunkies.com 使用的文本博客引擎。

RSS Bandit 有一个 虚拟文件夹 ,用于存储通过 CommentAPI 发布的所有评论,如图 8 所示。

单击以获取更大的图像

图 8。 “已发送邮件”文件夹

插件体系结构

在最近一集名为在 CLR 中传递 XML 数据的 MSDN 电视中,Don Box 介绍了在.NET Framework中的应用程序之间传递 XML 的各种机制。 在.NET Framework中,可以选择许多类型作为 XML 文档的表示形式,例如 StringIXPathNavigableXmlDocumentXmlReader 类的实例,每种类都有其利弊。 最近,Simon Fell 提出了 IBlogExtension 接口,作为基于.NET Framework与插件共享信息的新闻聚合器的通用机制,并选择 IXPathNavigable 接口作为在新闻聚合器和插件之间传递 XML (特别是 RSS 项) 的方法。

RSS Bandit 支持 IBlogExtension 接口,从而允许开发人员生成与 RSS Bandit 集成的插件。 图 9 是显示 Luke Hutteman 撰写的 http://www.sharpreader.net/wBloggarPlugin.zip w.bloggar 插件集成屏幕截图,该插件允许用户使用常用的 w.bloggar weblog 编辑器在其博客中发布有关特定新闻项目的功能。

单击以获取更大的图像

图 9. 从 RSS Bandit 发布到您的博客

RSS 强盗的未来计划

自从我上一篇文章以来,RSS 强盗取得了长足的发展,这主要是由于我自己和 托斯滕·伦德尔曼的努力,在 RSS 强盗工作区的一些人的帮助下。 我计划继续开发 RSS Bandit 以及 .NET 开发人员社区的各个成员,并继续使用它作为展示.NET Framework功能的平台,以构建利用 XML 的强大功能的客户端应用程序。

我想在 RSS Bandit 的下一个主要版本中看到一些功能,例如使用 Updater 应用程序块进行自动更新、一系列新闻的类似报纸的视图,以及直接从 RSS Bandit 编辑个人博客的功能。 如果想要帮助将这些功能或其他功能添加到 RSS Bandit,请随时加入工作区并提供帮助。我们始终可以使用帮助。

Dare Obasanjo 是 Microsoft WebData 团队的成员,除其他事项外,该团队还开发 .NET Framework、Microsoft XML Core Services (MSXML) 的 System.Xml 和 System.Data 命名空间中的组件,以及 Microsoft 数据访问组件 (MDAC) 。

可随时在 GotDotNet 上的 Extreme XML 消息板上 发布有关本文的任何问题或评论。