RSS 和 Atom 的基礎知識
Really Simple Syndication (RSS) 和 Atom 標准提供了針對各種不同使用的項的 XML 結構。最常見的 RSS 和 Atom 提要應用是數據分發格式,用於改進 Weblogs 和新聞站點。
RSS 和 Atom 提要包含相對較少的信息。因此,很容易下載文件,減少 Web 服務器的負載,而不是在用戶查看整個博客帖子頁面時提供通常很分散的所有信息。此外,RSS 和 Atom 文件還包含更多詳細的排序信息,比如作者、標題、主題和密碼標記信息,這些信息用於幫助識別和組織提要中的數據。
清單 1 中可以看到一個 RSS 提要樣例,以下內容摘自我的博客。
清單 1. 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 格式的相同信息。
清單 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 文件中提取的信息摘要
通常可以解析組成提要信息的 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>
可以按如下所示分析查詢:
主要組成部分是 <Html> 標記中的部分,這部分包括一個對本地 rss-summary 函數的調用,該函數提供 RSS 源(在這裡是一個本地文件,盡管它可能是一個 URL)。
通過使用 XPath 規范,前面聲明的 rss-summary 函數使用了一個 for 循環在每個項上進行迭代,以便選擇每個項。
要獲得每個項,可調用本地 rss-row 函數,該函數接受提供的鏈接和標題文本,並將此插入一個 Html 片段中。
可以使用 GNU Qexo 庫執行查詢,該庫提供了一個 XQuery 組件:$ Java -jar kawa-1.9.1.jar --xquery --main simplerss.xql。
輸出基本等同於前面在其他解決方案中已經看到的示例,因此,讓我們更進一步,對原始的、基本的示例進行擴展。
對輸出進行排序
對輸出的新聞項進行排序是最簡單的初始步驟之一。使用傳統解決方案時,排序可能很難,雖然在某些情況下並非不可能。但 XQuery 包括對許多不同數據類型的支持,這意味著您可以對源 XML 文件中的各種數據進行排序。
如果使用新聞提要,那麼對不同信息片上的項進行排序就存在更多的可能。典型模型是按日期對項進行排序,這樣就可以按照時間順序閱讀條目。
要添加對輸出的排序,只需將一個代碼行添加到 rss-summary 函數中的 for、let、where、order by 和 return (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 提要
圖片看不清楚?請點擊這裡查看原圖(大圖)。
連續對兩個文檔多次調用 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 提要
圖片看不清楚?請點擊這裡查看原圖(大圖)。
同時處理兩種提要類型
前面合並一個以上 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 提要的 XQuery 處理的基本知識,了解如何將一個提要轉換成一個 Html 文檔。然後,可以生成一個以符合您需要的格式輸出信息的更完善解決方案,其中包括多個提要的排序和合並,甚至還包括處理不同提要和源信息類型。
XQuery 提供了處理 XML 文件的靈活方法。一些人發現,從語法方面說,此方法更易於遵循。當然,某些 XQuery 功能,比如說創建單個 XML 中間文檔的靈活性,以便再次解析以處理不同源和輸入格式,有助於解決處理 XML 文件時將體驗到的一些問題。