$doc->rss->channel->item->title |
從文檔中選擇元素。只要熟悉文檔的結(jié)構(gòu),很容易編寫(xiě)這種表達(dá)式。但是,如果不很清楚需要的元素出現(xiàn)在何處(比如 Docbook、HTML 和類似的敘述性文檔中),SimpleXML 可以使用 XPath 表達(dá)式尋找這些元素。
在博山等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站制作、做網(wǎng)站 網(wǎng)站設(shè)計(jì)制作按需設(shè)計(jì),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站設(shè)計(jì),營(yíng)銷型網(wǎng)站建設(shè),外貿(mào)網(wǎng)站建設(shè),博山網(wǎng)站建設(shè)費(fèi)用合理。開(kāi)始使用 SimpleXML
假設(shè)需要一個(gè) PHP 頁(yè)面將 RSS 提要(feed)轉(zhuǎn)化成 HTML。RSS 是一種簡(jiǎn)單的 XML 格式用于發(fā)布連鎖內(nèi)容。文檔的根元素是rss
,它包括一個(gè) channel
元素。channel
元素包含關(guān)于提要的元數(shù)據(jù),如標(biāo)題、語(yǔ)言和 URL。它還包含各種封裝在 item
元素中的報(bào)道。每個(gè) item
都有一個(gè) link
元素,包括一個(gè) URL,還有 title
或 description
(通常兩者都有),包含普通文本。不使用名稱空間。RSS 的內(nèi)容當(dāng)然不止這些,不過(guò)對(duì)本文來(lái)說(shuō)知道這些就足夠了。清單 1 顯示了一個(gè)典型的例子,它包含兩個(gè)新聞項(xiàng)。
清單 1. RSS 提要
|
我們來(lái)開(kāi)發(fā)一個(gè) PHP 頁(yè)面將 RSS 提要格式化為 HTML。清單 2 顯示了這個(gè)頁(yè)面的基本結(jié)構(gòu)。
清單 2. PHP 代碼的靜態(tài)結(jié)構(gòu)
|
解析 XML 文檔
第一步是解析 XML 文檔并保存到變量中。只需要一行代碼,向 simplexml_load_file()
函數(shù)傳遞一個(gè) URL 即可:
$rss = simplexml_load_file('http://partners.userland.com/nytRss/nytHomepage.xml'); |
這里選擇的方案絕不是最佳方案。實(shí)際上不應(yīng)該每次單擊頁(yè)面時(shí)都加載和解析 RSS 提要。對(duì)于該頁(yè)面的讀者來(lái)說(shuō)這樣做太慢,而且可能造成所加載 RSS 提要的拒絕服務(wù),多數(shù) RSS 都規(guī)定了適當(dāng)?shù)拿啃r(shí)大的刷新次數(shù)。真正的解決方案應(yīng)該緩沖生成的 HTML 頁(yè)面、RSS 提要或兩者。但是,我們重點(diǎn)是使用 SimpleXML 庫(kù),因此這里沒(méi)有過(guò)多考慮。
對(duì)于這個(gè)例子,我已經(jīng)從 Userland 的 New York Times 提要(在 http://partners.userland.com/nytRss/nytHomepage.xml)填充了頁(yè)面。當(dāng)然,也可使用其他 RSS 提要的任何 URL。
要注意,雖然名稱為 simplexml_load_file()
,該函數(shù)實(shí)際上解析遠(yuǎn)程 HTTP URL 上的 XML 文檔。但這并不是該函數(shù)唯一令人感到奇怪的地方。返回值(這里存儲(chǔ)在 $rss
變量中)并沒(méi)有指向整個(gè)文檔,如果使用過(guò)其他 API 如文檔對(duì)象模型(DOM)您可能會(huì)這樣期望。相反,它指向文檔的根元素。從 SimpleXML 不能訪問(wèn)文檔序言和結(jié)語(yǔ)部分的內(nèi)容。
尋找提要標(biāo)題
整個(gè)提要的標(biāo)題(不是提要中各報(bào)道的標(biāo)題)位于 rss
根元素 channel
的 title
孩子中。很容易找到這個(gè)標(biāo)題,就仿佛 XML 文檔是類 rss
的一個(gè)對(duì)象的序列化形式,它的 channel 字段本身帶有一個(gè) title 字段。使用常規(guī) PHP 對(duì)象引用語(yǔ)法,尋找標(biāo)題的語(yǔ)句如下:
$title = $rss->channel->title; |
找到之后可以將其添加到輸出 HTML 中。這樣做很簡(jiǎn)單,只要回顯 $title
變量即可:
|
這一行輸出元素的字符串值而不是整個(gè)元素。就是說(shuō)寫(xiě)入文本內(nèi)容但不包括標(biāo)簽。
甚至可以完全跳過(guò)中間變量 $title
:
|
因?yàn)樵擁?yè)面在多處重用這個(gè)值,我發(fā)現(xiàn)用一個(gè)含義明確的變量來(lái)存儲(chǔ)會(huì)更方便。
迭代新聞項(xiàng)
然后必須發(fā)現(xiàn)提要中的項(xiàng)。完成這項(xiàng)任務(wù)的表達(dá)式很簡(jiǎn)單:
$rss->channel->item |
但是,提要通常包含多個(gè)新聞項(xiàng)。但也可能一個(gè)也沒(méi)有。因此,該語(yǔ)句返回一個(gè)數(shù)組,可以通過(guò) for-each
循環(huán)來(lái)遍歷它:
foreach ($rss->channel->item as $item) {
echo "" . $item->title . ""; echo "" . $item->description . " "; } |
通過(guò)從 RSS 提要中讀取 link
元素值添加鏈接也很容易。只要在 PHP 中輸出一個(gè) a
元素,并使用 $item->link
檢索 URL 即可。清單 3 增加了該元素并填充到 清單 1 的框架中。
清單 3. 簡(jiǎn)單而完整的 PHP RSS 閱讀器
channel->title;
?>
" . $item->title . ""; echo "" . $item->description . " "; } ?> |
這樣就用 PHP 完成了一個(gè)簡(jiǎn)單的 RSS 閱讀器:只需要幾行 HTML 和幾行 PHP。不算空白的話一共只有 20 行。當(dāng)然,這個(gè)實(shí)現(xiàn)的功能還不夠豐富,也不夠優(yōu)化或者健壯。我們來(lái)看看還能做什么。
回頁(yè)首
錯(cuò)誤處理
并非所有 RSS 提要都如期望的那樣結(jié)構(gòu)良好。XML 規(guī)范要求處理程序在發(fā)現(xiàn)結(jié)構(gòu)良好性錯(cuò)誤時(shí)停止處理文檔,SimpleXML 是符合標(biāo)準(zhǔn)的 XML 處理程序。但是在發(fā)現(xiàn)錯(cuò)誤時(shí)它沒(méi)有提供多少幫助。一般來(lái)說(shuō),它在 php-errors 文件中記錄錯(cuò)誤(但是不包括詳細(xì)的錯(cuò)誤消息),simplexml-load-file()
函數(shù)返回 FALSE。如果不能確保解析的文件是結(jié)構(gòu)良好的,在使用文件數(shù)據(jù)之前要檢查錯(cuò)誤,如清單 4 所示。
清單 4. 避免結(jié)構(gòu)錯(cuò)誤的輸入
xpath('//title') as $title) {
echo "" . $title . ""; } } else { echo "Oops! The input is malformed!"; } ?> |
其他常見(jiàn)的錯(cuò)誤是文檔實(shí)際上是結(jié)構(gòu)良好的,但是沒(méi)有在期望的地方包含期望的元素。如果項(xiàng)沒(méi)有標(biāo)題(比如在 top-100 這樣的 RSS 提要中),$doc->rss->channel->item->title
這樣的表達(dá)式會(huì)怎么樣呢?最簡(jiǎn)單的辦法是將返回值永遠(yuǎn)看作一個(gè)數(shù)組并循環(huán)遍歷該數(shù)組。這樣就可以判斷元素比預(yù)期的多還是少。但是,如果確定只需要文檔中的第一個(gè)元素 —— 即使有多個(gè),可以按索引訪問(wèn),索引號(hào)從零開(kāi)始。比如,如果要請(qǐng)求一個(gè)項(xiàng)的標(biāo)題,可以用如下代碼:
$doc->rss->channel->item[0]->title[0] |
如果沒(méi)有第一項(xiàng),或者第一項(xiàng)沒(méi)有標(biāo)題,該項(xiàng)就按照常規(guī) PHP 數(shù)組索引越界處理。即結(jié)果是空,在將其插入輸出 HTML 時(shí),它會(huì)被轉(zhuǎn)化成空白字符串。
識(shí)別和拒絕不打算處理的非預(yù)期格式通常屬于 XML 驗(yàn)證解析器的范疇。然而,SimpleXML 不能針對(duì)文檔類型定義(DTD)或模式進(jìn)行驗(yàn)證。它只檢查結(jié)構(gòu)良好性。
回頁(yè)首
處理名稱空間
很多站點(diǎn)現(xiàn)在從 RSS 轉(zhuǎn)向了 Atom。清單 5 顯示了一個(gè) Atom 文檔的例子。該文檔大部分和 RSS 的例子類似。但是增加了一些元數(shù)據(jù),而且根元素變成了 feed
而不是 rss
。feed
元素包含 entry 而不是項(xiàng)(item)。content
元素代替了 description
。最重要的是,Atom 文檔使用了名稱空間,但 RSS 文檔沒(méi)有。這樣,Atom 文檔就可以在內(nèi)容中內(nèi)嵌真正的、沒(méi)有轉(zhuǎn)義的可擴(kuò)展 HTML(XHTML)。
清單 5. Atom 文檔
Steve Palmer has posted a beta of Vienna 2.1, an open source RSS/Atom client for Mac OS X. Vienna is the first reader I've found acceptable for daily use; not great but good enough. (Of course my standards for "good enough" are pretty high.) 2.1 focuses on improving the user interface with a unified layout that lets you scroll through several articles, article filtering (e.g. read all articles since the last refresh), manual folder reordering, a new get info window, and an improved condensed layout. Matt Mullenweg has released Wordpress 2.0.4, a blog engine based on PHP and MySQL. 2.0.4 plugs various security holes, mostly involving plugins. |
雖然元素名稱變了,但用 SimpleXML 處理 Atom 文檔的基本方法和 RSS 相同。一個(gè)區(qū)別是現(xiàn)在請(qǐng)求被命名的元素和本地名稱時(shí)必須指定名稱空間統(tǒng)一資源標(biāo)識(shí)符(URI)。這需要兩個(gè)步驟:首先通過(guò)向 children()
函數(shù)傳遞名稱空間 URI 請(qǐng)求給定名稱空間中的孩子元素。然后用那個(gè)名稱空間中適當(dāng)?shù)谋镜孛Q請(qǐng)求元素。假設(shè)第一次把 Atom 提要加載到變量 $feed
中,如下所示:
$feed = simplexml_load_file('http://www.cafeconleche.org/today.atom'); |
下面的兩行尋找 title
元素:
$children = $feed->children('http://www.w3.org/2005/Atom'); $title = $children->title; |
如果愿意可以將這些代碼壓縮成一行,雖然行會(huì)變得有點(diǎn)長(zhǎng)。名稱空間中的所有其他元素也必須類似處理。清單 6 給出了一個(gè)完整的 PHP 頁(yè)面,其中顯示帶名稱空間的 Atom 提要中的標(biāo)題。
清單 6. 簡(jiǎn)單的 PHP Atom 標(biāo)題閱讀器
children('http://www.w3.org/2005/Atom');
$title = $children->title;
?>
" . $details->title . ""; } ?> |
回頁(yè)首
混合的內(nèi)容
為什么這個(gè)例子中只顯示標(biāo)題行呢?因?yàn)樵?Atom 中,記錄的內(nèi)容可以包含報(bào)道的全部文本:不僅僅是普通文本,還包括標(biāo)記。這是一種敘述性結(jié)構(gòu):行中的詞句是供人閱讀的。和多數(shù)的此類數(shù)據(jù)相似,也有大量的混合內(nèi)容。于是 XML 就不那么簡(jiǎn)單了,SimpleXML 方法也開(kāi)始顯示出了一些不足之處。由于不能合理地處理混合內(nèi)容,這一不足使其在很多應(yīng)用中被排除了。
可以做到一點(diǎn),但這不是一個(gè)完整的解決方案,只能用于 content
元素包含真正的 XHTML 的情況。可以使用 asXML()
函數(shù)將這些 XHTML 作為非解析源代碼直接復(fù)制到輸出中,比如:
echo " " . $details->content->asXML() . " "; |
生成的結(jié)果如清單 7 所示。
清單 7. asXML 輸出
Nikolai Grigoriev has released SVGMath 0.3, a presentation MathML formatter that produces SVG written in pure Python and published under an MIT license. According to Grigoriev, "The new version can work with multiple-namespace documents (e.g. replace all MathML subtrees with SVG in an XSL-FO or XHTML document); configuration is made more flexible, and several bugs are fixed. There is also a stylesheet to adjust the vertical position of the resulting SVG image in XSL-FO." |
這不是純粹的 XHTML。content
元素悄悄從 Atom 文檔中溜了進(jìn)來(lái),您真的不愿這樣。更糟的是,它的名稱空間不對(duì),因此不能被識(shí)別。幸運(yùn)的是,這個(gè)多出來(lái)的元素實(shí)際上沒(méi)有多大害處,因?yàn)?Web 瀏覽器會(huì)忽略不認(rèn)識(shí)的任何標(biāo)簽。完成的文檔是無(wú)效的,但是關(guān)系不大。如果還是覺(jué)得別扭,可以通過(guò)字符串操作將其去掉,如下所示:
$description = $details->content->asXML();
$tags = array(' |
為了使代碼更加健壯,可以使用正則表達(dá)式而不是假定起始標(biāo)簽和前面相同。具體來(lái)說(shuō),可以考慮各種可能的屬性:
// end-tag is fixed in form so it's easy to replace
$description = str_replace("", "", $description);
// remove start-tag, possibly including attributes and white space
$description = ereg_replace(" |
即使這樣改進(jìn)之后,代碼還是會(huì)在注釋、處理指令和 CDATA 節(jié)上出錯(cuò)。無(wú)論怎么分解,恐怕都不會(huì)簡(jiǎn)單了。混合內(nèi)容實(shí)際上超出了 SimpleXML 所能處理的范圍。
回頁(yè)首
XPath
只要知道文檔有什么元素以及在什么位置,$rss->channel->item->title
這樣的表達(dá)式很方便。但是,不一定會(huì)知道得這么清楚。比方說(shuō),在 XHTML 中,標(biāo)題元素(h1
、h2
、h3
等等)可以是 body
、div
、table
或其他幾種元素的孩子。此外,div
、table
、blockquote
及其他元素又可以互相嵌套多次。在很多不那么明確的場(chǎng)合中,使用 //h1
或 //h1[contains('Ben')]
這樣的 XPath 表達(dá)式更方便。SimpleXML 通過(guò) xpath()
函數(shù)支持這種功能。
清單 8 顯示的 PHP 頁(yè)面列出了 RSS 文檔中的所有標(biāo)題,包括提要本身以及每個(gè)項(xiàng)的標(biāo)題。
清單 8. 使用 XPath 查找 title 元素
" . $title . ""; } ?> |
SimpleXML 僅支持 XPath 位置路徑及位置路徑的組合。不支持那些不返回節(jié)點(diǎn)集的 XPath 表達(dá)式,如 count(//para)
或contains(title)
。
從 PHP 5.1 版開(kāi)始,SimpleXML 可以直接對(duì)帶名稱空間的文檔使用 XPath 查詢。和通常一樣,XPath 位置路徑必須使用名稱空間前綴,即使搜索的文檔使用默認(rèn)名稱空間也仍然如此。registerXPathNamespace()
函數(shù)把前綴和后續(xù)查詢中使用的名稱空間 URL 聯(lián)系在一起。比方說(shuō),如果要查詢 Atom 文檔中的所有 title
元素,應(yīng)使用清單 9 中所示的代碼。
清單 9. 使用 XPath 和名稱空間
$atom = simplexml_load_file('http://www.cafeconleche.org/today.atom');
$atom->registerXPathNamespace('atm', 'http://www.w3.org/2005/Atom');
$titles = $atom->xpath('//atm:title');
foreach ($titles as $title) {
echo "" . $title . ""; } |
最后一點(diǎn)忠告:PHP 中的 XPath 速度非常慢。當(dāng)改為 XPath 表達(dá)式之后頁(yè)面加載延遲從難以覺(jué)察變成了幾秒鐘,即使是在負(fù)荷不高的本地服務(wù)器上。如果采用這些技術(shù),必須使用某種緩存技術(shù)來(lái)獲得適當(dāng)?shù)男阅?。不可能?dòng)態(tài)生成每個(gè)頁(yè)面。
回頁(yè)首
結(jié)束語(yǔ)
如果不需要處理混合內(nèi)容,SimpleXML 對(duì)于 PHP 程序員的工具箱來(lái)說(shuō)是個(gè)不錯(cuò)新玩意。其適用的情況很多。具體而言,它能夠很好地處理簡(jiǎn)單的、類似記錄的數(shù)據(jù)。只要文檔層次不深、不很復(fù)雜,而且沒(méi)有混合內(nèi)容,SimpleXML 要比使用 DOM 簡(jiǎn)單得多。如果事先知道文檔結(jié)構(gòu)該工具將更有用,雖然通過(guò) XPath 可以滿足這種要求。雖然不支持驗(yàn)證和混合內(nèi)容有點(diǎn)不方便,但不是絕對(duì)的。很多簡(jiǎn)單格式?jīng)]有混合內(nèi)容,而且很多應(yīng)用只涉及到可預(yù)知的數(shù)據(jù)格式。如果符合您的需要,可以自己嘗試一下 SimpleXML。只要對(duì)錯(cuò)誤處理稍加注意,并且通過(guò)緩存來(lái)解決性能問(wèn)題,SimpleXML 可以成為 PHP 中一種可靠、健壯的 XML 處理方法。