|
如何對(duì)文檔進(jìn)行解析? 在完成所有的準(zhǔn)備工作后,現(xiàn)在腳本終于可以解析XML文檔:
Xml_parse_from_file(),一個(gè)自定義的函數(shù),打開(kāi)參數(shù)中指定的文件,并以4kb的大小進(jìn)行解析 xml_parse(),和xml_parse_from_file()一樣,當(dāng)發(fā)生錯(cuò)誤時(shí),即XML文檔的格式不完全時(shí),將會(huì)返回false。
我們可以使用xml_get_error_code()函數(shù)來(lái)得到最后一個(gè)錯(cuò)誤的數(shù)字代碼。將此數(shù)字代碼傳遞給xml_error_string()函數(shù)即可得到錯(cuò)誤的文本信息。輸出XML當(dāng)前的行數(shù),使得調(diào)試更容易。
當(dāng)解析文檔時(shí),對(duì)于Expat需要強(qiáng)調(diào)問(wèn)題的是:如何保持文檔結(jié)構(gòu)的基本描述?
如前所述,基于事件的解析器本身并不產(chǎn)生任何結(jié)構(gòu)信息。不過(guò)標(biāo)簽(tag)結(jié)構(gòu)是XML的重要特性。例如,元素序列<book><title>表示的意思不同于<figure><title>。書(shū)名和圖名是沒(méi)有關(guān)系的,雖然它們都用到"title"這個(gè)術(shù)語(yǔ)。因此,為了更有效地使用基于事件的解析器處理XML,必須使用自己的棧(stacks)或列表(lists)來(lái)維護(hù)文檔的結(jié)構(gòu)信息。
為了產(chǎn)生文檔結(jié)構(gòu)的鏡像,腳本至少需要知道目前元素的父元素。用Exapt的API是無(wú)法實(shí)現(xiàn)的,它只報(bào)告目前元素的事件,而沒(méi)有任何前后關(guān)系的信息。因此,需要建立自己的棧結(jié)構(gòu)。
腳本范例使用先進(jìn)后出(FILO)的棧結(jié)構(gòu)。通過(guò)一個(gè)數(shù)組,棧將保存全部的開(kāi)始元素。對(duì)于開(kāi)始元素處理函數(shù),目前的元素將被array_push()函數(shù)推到棧的頂部。相應(yīng)的,結(jié)束元素處理函數(shù)通過(guò)array_pop()將最頂?shù)脑匾谱摺?
對(duì)于序列<book><title></title></book>,棧的填充如下:
開(kāi)始元素book:將"book"賦給棧的第一個(gè)元素($stack[0])。 開(kāi)始元素title:將"title"賦給棧的頂部($stack[1])。 結(jié)束元素title:從棧中將最頂部的元素移去($stack[1])。 結(jié)束元素title:從棧中將最頂部的元素移去($stack[0])。
PHP3.0通過(guò)一個(gè)$depth變量手動(dòng)控制元素的嵌套來(lái)實(shí)現(xiàn)范例,這就使腳本看起來(lái)比較復(fù)雜。PHP4.0通過(guò)array_pop()和array_push()兩個(gè)函數(shù)來(lái)使腳本看起來(lái)更簡(jiǎn)潔。
如何收集XML文檔中的元素信息?
為了收集每個(gè)元素的信息,腳本需要記住每個(gè)元素的事件。通過(guò)使用一個(gè)全局的數(shù)組變量$elements來(lái)保存文檔中所有不同的元素。數(shù)組的項(xiàng)目是元素類(lèi)的實(shí)例,有4個(gè)屬性(類(lèi)的變量)
$count -該元素在文檔中被發(fā)現(xiàn)的次數(shù) $chars -元素中字符事件的字節(jié)數(shù) $parents -父元素 $childs - 子元素
注意:PHP的一個(gè)特性是你可以通過(guò)while(list() = each())loop遍歷整個(gè)類(lèi)結(jié)構(gòu),如同你遍歷整個(gè)相應(yīng)的數(shù)組一樣。所有的類(lèi)變量(當(dāng)你用PHP3.0時(shí)還有方法名)都以字符串的方式輸出。
當(dāng)發(fā)現(xiàn)一個(gè)元素時(shí),我們需要增加其相應(yīng)的記數(shù)器來(lái)跟蹤它在文檔中出現(xiàn)多少次。在相應(yīng)的$elements項(xiàng)中的記數(shù)元素也要加一。
我們同樣要讓父元素知道目前的元素是它的子元素。因此,目前元素的名稱(chēng)將會(huì)加入到父元素的$childs數(shù)組的項(xiàng)目中。最后,目前元素應(yīng)該記住誰(shuí)是它的父元素。所以,父元素被加入到目前元素$parents數(shù)組的項(xiàng)目中。
顯示統(tǒng)計(jì)信息 剩下的代碼在$elements數(shù)組和其子數(shù)組中循環(huán)顯示其統(tǒng)計(jì)結(jié)果。這就是最簡(jiǎn)單的嵌套循環(huán),盡管輸出正確的結(jié)果,但代碼既不簡(jiǎn)潔又沒(méi)有任何特別的技巧,它僅僅是一個(gè)你可能每天用他來(lái)完成工作的循環(huán)。
腳本范例被設(shè)計(jì)為通過(guò)PHP的CGI方式的命令行來(lái)調(diào)用。因此,統(tǒng)計(jì)結(jié)果輸出的格式為文本格式。如果你要將腳本運(yùn)用到互聯(lián)網(wǎng)上,那么你需要修改輸出函數(shù)來(lái)產(chǎn)生HTML格式。
如何用PHP&XML編制一個(gè)迷你搜索引擎實(shí)例? 讓我們首先來(lái)熟悉一下我們程序中用到的那個(gè)XML(保存為xyz.xml)。
<?xml version="1.0" encoding="gb2312" ?> <links>采用PHP和XML技術(shù)構(gòu)建的搜索引擎 <web memo="memo1" url="">name1</web> <sub>電腦網(wǎng)絡(luò) <web memo="nemo2">name2</web> <sub>程序設(shè)計(jì) <web memo="memo3">name3</web> <sub>PHP <web url="http://www.phpbuilder.com/" memo="[英文]PHP開(kāi)發(fā)資源。"> www.phpbuilder.com</web> <web url="http://www.fokus.gmd.de" memo="[英文]PHP開(kāi)發(fā)手冊(cè)。 "> PHP Manual</web> </sub> </sub> </sub> </links>
它的結(jié)構(gòu)相當(dāng)簡(jiǎn)單,根元素就是links,sub代表著一個(gè)類(lèi)別,web就是一個(gè)網(wǎng)站的信息,其中包含著屬性,url代表網(wǎng)站的聯(lián)接,memo為備注信息,<web>??</web> 、<sub>??</sub>中包含的為元素的數(shù)據(jù)在這里就是類(lèi)別和網(wǎng)站的名稱(chēng),這是符合上面的規(guī)定的。
現(xiàn)在我們來(lái)回答上面提出的問(wèn)題:為什么要用XML來(lái)編制搜索引擎? 第一個(gè)原因就是有時(shí)候由于各種原因我們可能不能用到數(shù)據(jù)庫(kù)(MySQL或者其他); 其次,對(duì)于小數(shù)據(jù)量的搜索引擎來(lái)說(shuō),它的數(shù)據(jù)量很小,如果用數(shù)據(jù)庫(kù)來(lái)做,效率未必有多高;
最重要的一點(diǎn)是,這個(gè)搜索引擎維護(hù)起來(lái)相當(dāng)?shù)暮?jiǎn)單,并且不用編寫(xiě)繁瑣的數(shù)據(jù)庫(kù)的維護(hù)的程序。例如,我們要添加一個(gè)類(lèi)別或者網(wǎng)頁(yè),只要編輯文本的文件,加上一福紈eb>???</web>或是<sub>????</sub>就可以了,而且,如果想把一個(gè)類(lèi)別移動(dòng)到另一個(gè)地方的話,我們只要將這一部分的sub復(fù)制過(guò)去就行了。
下面一個(gè)最簡(jiǎn)單的用PHP顯示XML的范例。
下面的程序是將解析XML并按照樹(shù)形結(jié)構(gòu)輸出至瀏覽器,并顯示每層的元素總數(shù)。
<?php $file = "demo.xml";// XML文件 function xml_parse_from_file($parser, $file) {// 解析XML文件的函數(shù) } function start_element($parser, $name, $attrs) {//遇到了開(kāi)元素標(biāo)記如<a href="link">就執(zhí)行這一段} function stop_element($parser, $name) {//遇到了開(kāi)元素標(biāo)記如</body>就執(zhí)行這一段} function data($parser, $data) {……} function showcount(){ //顯示每一層的元素總數(shù)}
global $level,$levelcount,$maxlevel; $level = -1; $parser = xml_parser_create();// 產(chǎn)生解析器的實(shí)例 xml_set_element_handler($parser, "start_element", "stop_element"); // 設(shè)置處理函數(shù) xml_set_character_data_handler($parser, "data"); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); $ret = xml_parse_from_file($parser, $file); // 解析文件 if(!$ret) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser))); } xml_parser_free($parser); // 釋放解析器 showcount(); ?>
在上面的程序的基礎(chǔ)上,可以顯示一段子樹(shù),我們依照元素的層數(shù)和他在該層的第幾號(hào)來(lái)對(duì)他進(jìn)行定位
例如:
links (0,1) +----web (1,1) +----sub (1,2) | +----web (2,1) | +----sub (2,2) | | +----web (3,1) | | +----sub (3,2) : : : 下面的代碼是我們的搜索引擎的基礎(chǔ)。因?yàn)椋@示出一個(gè)子類(lèi)別(如程序設(shè)計(jì)->PHP->)的信息就要用到他。
<?php …… function start_element($parser, $name, $attrs) { global $level,$levelcount,$maxlevel,$hide,$lev,$num,$PHP_SELF; $level += 1; if($level>$maxlevel) $maxlevel=$level; $levelcount[$level]+=1;
if($hide){ //判斷是否在子樹(shù)的范圍內(nèi),$hide==FALSE 為在 if($level==$lev&&$levelcount[$level]==$num) $hide=FALSE; }else{ if($level<=$lev)$hide=TRUE; }
if(!$hide){ ……//輸出 } } function data($parser, $data) { global $level,$hide; if(!$hide){ if(trim($data)!=""){ echo trim($data); } } } …… global $hide,$lev,$num,$PHP_SELF; $level = -1; $hide = TRUE; echo "<p><a href=$PHP_SELF>Root</a></p>"; if($lev==""){ $lev=0;$num=1; } …… ?>
mini的搜索引擎到底如何做呢?
作了若干的鋪墊,下面我們就來(lái)看一下我們的搜索引擎的主要文件。
第一段為仿sina,yahoo的按照類(lèi)別查詢 第二段為搜索查詢部分(把整個(gè)樹(shù)遍歷一遍)顯示符合的內(nèi)容。
xml3.php
關(guān)鍵字匹配采用eregi函數(shù),我們假設(shè)輸入的文字都是不會(huì)導(dǎo)致錯(cuò)誤的。
<全文完>
|