65.9K
CodeProject 正在变化。 阅读更多。
Home

真正最简单的联合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (12投票s)

2005年11月24日

5分钟阅读

viewsIcon

38092

downloadIcon

443

本文介绍了一个处理所有类型 RSS 供稿的 XSLT/JScript 框架,以及基于此框架构建的 HTML 新闻阅读器应用程序。

关于什么

近年来,RSS 已被证明是一种非常有用的数据分发技术。本文解决了在单个应用程序中处理不同标准 RSS 供稿的问题。对于任何构建自己的桌面聚合器或企业内部网环境的人来说,它都可能很有用。本文附带了一个新闻阅读器应用程序的骨架。

本文假定您使用 MSXML 3.0+ 作为 XML/XSLT 处理器。

标准化

RSS 唯一令人遗憾的是使用标准数量众多。您无法确定在浏览网络时会得到什么,因此您必须做好应对任何情况的准备。“任何情况”是

  • RSS 0.90 - Netscape 创建的 RSS 技术最初版本,现在几乎已经绝迹。规范仍可在 PurplePages 存档找到。
  • RSS 2.0/0.91-0.94 - 最流行的 RSS 分支。由 UserLand 的 Dave Winer 修改和简化(最初版本)而来。对于此格式,RSS 代表 Really Simple Syndication(真正简单的联合供稿)。随着 播客的引入,它变得更加流行。顺便说一句,不要被版本号迷惑:2.0 版本之前的版本是 0.94,而不是 1.0(这与 0.9x 完全不同)!规范可在 UserLand 站点中找到。
  • RSS 1.0 - 严格来说并非标准,而是 RDF(Resource Description Framework,资源描述框架)的派生——由 W3C 开发的 Web 元数据标准。它冗长且可扩展(通过使用模块),比 v2.0 灵活得多。对于 0.90 和 1.0 版本,RSS 代表 RDF Site Summary(RDF 站点摘要)。规范可在 RSS-DEV 工作组的站点找到。
  • Atom - 最新、因此也是最罕见的联合供稿格式。Atom 是(由 Internet Engineering Task Force 工作组)首次尝试开发的标准化、企业级联合供稿格式。完整的规范可在 IETF 站点找到:完整规范

转换...

我们先从样式表开始。有三点值得注意

  • local-name() XSLT 函数:当您需要去除所有命名空间内容以轻松获取节点名称时,它非常有用。
  • xsl:value-of 指令的 disable-output-escaping 选项:一个必备的 XSLT 元素。原因:网络大师倾向于在“description”和“summary”字段中嵌入有趣的 HTML 标记。通过将此选项设置为“yes”,我们可以保留标记,从而获得漂亮的页面,而不是一堆乱七八糟的标签。缺点是我们有一个安全问题:如果 disable-output-escaping 中嵌入了恶意脚本,它可能会使您的本地计算机暴露于风险。通常,您需要某种类型的脚本 <SCRIPT><OBJECT> 标签的清理程序;如果没有,建议您仅从受信任的站点阅读 RSS 供稿。
  • <xsl:text/> 指令:当您想手动去除不必要的空格时使用。这对于控制输出 HTML 代码的缩进非常有帮助。

前三个样式表可用于构建报纸风格的新闻供稿

列表 1.1:用于从 RSS 2.0/0.91 供稿构建报纸风格 HTML 页面的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" version="1.0" 
      indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body>
      <div style=
         "padding: 1em;background-color: #fafafa; border: 1px solid #cfcfcf;">
        <xsl:for-each select="rss/channel/item">
          <xsl:variable name="stl">
            <xsl:text/>background-color: #efeff5; 
                border: 1px solid #cfcfcf;padding: 0em 1em 0em; margin:
            <xsl:text/>
            <xsl:choose>
              <xsl:when test="position()=last()"> 0em</xsl:when>
              <xsl:otherwise> 0em 0em 1em 0em</xsl:otherwise>
            </xsl:choose>
          </xsl:variable>
          <div>
            <xsl:attribute name="style"><xsl:value-of select="$stl"/>
            </xsl:attribute>
            <p><h3 style="color:#800000"><xsl:value-of select="title"/></h3>
            </p>
            <p><xsl:value-of disable-output-escaping="yes" 
                                               select="description"/>
            </p>
            <xsl:variable name="pub" select="pubDate"/>
            <xsl:if test="count($pub) > 0">
              <p align="right" 
                  style="margin:0; padding:0"><xsl:value-of select="pubDate"/>
              </p>
            </xsl:if>
            <p style="margin:0; padding:0em 0em 1em 0em"><a target="_blank">
              <xsl:attribute name="href">
                <xsl:value-of select="link"/>
              </xsl:attribute>
              <xsl:value-of select="link"/>
            </a></p>
          </div>
        </xsl:for-each>
      </div>
    </body></html>
  </xsl:template>
</xsl:stylesheet>

列表 1.2:用于从 RSS 1.0 供稿构建报纸风格 HTML 页面的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
      version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body>
      <div style=
         "padding: 1em;background-color: #fafafa; border: 1px solid #cfcfcf;">
        <xsl:for-each select="*/*[local-name()='item']">
          <xsl:variable name="stl">
            <xsl:text/>background-color: #efeff5; 
               border: 1px solid #cfcfcf;padding: 0em 1em 0em; margin:
            <xsl:text/>
            <xsl:choose>
              <xsl:when test="position()=last()"> 0em</xsl:when>
              <xsl:otherwise> 0em 0em 1em 0em</xsl:otherwise>
            </xsl:choose>
          </xsl:variable>
          <div>
            <xsl:attribute name="style"><xsl:value-of select="$stl"/>
            </xsl:attribute>
            <p><h3 style="color:#800000">
                <xsl:value-of select="./*[local-name()='title']"/>
            </h3></p>
            <p><xsl:value-of disable-output-escaping="yes" 
                select="./*[local-name()='description']"/>
            <br/><br/>
            <xsl:variable name="pub" select="*[local-name()='date']"/>
            <xsl:variable name="pub_date" 
                select="concat(substring($pub, 0, 11), ', ', 
                               substring($pub, 12, 8), ' (GMT+',  
                               substring($pub, 21, 5), ')')"/>
            <xsl:if test="count($pub) > 0">
              <div align="right" style="margin:0em; padding:0em 0em 0em 0em;">
              <xsl:value-of select="$pub_date"/></div>
            </xsl:if>
            <a target="_blank">
              <xsl:attribute name="href">
                <xsl:value-of select="./*[local-name()='link']"/>
              </xsl:attribute><xsl:value-of select="./*[local-name()='link']"/>
            </a></p>
          </div>
        </xsl:for-each>
      </div>
    </body></html>
  </xsl:template>
</xsl:stylesheet>

列表 1.3:用于从 Atom 供稿构建报纸风格 HTML 页面的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
      version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body>
      <div style=
         "padding: 1em;background-color: #fafafa; border: 1px solid #cfcfcf;">
        <xsl:for-each select="*/*[local-name()='entry']">
          <xsl:variable name="stl">
            <xsl:text/>
              background-color: #efeff5; 
              border: 1px solid #cfcfcf;padding: 0em 1em 0em; margin:
            <xsl:text/>
            <xsl:choose>
              <xsl:when test="position()=last()"> 0em</xsl:when>
              <xsl:otherwise> 0em 0em 1em 0em</xsl:otherwise>
            </xsl:choose>
          </xsl:variable>
          <div>
            <xsl:attribute name="style"><xsl:value-of select="$stl"/>
            </xsl:attribute>
            <p><h3 style="color:maroon">
              <xsl:value-of select="*[local-name()='title']"/></h3></p>
            <p><xsl:value-of disable-output-escaping="yes" 
              select="*[local-name()='summary']"/></p>
            <xsl:variable name="pub" select="*[local-name()='updated']"/>
            <xsl:variable name="pub_date" select=
              "concat(substring($pub, 0, 11), ', ', substring($pub, 12, 8))"/>
            <xsl:if test="count($pub)>0">
              <p align="right" style="margin:0; padding:0;">
                <xsl:value-of select="$pub_date"/>
              </p>
            </xsl:if>
            <p style="margin:0; padding:0em 0em 1em 0em;"><a target="_blank">
              <xsl:attribute name="href">
                <xsl:value-of 
                  select="*[local-name()='link']/@*[local-name()='href']"/>
              </xsl:attribute>
              <xsl:value-of 
                select="*[local-name()='link']/@*[local-name()='href']"/>
            </a></p>
          </div>
        </xsl:for-each>
      </div>
    </body></html>
  </xsl:template>
</xsl:stylesheet>

下一个感兴趣的点是供稿中找到的所有标题列表——大纲。此列表中的每个项都将是一个指向 JavaScript “navTo”函数的链接,其数字参数等于该项在列表中的位置。

列表 2.1:用于从 RSS 2.0/0.91 供稿检索项目列表的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
    version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body><ul style="margin-left:25">
      <xsl:for-each select="rss/channel/item">
        <li><a href="javascript:navTo('{position()}')">
          <font style="size:-1;color:#800000">
            <xsl:value-of select="title"/>
          </font>
        </a><br/></li>
      </xsl:for-each>
    </ul></body></html>
  </xsl:template>
</xsl:stylesheet>

列表 2.2:用于从 RSS 1.0 供稿检索项目列表的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" version="1.0" 
              indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body><ul style="margin-left:25">
      <xsl:for-each select="*/*[local-name()='item']">
        <li><a href="javascript:navTo('{position()}')">
          <font style="size:-1;color:#800000">
            <xsl:value-of select="./*[local-name()='title']/text()"/>
          </font>
        </a><br/></li>
      </xsl:for-each>
    </ul></body></html>
  </xsl:template>
</xsl:stylesheet>

列表 2.3:用于从 Atom 供稿检索项目列表的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
    version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="/">
    <html><body><ul style="margin-left:25">
      <xsl:for-each select="*/*[local-name()='entry']">
        <li><a href="javascript:navTo('{position()}')">
          <font style="size:-1;color:#800000">
            <xsl:value-of select="./*[local-name()='title']/text()"/>
          </font>
        </a><br/></li>
      </xsl:for-each>
    </ul></body></html>
  </xsl:template>
</xsl:stylesheet>

最后一组样式表负责转换单个新闻项。请注意,这些转换不能应用于原始 RSS 文件;在使用它们之前,您必须以编程方式提取所需项,并将其应用于其中一个样式表。

列表 3.1:用于表示 RSS 2.0/0.91 供稿中独立新闻项的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
    version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="*">
    <div style="padding: 0em 1em 0em;
      background-color: #fafafa; border: 1px solid #cfcfcf;">
      <p><h3 style="color:#800000"><xsl:value-of select="title"/></h3></p>
      <div style="padding: 0em 1em 0em; margin: 0em; 
        background-color: #efeff5; border: 1px solid #cfcfcf;">
        <p><xsl:value-of disable-output-escaping="yes" select="description"/>
        </p>
        <xsl:variable name="pub" select="pubDate"/>
        <xsl:if test="count($pub)>0">
          <p align="right" style="margin:0em; padding:0em 0em 1em 0em;">
          <xsl:value-of select="pubDate"/></p>
        </xsl:if>
      </div>
      <p><a target="_blank">
        <xsl:attribute name="href">
          <xsl:value-of select="link"/>
        </xsl:attribute>
        <xsl:value-of select="link"/>
      </a></p>
    </div>
  </xsl:template>
</xsl:stylesheet>

列表 3.2:用于表示 RSS 1.0 供稿中独立新闻项的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
    version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="*">
    <div style="padding: 0em 1em 0em;
      background-color: #fafafa; border: 1px solid #cfcfcf;">
      <p><h3 style="color:#800000">
        <xsl:value-of select="./*[local-name()='title']"/>
      </h3></p>
      <div style="padding: 0em 1em 0em; margin: 0em; 
        background-color: #efeff5; border: 1px solid #cfcfcf;">
        <p><xsl:value-of disable-output-escaping="yes" 
            select="./*[local-name()='description']"/></p>
        <xsl:variable name="pub" select="*[local-name()='date']"/>
        <xsl:variable name="pub_date" 
            select="concat(substring($pub, 0, 11), ', ', 
                        substring($pub, 12, 8), 
                        ' (GMT+',  substring($pub, 21, 5), ')')"/>
        <xsl:if test="count($pub) > 0">
          <p align="right" style="margin:0em; padding:0em 0em 1em 0em;">
              <xsl:value-of select="$pub_date"/>
          </p>
        </xsl:if>
      </div>
      <p><a target="_blank">
        <xsl:attribute name="href">
          <xsl:value-of select="./*[local-name()='link']"/>
        </xsl:attribute><xsl:value-of select="./*[local-name()='link']"/>
      </a></p>
    </div>
  </xsl:template>
</xsl:stylesheet>

列表 3.3:用于表示 Atom 供稿中独立新闻项的 XSLT 样式表

<?xml version="1.0"?>
  <xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" 
    version="1.0" indent="yes" encoding="iso-8859-1"/>
  <xsl:template match="*">
    <div style="padding: 0em 1em 0em;
       background-color: #fafafa; border: 1px solid #cfcfcf;">
      <p><h3 style="color:#800000">
          <xsl:value-of select="*[local-name()='title']"/>
      </h3></p>
      <xsl:variable name="cnt" select="*[local-name()='content']">
      </xsl:variable>
      <xsl:if test="count($cnt)>0">
        <div style="padding: 0em 1em 0em 1em; margin: 0em; 
          background-color: #efeff5; border: 1px solid #cfcfcf;">
          <p><xsl:value-of disable-output-escaping="yes" 
                   select="*[local-name()='content']"/></p>
          <xsl:variable name="pub" select="*[local-name()='updated']"/>
          <xsl:variable name="pub_date" 
             select="concat(substring($pub, 0, 11), ', ', 
                                 substring($pub, 12, 8))"/>
          <xsl:if test="count($pub)>0">
            <p align="right" style="margin:0em; padding:0em 0em 1em 0em;">
                <xsl:value-of select="$pub_date"/>
            </p>
          </xsl:if>
        </div>
      </xsl:if>
      <xsl:if test="count($cnt)=0">
        <p style="padding: 1em; margin: 0em; 
          background-color: #efeff5; border: 1px solid #cfcfcf;">
          <xsl:value-of disable-output-escaping="yes" 
                             select="*[local-name()='summary']"/>
        </p>
      </xsl:if>
      <p><a target="_blank">
        <xsl:attribute name="href">
          <xsl:value-of 
            select="*[local-name()='link']/@*[local-name()='href']"/>
        </xsl:attribute>
        <xsl:value-of 
          select="*[local-name()='link']/@*[local-name()='href']"/>
      </a></p>
    </div>
  </xsl:template>
</xsl:stylesheet>

……以及阅读

在对 RSS 文件进行任何操作之前,您需要知道它属于哪个标准,对吗?我们通过分析 <xml-stylesheet> 的子节点来做到这一点。

列表 4.1:提取 RSS 标准

function whatStd(rssdocument)
{
    var rssroot = 
        rssdocument.documentElement.selectSingleNode("/*");
    var rsssdtd = rssroot.baseName;

    switch(rsssdtd)
    {
      case "rss":
        return "rss2";

      case "RDF":
        return "rss1";

      case "feed":
        return "atom";

      default:
        return "";
    }
}

这段代码(以及以下所有代码)的坏处是它大量使用了 Microsoft 对 W3C XML API 的扩展。作为解决方案,您可以简单地提取 DocumentElementfirstChild

列表 4.2:提取 RSS 频道信息

var rss_title;
switch(standard)
{
case "atom":
  rss_title = xml.documentElement.selectSingleNode(
                         "/*/*[local-name()='title']");
  break;

case "rss1":
  rss_title = xml.documentElement.selectSingleNode(
      "/*/*[local-name()='channel']/*[local-name()='title']");
  break;

case "rss2":
  rss_title = xml.documentElement.selectSingleNode(
                                   "/*/channel/title");
  break;
}

var rss_link;
switch(standard)
{
case "atom":
  rss_link = xml.documentElement.selectSingleNode(
          "/*/*[local-name()='link']/@*[local-name()='href']");
  break;

case "rss1":
  rss_link = xml.documentElement.selectSingleNode(
        "/*/*[local-name()='channel']/*[local-name()='link']");
  break;

case "rss2":
  rss_link = xml.documentElement.selectSingleNode(
                                            "/*/channel/link");
  break;
}

rsstitle.innerHTML = 
    "<a target=\"_blank\" title=\"Opens in new window\" href=\"" + 
    rss_link.text + 
    "\"><font color=\"maroon\" size=\"4\"><b>" + 
    rss_title.text + "</b></font></a>";

提取了频道信息后,提取单个供稿项将非常容易。这是我们要做的事情。

列表 4.3:提取新闻项

function navTo(where)
{
    if(rssFile != "")
    {
      var rss_item;
    
      switch(standard)
      {
        case "atom":
          rss_item = xml.documentElement.selectSingleNode(
                 "/*/*[local-name()='entry'][" + where + "]");
          break;
    
        case "rss1":
          rss_item = xml.documentElement.selectSingleNode(
                  "/*/*[local-name()='item'][" + where + "]");
          break;
    
        case "rss2":
          rss_item = xml.documentElement.selectSingleNode(
                            "/*/channel/item[" + where + "]");
          break;
    
        default:
          rss_item = null;
      }
    
      if(rss_item)
      {
        var item_i = 
            rss_item.transformNode(xsl_i.documentElement);
    
        contentcell.vAlign = "Top";
        content.innerHTML = item_i;
    
        ...
    
      }
    }
}

请注意 xsl_i(用于转换),这是我之前描述过的项提取样式表。Where 是一个数字的字符串表示——项在供稿中的位置。

就这些了。随时给我发电子邮件,提出您的建议/意见/错误报告。

链接

教程

RSS 列表

工具和其他一切

历史

  • 2005 年 11 月 23 日
    • 发布文章,样式表和新闻阅读器的第一个版本。
  • 2006 年 2 月 13 日
    • 代码清理,部分函数完全重写;
    • XPath 查询已清理;
    • 添加了“保存 HTML”功能;
    • XSLT 样式表已优化/清理。
  • 2006 年 4 月 25 日
    • 添加了自动/手动供稿更新功能;
    • 细微改进和 bug 修复。
© . All rights reserved.