使用 XQuery 聚合 RSS 和 Atom 信息

在将过滤指令嵌入到用来生成输出格式的文档中后,XQuery 可以使合并和过滤 XML 文档中的信息变得更容易。您可以使用该功能将来自 RSS 和 Atom 提要的信息聚合成您需要的格式。本文将查看 RSS 和 Atom 格式的结构,并了解 XQuery 如何能够简化这类信息的显示。

RSS 和 Atom 的基础知识

Really Simple Syndication (RSS) 和 Atom 标准提供了针对各种不同使用的项的 XML 结构。最常见的 RSS 和 Atom 提要应用是数据分发格式,用于改进 Weblogs 和新闻站点。

RSS 和 Atom 提要包含相对较少的信息。因此,很容易下载文件,减少 Web 服务器的负载,而不是在用户查看整个博客帖子页面时提供通常很分散的所有信息。此外,RSS 和 Atom 文件还包含更多详细的排序信息,比如作者、标题、主题和密码标记信息,这些信息用于帮助识别和组织提要中的数据。

.rss详细例子 

<?xml version="1.0" encoding="UTF-8"?>
<!-- generator="wordpress/2.0.4" -->
<rss version="2.0"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  >

<channel>
  <title>MCslp</title>
  <link>http://mcslp.com</link>
  <description>News from the desk of Martin MC Brown</description>
  <pubDate>Wed, 07 Nov 2007 23:25:53 +0000</pubDate>
  <generator>http://wordpress.org/?v=2.0.4</generator>
  <language>en</language>
      <item>
    <title>System Administration Toolkit: Testing system validity</title>
    <link>http://mcslp.com/?p=269</link>
    <comments>http://mcslp.com/?p=269#comments</comments>
    <pubDate>Wed, 07 Nov 2007 23:25:48 +0000</pubDate>
    <dc:creator>Martin MC Brown</dc:creator>
   
  <category>Articles</category>
  <category>IBM developerWorks</category>
  <category>Open Source</category>
  <category>System Administration</category>
    <guid isPermaLink="false">http://mcslp.com/?p=269</guid>
    <description><![CDATA[Have you ever wondered whether the system you are
using is the same as the one that you originally configured?
Making sure that the configuration and setting information that you configured is the
same as when you configured it should be a basic part of any security procedure.
After all, if an unscrupulous person has [...]]]></description>
      <content:encoded><![CDATA[<p>Have you ever wondered whether the
  system you are using is the same as the one that you originally configured?
  </p>
<p>Making sure that the configuration and setting information that you configured
is the same as when you configured it should be a basic part of any security procedure.
After all, if an unscrupulous person has changed the configuration of your system, you
want to know about it. </p>
<p>Tracking that information though can be difficult. You can't expect to
check the contents of every single file. Even if you automated the process, the
potential quantity of information to be checked could be enormous and often what you
want first is a quick indication of where to start looking. </p>
<p>In my new article, System Administration Toolkit: Testing system validity I
show you a number of techniques for recording and verifying this information, and
include sample scripts that will automate the process for you. </p>
<p>Read: <a href="http://www.ibm.com/developerworks/aix/library/
au-satsystemvalidity/index.html?ca=drs-">Systems Administration Toolkit:
Testing system validity</a>
</p>
]]></content:encoded>
      <wfw:commentRSS>http://mcslp.com/?feed=rss2&p=269</wfw:commentRSS>
    </item>
...
</rss>

 

清单 2 中是 Atom 格式的相同信息。

<?xml version="1.0" encoding="UTF-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en">
  <title>MCslp</title>
  <link rel="alternate" type="text/html" href="http://mcslp.com"/>
  <tagline>News from the desk of Martin MC Brown</tagline>
  <modified>2007-11-07T23:25:53Z</modified>
  <copyright>Copyright 2007</copyright>
  <generator url="http://wordpress.org/"
           version="2.0.4">WordPress</generator>
  <entry>
    <author>
      <name>Martin MC Brown</name>
    </author>
    <title type="text/html" mode="escaped"
      ><![CDATA[System Administration Toolkit:
           Testing system validity]]></title>
    <link rel="alternate" type="text/html" href="http://mcslp.com/?p=269"/>
    <id>http://mcslp.com/?p=269</id>
    <modified>2007-11-07T23:25:48Z</modified>
    <issued>2007-11-07T23:25:48Z</issued>

    <dc:subject>Articles</dc:subject>
    <dc:subject>IBM developerWorks</dc:subject>
    <dc:subject>Open Source</dc:subject>
    <dc:subject>System Administration</dc:subject>
    <summary type="text/plain" mode="escaped"><![CDATA[Have you ever
wondered whether the system you are using is the same as the one that you
originally configured?
Making sure that the configuration and setting information that you configured
is the same as when you configured it should be a basic part of any security
procedure. After all, if an unscrupulous person has [...]]]></summary>
    <content type="text/html" mode="escaped"
          xml:base="http://mcslp.com/?p=269"><![CDATA[...]]></content>
  </entry>

 

表 1 是可从 RSS 和 Atom 文件中提取的信息摘要。此表列出了每种类型的信息的对应 XML 标记。稍后需要使用此表解析和处理这些独特的文件。


表 1. 可从 RSS 和 Atom 文件中提取的信息摘要

RSSAtom描述
通道提要提要信息的根源
标题标题提要的标题,或者帖子的标题
链接链接到原始主机的链接,或者到单个帖子的链接
条目单个新闻项或博客帖子的根源
dc:creator作者帖子的作者
发布日期修改日期修改日期
发布日期发出日期发布日期
种类dc:subject种类或主题
描述摘要帖子的摘要
content:encoded内容完整的帖子内容

通常可以解析组成提要信息的 XML 文件的内容,然后用合适的格式打印出该信息。

传统 RSS 和 Atom 处理

在查看 XQuery 解决方案之前,将了解更传统的解决方案如何处理 RSS 和 Atom 文件解析以及生成输出过程中出现的问题。进行此演示的目的是为了让您了解如何将 RSS 和 Atom 提要转换成 HTML。

处理 RSS 或 Atom 提要的传统方法是使用编程语言(比如 Perl、PHP 或 Java)并解析整个 XML 文件内容。然后动态输出该信息,或者将该信息输出成静态 HTML 文件来显示它。

在清单 3 中可以看到一个 Perl 处理器样例。该脚本使用 XML::FeedPP 模块,此模块可为您处理许多复杂事物。该模块下载并解析 XML,以可迭代对象形式返回信息,以便输出项标题和链接地址。


清单 3. 使用 XML::FeedPP 模块的基于 Perl 的解析器

                
use XML::FeedPP;

my $source = 'http://planet.mcslp.com/wp-rss2.php';

my $feed = XML::FeedPP->new( $source );

print <<EOF;
<html>
<body>
<b>All the news that's fit to print</b>
<hr/>
EOF

foreach my $item ($feed->get_item()) 
{
    my ($title,$link) = ($item->link(),$item->title());

    print <<EOF;
<div>
   RSS item <b>$title</b> is located at <b>$link</b>
</div>
EOF

}

print <<EOF
  </body>
</html>
EOF

运行脚本就可以得到类似清单 4 中所示的输出。该输出是 HTML 格式的,当然,使用编程语言解决方案的好处是可以将信息插入数据库。


清单 4. 从基于 Perl 的 RSS 解析器中截取的输出

                
<html>
<body>
<b>All the news that's fit to print</b>
<hr/>
<div>
   RSS item <b>http://feeds.computerworld.com/~r/Computerworld/MartinMCBrown/
   ~3/188475547/six_months_with_two_skype_phones</b> is located at <b>Six 
   months with two Skype phones</b>
</div>
<div>
   RSS item <b>http://feeds.computerworld.com/~r/Computerworld/MartinMCBrown/
   ~3/187849420/what_to_do_with_the_old_computing_bits_and_pieces</b> is located 
   at <b>What to do with the old computing bits and pieces</b>
</div>
...
</div>
  </body>
</html>

使用编程解决方案的一个问题是处理 XML 是一个相对复杂的过程,不同的实现和语言对于 XML 信息处理有着不同的处理能力级别。

但是,最复杂的情况(尤其对大多数语言而言)是标记和编程元素常常组合在相同文件中,这使得实际的处理可能非常复杂。修改输出样式和布局可能很困难,甚至难以解决,因为这可能需要对编程逻辑进行显著更改才能得以实现。

另一个可供选择的办法是使用 XSLT 样式表,将信息动态转换成 HTML。清单 5 中显示了一个 XSLT 示例,该 XSLT 生成了与 Perl 脚本提供的输出相同的基本输出。


清单 5. 使用 XSLT 样式表

                
<?xml version="1.0" encoding="utf-8" ?> 
<xsl:stylesheet
     version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="xml" />
     <xsl:template match="rss/channel">
     <html>
          <body>
               <xsl:apply-templates select="item" />
          </body>
     </html>
     </xsl:template>
     <xsl:template match="item">
       <div>
         RSS item <b><xsl:value-of select="title"/></b> is located 
              at <b><xsl:value-of select="link"/></b>
       </div>
     </xsl:template>
</xsl:stylesheet>

XSLT 解决方案具有的主要优点是可以将处理的编程部分作为格式的源嵌入相同文件。您可以看到基本的文档结构,甚至可以添加解析单个组件的 XSL 语句。

使用 XSLT 的不利方面是输入 XML 的复杂性和输出文件的复杂性可能导致更复杂的处理。尽管 XSLT 支持基本编程概念(比如循环),甚至支持一些更复杂的数据和信息处理,但与完整的编程语言比较,它的能力是非常有限的。这种复杂性可能导致处理较慢,尤其是特别大和复杂的文件。

如果亲自实践,您就会发现编写一个同时处理所有 RSS 和 Atom 提要元素的 XSL 转换会很难,但也不是不可能。理解输出以及其工作方式可能非常困难。





回页首


使用 XQuery 动态转换 RSS

XQuery 将 XPath 规范语言提取单个元素的灵活性与易于定义函数、循环和可编程元素的能力结合在一起。这种结合将 XPath 中简化的路径处理转换成一种在处理期间读取和处理信息的更灵活方式。

与 XSLT 不同,XQuery 拥有用户更熟悉的编程环境和执行模型,并且有一些令信息处理更容易的强大排序,从而不必求助基于编程语言的解决方案。

让我们从一个非常简单的、等同于前面示例的例子开始吧,该示例以基本 HTML 文件(清单 6)的形式输出 RSS 源中的信息。


清单 6. 一个简单的基于 XQuery 的 RSS 解析器

                
declare function local:rss-row ($link, $title)
{
<div>
  RSS item <b>{$title}</b> is located at <b>{$link}</b>
</div>
};

declare function local:rss-summary ($url)
{
 for $b in doc($url)/rss/channel/item
 return local:rss-row($b/link/text(), $b/title/text())
};

<html>
<body>
<b>All the news that's fit to print</b>
<hr/>

 {local:rss-summary("planet.rss2.xml")}

  </body>
</html>

可以按如下所示分析查询:

  1. 主要组成部分是 <html> 标记中的部分,这部分包括一个对本地 rss-summary 函数的调用,该函数提供 RSS 源(在这里是一个本地文件,尽管它可能是一个 URL)。
  2. 通过使用 XPath 规范,前面声明的 rss-summary 函数使用了一个 for 循环在每个项上进行迭代,以便选择每个项。
  3. 要获得每个项,可调用本地 rss-row 函数,该函数接受提供的链接和标题文本,并将此插入一个 HTML 片段中。

可以使用 GNU Qexo 库执行查询,该库提供了一个 XQuery 组件:$ java -jar kawa-1.9.1.jar --xquery --main simplerss.xql

输出基本等同于前面在其他解决方案中已经看到的示例,因此,让我们更进一步,对原始的、基本的示例进行扩展。





回页首


对输出进行排序

对输出的新闻项进行排序是最简单的初始步骤之一。使用传统解决方案时,排序可能很难,虽然在某些情况下并非不可能。但 XQuery 包括对许多不同数据类型的支持,这意味着您可以对源 XML 文件中的各种数据进行排序。

如果使用新闻提要,那么对不同信息片上的项进行排序就存在更多的可能。典型模型是按日期对项进行排序,这样就可以按照时间顺序阅读条目。

要添加对输出的排序,只需将一个代码行添加到 rss-summary 函数中的 forletwhereorder byreturn (FLOWR) 表达式中,对输出进行排序即可,如清单 7 中所示。


清单 7. 添加对输出的排序

                
declare function local:rss-summary ($url)
{
 for $b in doc($url)/rss/channel/item
 order by $b/pubDate 
 return local:rss-row($b/link/text(), $b/title/text())
};

XQuery 知道将信息写入 RSS 和 Atom 文件的日期,因此它可以自动对这些信息进行排序。如果想按降序日期 —最新的项在最前面— 顺序对项进行排序,那么只需将 descending 参数添加到 order by 表达式(清单 8)中即可。


清单 8. 添加 descending 参数

                
declare function local:rss-summary ($url)
{
 for $b in doc($url)/rss/channel/item
 order by $b/pubDate descending
 return local:rss-row($b/link/text(), $b/title/text())
};

在上面两个按日期进行排序的示例中,都通过选择单个标记的内容作为排序值,使用 XPath 表达式来引用单个项(在这里是 RSS 提要中的一个项)。

在通过使用 XQuery 以 HTML 形式输出单个 RSS 提要的基本系统准备就绪之后,现在需要处理多个提要。





回页首


合并多个提要

在原始脚本中,由系统通过 rss-summary 函数调用 {local:rss-summary("planet.rss2.xml")} 中的规范来决定处理哪一个提要。

要添加更多的提要,可多次调用该函数。planet.mcslp.com 实际上是将由许多不同提要合并成的更易于显示的单个博客和提要聚合在一起。可以使用 XQuery 复制此过程,将多个提要合并在一起。

此外,将提要合并在一起时,您可能想为每个帖子添加一个标题,以便查看每个帖子的来源。清单 9 显示了一个修改过的提要输出,该输出包含来自两个提要的信息。


清单 9. 显示多个 RSS 提要 (multirss.xql)

                
declare function local:rss-row ($doctitle, $link, $title)
{
<li><a href="{$link}">{$doctitle}: {$title}</a></li>
};

declare function local:rss-summary ($url)
{
 let $feeddoc := doc($url)

 for $b in $feeddoc/rss/channel/item
 order by $b/pubDate descending
 return local:rss-row($feeddoc/rss/channel/title/text(), $b/link/text(), $b/title/text())

};

<html>
<body>
<b>All the news that's fit to print</b>
<hr/>

<ul>
 {local:rss-summary("http://coalface.mcslp.com/wp-rss2.php")}
 {local:rss-summary("http://www.mcslp.com/wp-rss2.php")}
</ul>
  </body>
</html>

图 1 显示该过程的输出,即最终呈现的 HTML。


图 1. 多个 RSS 提要
该过程最终呈现的 HTML 输出

连续对两个文档多次调用 rss-summary 函数时存在的问题是信息无法合并。作为替代方法,您可以逐个输出来自两个提要的信息。

要实际合并多个提要,最简单的方法是创建一个中间 XML 文档,然后可以使用 XQuery 再次解析该文档来过滤出单个信息。可以参见清单 10 中的这样一个示例。


清单 10. 使用中间文档合并多个提要 (multirss2.xql)

                
declare function local:buildmergerow ($doctitle, $item)
{
<item>
<doctitle>{$doctitle}</doctitle>
<title>{$item/title/text()}</title>
<link>{$item/link/text()}</link>
<pubdate>{$item/pubDate/text()}</pubdate>
</item>
};

declare function local:rss-row ($item)
{
<li><a href="{$item/link/text()}">{$item/doctitle/text()}: 
               {$item/title/text()}</a></li>
};

declare function local:rss-summary ($url)
{
 let $feeddoc := doc($url)

 for $b in $feeddoc/rss/channel/item
         return local:buildmergerow($feeddoc/rss/channel/title/text(), $b)
};

<html>
<body>
<b>All the news that's fit to print</b>
<hr/>

<ul>

{ 
let $feedlist := ("http://coalface.mcslp.com/wp-rss2.php",
                  "http://www.mcslp.com/wp-rss2.php")

let $merged := for $url in $feedlist
        return local:rss-summary($url)

return for $item in $merged
        order by $item/pubdate descending
        return local:rss-row($item)

}

 </ul>
  </body>
</html>

清单 10 中示例的工作方式比以前的示例更复杂一些,但仍然十分简单。该示例可拆分成四个组成部分,三个函数和主要执行块,每个组成部分都有不同的作用:

  • buildmergerow() 函数接受提要标题和单个项,并为包含提要标题、项标题、链接和发布日期信息的每个项创建一个中间 XML 结构。
  • rss-summary() 函数的工作方式差不多和之前一样,处理每个项的单个提要,但在每个项上调用 buildmergerow()。
  • rss-row() 函数将准 RSS XML 格式的项格式化为 HTML 列表项。

主块提供了一个提要列表。您可以遍历该提要列表,处理每个提要,然后返回该过程的输出,将它放置在 $merged 变量中。因为要将整个 for 循环的输出指定给变量,所以结果就变成您将准 RSS 项 XML 放置在用于所有提要的变量中。一旦结束该过程,$merge 的值将包含来自 XML 格式的所有 RSS 提要的所有项。

然后,这一部分中的最后一个 for 循环将在这个准 RSS 列表上进行迭代,对项进行排序,并使用 rss-row() 函数对信息进行格式化。因为已经将来自所有提要的所有项合并成单个 $merged 列表,所以可以使用相同参数(在这里为日期)对所有项进行排序,并按反时间顺序生成列表的适当合并列表。

可以参见图 2 中显示的过程结果。


图 2. 合并的 RSS 提要
result




回页首


同时处理两种提要类型

前面合并一个以上 RSS 提要的示例实际上提供了如何解决不同提要类型的解决方案。可以使用相同的中间处理技巧将 RSS 和 Atom 提要解析成中间 XML 格式,然后处理该中间 XML 文档,生成您想要的信息。

在此实例中,有少许障碍需要克服。第一个问题是 Atom 使用源 XML 文档中的名称空间,因此必须声明 Atom 名称空间来正确提取信息。

第二个问题是确定想要访问的文档类型。尽管通常从提要或文档名称就可以很清楚地知道它们的类型,但还可以使用 XQuery 中的 if 语句寻找特定标记,然后执行适当的解析函数,从文件中提取信息。在清单 11 中可以看见代码片段中该语句的示例。


清单 11. 确定 提要类型信息的 if 语句

                
if (count($feeddoc/atom:feed/atom:entry) > 0)
        then
              local:parse-atom($feeddoc)
        else
              local:parse-rss($feeddoc)

清单 12 显示了完整的清单。此解决方案对以前的解决方案进行了修改。现在有两个函数,一个用于 Atom 提要,一个用于 RSS 提要,而不是只有一个用来构建中间文档的函数。与以前的解决方案类似,现在有用来处理提要(因为用于每种提要的 XPath 规范是不同的)的单独函数,还有用来构建中间 XML 文档的相应函数。


清单 12. 合并不同的提要类型 (multifeed.xql)

                
declare namespace atom = "http://purl.org/atom/ns#";

declare function local:atombuildmergerow ($doctitle, $item)
{
<item>
<doctitle>{$doctitle}</doctitle>
<title>{ $item/atom:title/text() }</title>
<link>{$item/atom:id/text()}</link>
<pubdate>{$item/atom:modified/text()}</pubdate>
</item>
};

declare function local:rssbuildmergerow ($doctitle, $item)
{
<item>
<doctitle>{$doctitle}</doctitle>
<title>{$item/title/text()}</title>
<link>{$item/link/text()}</link>
<pubdate>{$item/pubDate/text()}</pubdate>
</item>
};

declare function local:rss-row ($item)
{
<li><a href="{$item/link/text()}">{$item/doctitle/text()}: 
                            {$item/title/text()}</a></li>
};

declare function local:parse-rss($feeddoc)
{
    for $b in $feeddoc/rss/channel/item
         return local:rssbuildmergerow($feeddoc/rss/channel/title/text(), $b)
};

declare function local:parse-atom($feeddoc)
{
    for $b in $feeddoc/atom:feed/atom:entry
         return local:atombuildmergerow($feeddoc/atom:feed/atom:title/text(), $b)
};

<html>
<body>
<b>All the news that's fit to print</b>
<hr/>

<ul>

{
let $feedlist := ("coalface.rss2.xml",
                  "mcslp.atom.xml")

let $merged := for $url in $feedlist
        let $feeddoc := doc($url)
        return if (count($feeddoc/atom:feed/atom:entry) > 0)
        then
              local:parse-atom($feeddoc)
        else
              local:parse-rss($feeddoc)

return for $item in $merged
        order by $item/title
        return local:rss-row($item)

}

 </ul>
  </body>
</html>

在这里,该脚本使用了文件的本地副本来节约时间。让我们使用不同的 XQuery 处理器来解析不包含内置 URL 访问器方法的内容(如 Qexo 工具包所做)。例如,如果使用 Saxon XQuery 处理器,那么可以按如下所示运行脚本:$ java -cp /usr/share/saxon/lib/saxon8.jar net.sf.saxon.Query multifeed.xql

图 3 显示了来自提要的输出。该输出应该等同于图 2 的输出。不同之处并不在生成的结果上,而在于您使用 Atom 和 RSS 提要生成了该信息。


图 3. 合并的 RSS 和 Atom 摘要
合并的 RSS 和 Atom 摘要




回页首


结束语

分享这篇文章……

digg 提交到 Digg
del.icio.us 发布到 del.icio.us
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值