设想一个从最热门的门户网站获得最新的新闻的站点。股票价格,天气信息,新闻故事,线式讨论组,软件发布……所有这一切都将被动态更新,每小时一次,不需要任何手工干预。我们可以想象这随之而来的站点访问量,源源不断的广告收入以及网管大人所受到的“阿谀奉承”。
但是现在,停止幻想,开始阅读,因为只要你密切关注此项技术,说不定你就能成为站点的主人。 对你的要求也只是稍许的想象力,一些聪明的PHP编码和几个免费的RSS文件。另外,很明显还包括这篇文章剩下的九个部分。
有内容,就联合成辛迪加(Have Content, Will Syndicate)
我们从最基本的开始——那么RSS究竟是什么鬼东西呢?
RSS(即RDF Site Summary)是一种格式,最早由Netscape公司设计,用于分发其门户站点My.Netscape.Com上的内容的描述信息。自1997年被提出以来,几经沉浮——可以点击文章末尾的链接,了解一下RSS悠久复杂的历史。现在的稳定的版本是RSS1.0,符合RDF规范。这一版本可以说即轻便又功能齐全。
RSS使得网管及时公布和分发某一特定站点的特定位置的最新最有趣的内容的描述信息变的可能。 从新闻文章列表到股票市场数据或着是天气预报,所有这些信息都可以通过结构良好的XML文档来发布,从而也可以被任何XML分析器进行分析,处理和翻译。
网站上最新信息的列表是经常更新的,而RSS使得这一列表的分发成为可能,也就为Web上简易的内容辛迪加联合打开了大门。想了解这其中的道理,请看下面这个简单的例子:
站点A,属新闻站点(“内容辛迪加组织者”),能够每小时发布一个包含最新新闻列表以及相应链接的RSS文档。 而这一RSS文档可以被其它站点获取(如站点B,“内容收集者”),分析并显示在站点B的索引页面上。 每次站点A发布一个新的RSS文档,站点B的索引页面都可以自动更新,以获取最新的新闻。
这种方案对交易中的双方机构都有效。 既然RSS文档中的链接都指向站点A上相应的文章,那么站点A将迅速体验到访问量的增加。 而站点B的网管可以休假一个星期,因为他有办法自动更新其站点上的索引页面,而这一方法仅仅是把索引页面与站点A发布的动态内容相连接而已。
有许多受欢迎的站点向公众提供详细的RSS或RDF新闻,如Freshmeat(http://www.freshmeat.net)和Slashdot(http://www.slashdot.org),当然还有其它许多站点。在这篇文章当中,我将广泛的使用Freshmeat网站的RDF文件。需要说明的一点是,这里所谈到的技术也可以应用于其它任何RSS1.0或RDF文件。
交换频道(Switching Channels)
典型的RSS文档包含一个由描述性元数据标记出来的资源列表(URLs),请看下面的例子:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/">
<channel rdf:about="http://www.melonfire.com/">
<title>Trog</title>
<description>Well-written technical articles and
tutorials on Web technologies</description>
<link>http://www.melonfire.com/community/columns/trog/</link>
<items>
<rdf:Seq>
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=100" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=71" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=62" />
</rdf:Seq>
</items>
</channel>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?i
d=10
0">
<title>Building A PHP-Based Mail Client (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=100
</li
nk>
<description>Ever wondered how Web-based mail clients
work? Find out here.</description>
</item>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?i
d=71">
<title>Using PHP With XML (part 1)</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=71<
/link>
<description>Use PHP's SAX parser to parse XML data and
generate HTML pages.</description>
</item>
<item
rdf:about="http://www.melonfire.com/community/columns/trog/article.php?i
d=62">
<title>Access Granted</title>
<link>http://www.melonfire.com/community/columns/trog/article.php?id=62<
/link>
<description>Precisely control access to information
with the mySQL grant tables.</description>
</item>
你可以看到,RDF文件由几个界限分明的部分组成。首先是文档序码(prolog),
<?xml version="1.0" encoding="UTF-8"?>
然后是根元素中的名称空间声明。
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/">
接着是<channel>部分,这部分包含了RDF所要描述的频道的一般信息。在上面的例子中,频道是Melonfire网站的Trog专栏,专栏内容是新的技术文章和指南,每星期更新一次。
<channel rdf:about="http://www.melonfire.com/">
<title>Trog</title>
<description>Well-written technical articles and
tutorials on Web technologies</description>
<link>http://www.melonfire.com/community/columns/trog/</link>
<items>
<rdf:Seq>
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=100" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=71" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=62" />
</rdf:Seq>
</items>
</channel>
<channel>区包含了一个<items>区块,<items>区块又包含了文档中描述的所有资源的一个顺序列表。该列表通过一系列的<li />元素来表示。区块中每一个资源都在后面的<item>区块中有更详细的描述。
<items>
<rdf:Seq>
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=100" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=71" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=62" />
</rdf:Seq>
</items>
还可以在其中放置一个<image>区块,这样你就可以发布频道标志的URL。
所以为了肉,RSS1.0文档中的每一个<item>区块都更详细地描述一个单独的资源,包括标题,URL和资源描述。
<items>
<rdf:Seq>
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=100" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=71" />
<li
rdf:resource="http://www.melonfire.com/community/columns/trog/article.ph
p?id
=62" />
</rdf:Seq>
</items>
在这个例子里,<item>区块描述了Ttrog“频道”中单独的一篇文章,并为这篇文章提供了描述和标题,以及URL。内容收集者可以利用URL创建“向后”链接。
你看得到,RSS1.0文件相当地直观明了,不管是手工,还是通过编程,都非常容易创建。上面的例子和解释仅仅是说明性质的,通常,你可以用RSS1.0和RDF做更多的事情。你最好看一下文章末尾提供的链接,以获取更多的信息。不过在这之前,我们再花几分钟讨论一如何将RSS1.0文档插入到你自己的Web站点之中。
既然从技术上讲,RSS是结构良好的XML文档,所以可以用标准的XML编程技术来处理它。主要有两种技术:SAX(the Simple API for XML)和DOM(the Document Object Model)。
SAX分析器工作时遍历整个XML文档,在遇到不用类型的标记时调用特定的函数。比如,调用特定函数处理一个开始标记,调用另一个函数处理一个结束标记,再调用一个函数处理两者之间的数据。分析器的职责仅仅是顺序遍历这个文档。而它所调用的函数负责处理发现的标记。一旦一个标记被处理完毕,分析器继续分析文档中的下一个元素,这一过程不断重复。
另一方面,DOM分析器工作是把整个XML文档读进内存当中,并将之转换成一种分层的树型结构。而且为访问不同的树结点(以及结点所附的内容)提供了API。递归处理方式加上API函数使得开发者能够区分不同类型的结点(元素,属性,字符数据,注释等),同时根据文档树的结点类型和结点深度,使得执行不同的动作成为可能。
SAX和DOM分析器几乎支持每一种语言,包括你我的最爱——PHP。我将在这篇文章中利用PHP的SAX分析器处理RDF的例子。 当然,使用DOM分析器也同样很容易。
让我们看这个简单的例子,把它记在脑海里。下面是一个我将要使用的RDF文件,这个文件直接选自http://www.freshmeat.net/ :
<?xml version="1.0" encoding="ISO-8859-1"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>
<channel rdf:about="http://freshmeat.net/">
<title>freshmeat.net</title>
<link>http://freshmeat.net/</link>
<description>freshmeat.net maintains the Web's largest index of Unix
and cross-platform open source software. Thousands of applications are
meticulously cataloged in the freshmeat.net database, and links to new
code are added daily.</description>
<dc:language>en-us</dc:language>
<dc:subject>Technology</dc:subject>
<dc:publisher>freshmeat.net</dc:publisher>
<dc:creator>freshmeat.net contributors</dc:creator>
<dc:rights>Copyright (c) 1997-2002 OSDN</dc:rights>
<dc:date>2002-02-11T10:20+00:00</dc:date>
<items>
<rdf:Seq>
<rdf:li rdf:resource="http://freshmeat.net/releases/69583/" />
<rdf:li rdf:resource="http://freshmeat.net/releases/69581/" />
<!-- and so on -->
</rdf:Seq>
</items>
<image rdf:resource="http://freshmeat.net/img/fmII-button.gif" />
<textinput rdf:resource="http://freshmeat.net/search/" />
</channel>
<image rdf:about="http://freshmeat.net/img/fmII-button.gif">
<title>freshmeat.net</title>
<url>http://freshmeat.net/img/fmII-button.gif</url>
<link>http://freshmeat.net/</link>
</image>
<item rdf:about="http://freshmeat.net/releases/69583/">
<title>sloop.splitter 0.2.1</title>
<link>http://freshmeat.net/releases/69583/</link>
<description>A real time sound effects program.</description>
<dc:date>2002-02-11T04:52-06:00</dc:date>
</item>
<item rdf:about="http://freshmeat.net/releases/69581/">
<title>apacompile 1.9.9</title>
<link>http://freshmeat.net/releases/69581/</link>
<description>A full-featured Apache compilation HOWTO.</description>
<dc:date>2002-02-11T04:52-06:00</dc:date>
</item>
<!-- and so on -->
</rdf:RDF>
下面是分析这一文档并显示其中数据的PHP脚本:
<?php
// XML file
$file = "fm-releases.rdf";
// set up some variables for use by the parser
$currentTag = "";
$flag = "";
// create parser
$xp = xml_parser_create();
// set element handler
xml_set_element_handler($xp, "elementBegin", "elementEnd");
xml_set_character_data_handler($xp, "characterData");
xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, TRUE);
// read XML file
if (!($fp = fopen($file, "r")))
{
die("Could not read $file");
}
// parse data
while ($xml = fread($fp, 4096))
{
if (!xml_parse($xp, $xml, feof($fp)))
{
die("XML parser error: " .
xml_error_string(xml_get_error_code($xp)));
}
}
// destroy parser
xml_parser_free($xp);
// opening tag handler
function elementBegin($parser, $name, $attributes)
{
global $currentTag, $flag;
// export the name of the current tag to the global scope
$currentTag = $name;
// if within an item block, set a flag
if ($name == "ITEM")
{
$flag = 1;
}
}
// closing tag handler
function elementEnd($parser, $name)
{
global $currentTag, $flag;
$currentTag = "";
// if exiting an item block, print a line and reset the flag
if ($name == "ITEM")
{
echo "<hr>";
$flag = 0;
}
}
// character data handler
function characterData($parser, $data)
{
global $currentTag, $flag;
// if within an item block, print item data
if (($currentTag == "TITLE" || $currentTag == "LINK" ||
$currentTag ==
"DESCRIPTION") && $flag == 1)
{
echo "$currentTag: $data <br>";
}
}
?>
看不明白? 别着急,后面将会作出解释。
捕获旗标
这段脚本首先要做的是设定一些全局变量:
// XML file
$file = "fm-releases.rdf";
// set up some variables for use by the parser
$currentTag = "";
$flag = "";
$currentTag变量保存是分析器当前处理的元素的名称——你很快就会看到为什么需要它。
因为我的最终目的是显示频道中的每一个单独的条目(item),并且带有链结。另外还要知道分析器什么时候退出了<channel></channel>区块,什么时候又进入了文档的 <item></item>部分。再说我用的是SAX分析器,它按顺序方式工作,没有任何分析器API可供使用,无法知道文档树中的深度和位置。所以,我不得不自己发明一个机制来做这件事——这就是引入$flag变量的原因。
$flag变量将用于判断分析器是在<channel>区块还是在<item>区块里面。
下一步要做的是初始化SAX分析器,并开始分析RSS文档。
// create parser
$xp = xml_parser_create();
// set element handler
xml_set_element_handler($xp, "elementBegin", "elementEnd");
xml_set_character_data_handler($xp, "characterData");
xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, TRUE);
// read XML file
if (!($fp = fopen($file, "r")))
{
die("Could not read $file");
}
// parse data
while ($xml = fread($fp, 4096))
{
if (!xml_parse($xp, $xml, feof($fp)))
{
die("XML parser error: " .
xml_error_string(xml_get_error_code($xp)));
}
}
// destroy parser
xml_parser_free($xp);
这段代码简单明了,其中的注释已经解释的足够清楚了。xml_parser_create()函数建立一个分析器实例,并将之赋给句柄$xp。接着再创建回调函数处理开标记和闭标记,以及二者之间的字符数据。最后,xml_parse()函数联合多次fread()调用,读取RDF文件并分析它。
在文档中,每次遇到开标记,开标记处理器elementBegin()就会被调用。
// opening tag handler
function elementBegin($parser, $name, $attributes)
{
global $currentTag, $flag;
// export the name of the current tag to the global scope
$currentTag = $name;
// if within an item block, set a flag
if ($name == "ITEM")
{
$flag = 1;
}
}
这个函数以当前标记的名称和属性作为起参数。标记名称被赋值给全局变量$currentTag。如果,这个开标记是<item>,那么把$flag变量置1。
同样,如果遇到闭标记,那么闭标记处理器elementEnd()将被调用。
// closing tag handler
function elementEnd($parser, $name)
{
global $currentTag, $flag;
$currentTag = "";
// if exiting an item block, print a line and reset the flag
if ($name == "ITEM")
{
echo "<hr>";
$flag = 0;
}
}
闭标记处理函数也是以标记名称作为其参数。如果是遇到的是一个为</item>的闭标记,变量$flag的值重置为0,并把变量$currentTag的值清空。
那么,如何处理标记之间的字符数据呢? 这才是我们的兴趣所在。先向字符数据处理器characterData()打个招呼吧。
// character data handler
function characterData($parser, $data)
{
global $currentTag, $flag;
// if within an item block, print item data
if (($currentTag == "TITLE" || $currentTag == "LINK" ||
$currentTag ==
"DESCRIPTION") && $flag == 1)
{
echo "$currentTag: $data <br>";
}
}
现在你可以看一下传给这个函数的参数,你会发现它只接收了开标记和闭标记之间的数据,而根本不知道分析器当前正在处理哪个标记。而这正事我们一开始就引入全局变量$currentTag的原因。
如果$flag变量的值为1,也就是说如果分析器当前处于<item></itme>区块之间,那么当前被处理的元素,不管是<title>,<link>还是<description>,数据都被打印到输出设备上(在这里,输出设备是Web浏览器),并在每个元素的输出后面加上换行符<br>。
整个RDF文档就是以这种顺序方式处理,每发现一个<item>标记就显示一定的输出。你可以看一下下面的运行结果:
筑巢时间(Nesting Time)
前面的例子只是用来说明问题的。如果你真想把RDF内容插入到Web站点当中,就需要把事情做的更好一些。所以把前面的脚本的作了改进,新增了一些东西,从而简化格式化RDF数据的任务。
<html>
<head>
<basefont face="Verdana">
</head>
<body>
<table border="0" cellspacing="5" cellpadding="5">
<tr>
<td><b>New releases on freshmeat.net today:</b></td>
</tr>
<?php
// XML file
$file = "http://www.freshmeat.net/backend/fm-releases.rdf";
// set up some variables for use by the parser
$currentTag = "";
$flag = "";
$count = 0;
// this is an associative array of channel data with keys ("title",
"link",
"description")
$channel = array();
// this is an array of arrays, with each array element representing an
<item> // each outer array element is itself an associative array
// with keys ("title", "link", "description")
$items = array();
// opening tag handler
function elementBegin($parser, $name, $attributes)
{
global $currentTag, $flag;
$currentTag = $name;
// set flag if entering <channel> or <item> block
if ($name == "ITEM")
{
$flag = 1;
}
else if ($name == "CHANNEL")
{
$flag = 2;
}
}
// closing tag handler
function elementEnd($parser, $name)
{
global $currentTag, $flag, $count;
$currentTag = "";
// set flag if exiting <channel> or <item> block
if ($name == "ITEM")
{
$count++;
$flag = 0;
}
else if ($name == "CHANNEL")
{
$flag = 0;
}
}
// character data handler
function characterData($parser, $data)
{
global $currentTag, $flag, $items, $count, $channel;
$data = trim(htmlspecialchars($data));
if ($currentTag == "TITLE" || $currentTag == "LINK" ||
$currentTag ==
"DESCRIPTION")
{
// add data to $channels[] or $items[] array
if ($flag == 1)
{
$items[$count][strtolower($currentTag)] .=
$data;
}
else if ($flag == 2)
{
$channel[strtolower($currentTag)] .= $data;
}
}
}
// create parser
$xp = xml_parser_create();
// set element handler
xml_set_element_handler($xp, "elementBegin", "elementEnd");
xml_set_character_data_handler($xp, "characterData");
xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, TRUE);
xml_parser_set_option($xp, XML_OPTION_SKIP_WHITE, TRUE);
// read XML file
if (!($fp = fopen($file, "r")))
{
die("Could not read $file");
}
// parse data
while ($xml = fread($fp, 4096))
{
if (!xml_parse($xp, $xml, feof($fp)))
{
die("XML parser error: " .
xml_error_string(xml_get_error_code($xp)));
}
}
// destroy parser
xml_parser_free($xp);
// now iterate through $items[] array
// and print each item as a table row
foreach ($items as $item)
{
echo "<tr><td><a href=" . $item["link"] . ">" . $item["title"] .
"</a><br>" . $item["description"] . "</td></tr>"; }
?>
</table>
</body>
</html>
与先前的那段的主要区别在于,这段脚本创建了两个数组,用于保存分析过程中所提取的信息。其中,$channel是联合性数组(associative array),存放被处理的频道的基本描述信息,而$items是一个二维数组,包含关于单独的频道条目(channel intems)的信息。$items数组中的每一个元素本身又是一个联合性数组,包含title,URL和description关键字。$items数组中元素总数与RDF文档中的<item>区块总数相同。
还需注意$flag变量的变化,根据被处理的是<channel></channel>区块还是<item></item>区块,它现在保存两个值。这一点很有必要,因为只有这样,分析器才能把信息放入正确的数组里面。
一旦文档分析完毕,事情就简单了——遍历$items 数组,以表格形式打印其中的每一个条目(item)。
返回到类(Back To Class)
既然你有这么大的权力,那么究竟为什么要把自己限制在仅仅是单个的RDF来源呢?就象我早先说过的一样,大多数主要的站点都经常为他们所提供的内容做快照。其实将所有这些不同的来源插入到你的站点当中是相当简单的。让我们看看是如何做的。
首先,我们把前面例子中的代码模块化。这样一来,你就无须为每一个单个的来源都一遍又一遍的重写相同的代码了。简化的方法就是将之打包成类,再把这个类包含到我的PHP脚本当中。
类代码如下:
<?
class RDFParser
{
//
// variables
//
// set up local variables for this class
var $currentTag = "";
var $flag = "";
var $count = 0;
// this is an associative array of channel data with keys
("title", "link", "description")
var $channel = array();
// this is an array of arrays, with each array element
representing an <item>
// each outer array element is itself an associative array
// with keys ("title", "link", "description")
var $items = array();
//
// methods
//
// set the name of the RDF file to parse
// this is usually a local file
// you may set it to a remote file if your PHP build supports
URL fopen()
function setResource($file)
{
$this->file = $file;
}
// parse the RDF file set with setResource()
// this populates the $channel and $items arrays
function parseResource()
{
// create parser
$this->xp = xml_parser_create();
// set object reference
xml_set_object($this->xp, $this);
// set handlers and parser options
xml_set_element_handler($this->xp, "elementBegin",
"elementEnd");
xml_set_character_data_handler($this->xp,
"characterData");
xml_parser_set_option($this->xp,
XML_OPTION_CASE_FOLDING, TRUE);
xml_parser_set_option($this->xp, XML_OPTION_SKIP_WHITE,
TRUE);
// read XML file
if (!($fp = fopen($this->file, "r")))
{
die("Could not read $this->file");
}
// parse data
while ($xml = fread($fp, 4096))
{
if (!xml_parse($this->xp, $xml, feof($fp)))
{
die("XML parser error: " .
xml_error_string(xml_get_error_code($this->xp)));
}
}
// destroy parser
xml_parser_free($this->xp);
}
// opening tag handler
function elementBegin($parser, $name, $attributes)
{
$this->currentTag = $name;
// set flag if entering <channel> or <item> block
if ($name == "ITEM")
{
$this->flag = 1;
}
else if ($name == "CHANNEL")
{
$this->flag = 2;
}
}
// closing tag handler
function elementEnd($parser, $name)
{
$this->currentTag = "";
// set flag if exiting <channel> or <item> block
if ($name == "ITEM")
{
$this->count++;
$this->flag = 0;
}
else if ($name == "CHANNEL")
{
$this->flag = 0;
}
}
// character data handler
function characterData($parser, $data)
{
$data = trim(htmlspecialchars($data));
if ($this->currentTag == "TITLE" || $this->currentTag ==
"LINK" || $this->currentTag == "DESCRIPTION")
{
// add data to $channels[] or $items[] array
if ($this->flag == 1)
{
$this->items[$this->count][strtolower($this->currentTag)] .= $data;
}
else if ($this->flag == 2)
{
$this->channel[strtolower($this->currentTag)] .= $data;
}
}
}
// return an associative array containing channel information
// (the $channel[] array)
function getChannelInfo()
{
return $this->channel;
}
// return an associative array of arrays containing item
information
// (the $items[] array)
function getItems()
{
return $this->items;
}
}
?>
如果你对PHP类较为熟悉的话,那么理解这段代码是相当容易的。如果不太懂的话,那么请直接跳到文章末尾的链接部分,看一篇关于类工作原理的好文章。然后在回来继续阅读上面的代码。
在使用这个类之前,我要特别花几分钟指出其中的一行代码——即上面对xml_set_object()函数调用的那一行。
现在的问题是如何使用这个类实际生成具有多个内容来源的Web页。
<?
include("class.RDFParser.php");
// how many items to display in each channel
$maxItems = 5;
?>
<html>
<head>
<basefont face="Verdana">
<body>
<table width="100%" border="0" cellspacing="5" cellpadding="5"> <tr>
<!-- first cell -->
<td valign=top align=left>
<font size="-1">
<?
// get and parse freshmeat.net channel
$f = new RDFParser();
$f->setResource("http://www.freshmeat.net/backend/fm-releases.rdf");
$f->parseResource();
$f_channel = $f->getChannelInfo();
$f_items = $f->getItems();
// now format and print it...
?>
The latest from <a href=<? echo $f_channel["link"]; ?>><? echo
$f_channel["title"]; ?></a> <br> <ul> <? // iterate through items array
for ($x=0; $x<$maxItems; $x++) {
if (is_array($f_items[$x]))
{
// print data
$item = $f_items[$x];
echo "<li><a href=" . $item["link"] . ">" .
$item["title"] . "</a>";
}
}
?>
</ul>
</font>
</td>
<!-- second cell -->
<td align=center width=50%>
<i>Primary page content here</i>
</td>
<!-- third cell -->
<td valign=top align=left>
<font size="-1">
<?
// get and parse slashdot.org channel
$s = new RDFParser();
$s->setResource("http://slashdot.org/slashdot.rdf");
$s->parseResource();
$s_channel = $s->getChannelInfo();
$s_items = $s->getItems();
// now format and print it...
?>
The latest from <a href=<? echo $s_channel["link"]; ?>><? echo
$s_channel["title"]; ?></a> <br> <ul> <? // iterate through items array
for ($x=0; $x<$maxItems; $x++) {
if (is_array($s_items[$x]))
{
// print data
$item = $s_items[$x];
echo "<li><a href=" . $item["link"] . ">" .
$item["title"] . "</a>";
}
}
?>
</ul>
</font>
</td>
</tr>
</table>
</body>
</head>
</html>
这段代码相当简单。一旦你用“new”关键字生成一个类的实例,
$f = new RDFParser();
那么就可以用类方法来设置要分析的RDF文件的位置,
$f->setResource("http://www.freshmeat.net/backend/fm-releases.rdf");
$f->parseResource();
并且获取$channel和$items数组,以用于后面的处理。
<?
$f_channel = $f->getChannelInfo();
$f_items = $f->getItems();
?>
The latest from <a href=<? echo $f_channel["link"]; ?>><? echo
$f_channel["title"]; ?></a> <br> <ul> <? // iterate through items array
for ($x=0; $x<$maxItems; $x++) {
if (is_array($f_items[$x]))
{
// print data
$item = $f_items[$x];
echo "<li><a href=" . $item["link"] . ">" .
$item["title"] . "</a>";
}
}
?>
</ul>
每次你重新装入上面的脚本,相应的RDF文件就会被从特定的位置上取来,经过分析之后,按要求的格式显示出来。
如果你站点具有高的访问量,你就可能觉得我们的辛苦无意义之极,尤其是当所用的RDF数据更新地没有那么快时,情况更糟。 在这种情况下,或许探究一下在本地缓存RDF数据才是较明智的做法:要么扩展上面的例子程序,在其中加入缓存功能;要么每阁几个小时都花很长的时间下载一个最新RDF文件的本地副本到你的Web服务器上,然后使用这个本地副本,而不是那个“活”的(the “live” one)。
免费午餐(A Free Lunch)
上面我所写的那个类也是很基本的,是拿来说明问题的,或许也可以用于低访问量的站点。如果你想寻找一些更专业的东西,去网上吧,那里有许多的开放源码的RDF分析器,他们带有各种附加的功能(包括缓存)。 那么就让我们看一些如何运用这些分析器的例子吧。
第一个要讲的是由Stefan Saasen 为fase4网站开发的RDF分析器类,可以从http://www.fase4.com/rdf/上免费下载。这是一个功能非常齐全的RDF分析器,支持缓存和通过代理认证。下面是如何使用它的例子:
<html>
<head>
<style type="text/css">
body {font-family: Verdana; font-size: 11px;}
.fase4_rdf {font-size: 13px; font-family: Verdana} .fase4_rdf_title
{font-size: 13px; font-weight : bolder;}
</style>
</head>
<body>
<?
// include class
include("rdf.class.php");
// instantiate object
$rdf = new fase4_rdf;
// set number of items to display
$rdf->set_max_item(5);
// set RDF engine options
$rdf->use_dynamic_display(true);
$rdf->set_Options( array("image"=>"hidden", "textinput"=>"hidden") );
// parse and display data
$rdf->parse_RDF("http://www.freshmeat.net/backend/fm-releases.rdf");
$rdf->finish();
?>
</body>
</html>
另一个要介绍的是由Jason Williams开发的PHP RDF分析器,可以在http://www.nerdzine.net/php_rdf/下载。这是一个未经任何任何渲染的PHP类,实现了一些基本的方法。但是它包含的大量的属性,可以让你用来安排经过处理的数据,直到你满意为止。
--------------------------------------------------------------------------------
<html>
<head>
<basefont face="Verdana">
</head>
<body link="Red" vlink="Red" alink="Red">
<?
include("rdf_class.php");
// this needs to be a local file
$f = new rdfFile("./fm-releases.rdf");
$f->parse(True);
$f->ReturnTable(True, "black", "white", "100%");
?>
</body>
</html>
关于这些类的文档在他们各自的网站上都有介绍。
增加一点样式(Adding A Little Style)
万一你讨厌遍历那些PHP数组并把他们用HTML标记出来的方式,那么你也可以选择通过使用XSLT样式单来格式化和显示这些数据。PHP4 .1可以通过新的XSLT API来支持Sablotron XSLT处理器,新的API可以用来合并一个XSLT样式单和一个XML文档(在这里,即RDF文件),从而非常容易的把XML标记转化为浏览器可读的HTML标记。
我不想在这上面讲的太细,你可以看一下PHP 手册,或者关注一下本文后面的链接以获得更加详细的信息。不过我还是会给出一个简单的例子来说明这个问题。首先,给出样式单文件:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rss="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.0">
<!-- main page -->
<xsl:template match="/rdf:RDF">
<html>
<head>
<basefont face="Arial" size="2"/>
</head>
<body>
<xsl:apply-templates select="rss:channel" />
<ul>
<xsl:apply-templates select="rss:item" />
</ul>
</body>
</html>
</xsl:template>
<!-- channel -->
<xsl:template match="rss:channel">
<b>
<a>
<xsl:attribute name="href"><xsl:value-of select="rss:link"
/></xsl:attribute>
<xsl:value-of select="rss:title" />
</a>
</b>
</xsl:template>
<!-- item -->
<xsl:template match="rss:item">
<li />
<a>
<xsl:attribute name="href"><xsl:value-of select="rss:link"
/></xsl:attribute>
<xsl:value-of select="rss:title" />
</a>
<br />
<xsl:value-of select="rss:description" />
</xsl:template>
</xsl:stylesheet>
下面是PHP脚本,用来把上面的样式单和讲的“鲜肉”RDF文档合并起来,生成一个HTML页面:
<?php
// XML file
// this needs to be a local file
$xml = "fm-releases.rdf";
// XSLT file
$xslt = "fm.xsl";
// create a new XSLT processor
$xp = xslt_create();
// transform the XML file as per the XSLT stylesheet
// return the result to $result
$result = xslt_process($xp, $xml, $xslt);
if ($result)
{
// print it
echo $result;
}
// clean up
xslt_free($xp);
?>
我想这相当简单,无须再加说明。两个文档合并在一起,产生了下面的“合成物”:
这是把RDF数据转换成浏览器可读的HTML的另一种方法,或许简单一些(尽管并不是最理想的)。不过要注意一点,你需要运行一个外部程序来定期更新你的RDF文件的本地副本,原因在于PHP XSLT处理器访问远端文件可能有困难。
家庭作业(Homework)
如果你有兴趣对这些文中谈到的和没谈到的技术了解的更多,你可以考虑访问下面的链接:
RSS 1.0 规范:http://www.purl.org/rss/1.0/
RSS 发展史:http://backend.userland.com/stories/rss091
W3C 网站关于RDF的内容:http://www.w3.org/RDF/
PHP中关于SAX和DOM编程的讨论:http://www.devshed.com/Server_Side/XML/XMLwithPHP
用PHP实现XSLT转换的讨论:http://www.devshed.com/Server_Side/XML/XSLTrans
关于PHP类的讨论:http://www.devshed.com/Server_Side/PHP/BackToClass
XML基础的讨论:http://www.devshed.com/Server_Side/XML/XMLBasic
XSLT基础的讨论: http://www.devshed.com/Server_Side/XML/XSLBasics
PHP手册中关于SAX函数的内容: http://www.php.net/manual/en/ref.xml.php
PHP手册中关于XSLT函数的内容:http://www.php.net/manual/en/ref.xslt.php
下次再见了……保重身体呀!
注意:本文中的所有例子都在Linux/i386,Apache1.3.12,,PHP4.1.1环境下通过检验。
这些例子仅用于说明问题,不是为了某一个产品。Melonfire网站不对文中的源代码提供任何授权或支持。(本文版权为Melonfire网站所有, 原文出处:http://www.devshed.com/Server_Side/PHP/PHPRDF/page1.html)
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/