PHP中的XML應(yīng)用
XML代表Extensible Markup Language(eXtensible Markup Language的縮寫,意為可擴(kuò)展的標(biāo)記語言)。XML是一套定義語義標(biāo)記的規(guī)則,這些標(biāo)記將文檔分成許多部件并對(duì)這些部件加以標(biāo)識(shí)。它也是元標(biāo)記語言,即定義了用于定義其他與特定領(lǐng)域有關(guān)的、語義的、結(jié)構(gòu)化的標(biāo)記語言的句法語言。XML是當(dāng)今最熱門的技術(shù)。而PHP也具有分析XML文檔的功能,下面我們將共同探討一下PHP中的XML應(yīng)用的情況。
XML概貌
談起XML(eXtended Markup Language:可擴(kuò)展標(biāo)記語言),我們不妨先看一段HTML的代碼:
<html>
<title>XML</title>
<body>
<p><center><font color="red">TEXT</font></center></p>
<a href="www.domain.com"><img src="http://edu.cnzz.cn/NewsInfo/logo.jpg"/></a>
</body>
</html>
上面這段代碼從結(jié)構(gòu)上就符合XML的規(guī)則,XML可以理解是包含數(shù)據(jù)的樹形的結(jié)構(gòu)類型:
1、引用同一個(gè)元素的時(shí)候,使用一致的大小寫,如<center></Center>就是不符合規(guī)定的
2、任何屬性值(如 href="????")要用""引起來,如<a href=www.yahoo.com>就是不正確的
3、所有元素必須由打開<和關(guān)閉>標(biāo)注組成,元素應(yīng)該形如<body></body>或空元素<img ... />,如果結(jié)尾的 "/>" 少了"/"就是錯(cuò)誤的代碼
4、所有元素必須彼此嵌套,就像寫程序的循環(huán)一樣,而且,所有的元素必須嵌套于根元素之中,比如上面的代碼所有的內(nèi)容都嵌套于<html></html>之中。
5、元素名稱(即上面的body a p img等)應(yīng)為字母開頭。
怎樣應(yīng)用PHP的XML解析器Expat?
Expat是PHP腳本語言的XML解析器(同樣稱為XML處理器),可以使程序訪問XML文檔的結(jié)構(gòu)和內(nèi)容。它是一種基于事件的解析器。XML解析器有兩種基本類型:
基于樹型的解析器:將XML文檔轉(zhuǎn)換成樹型結(jié)構(gòu)。這類解析器分析整篇文章,同時(shí)提供一個(gè)API來訪問所產(chǎn)生樹的每個(gè)元素。其通用的標(biāo)準(zhǔn)為DOM(文檔對(duì)象模式)。
基于事件的解析器:將XML文檔視為一系列的事件。當(dāng)一個(gè)特殊事件發(fā)生時(shí),解析器將調(diào)用開發(fā)者提供的函數(shù)來處理。基于事件的解析器有一個(gè)XML文檔的數(shù)據(jù)集中視圖,也就是說它集中在XML文檔的數(shù)據(jù)部分,而不是其結(jié)構(gòu)。這些解析器從頭到尾處理文檔,并將類似于-元素的開始、元素的結(jié)尾、特征數(shù)據(jù)的開始等等-事件通過回調(diào)(callback)函數(shù)報(bào)告給應(yīng)用程序。
以下是一個(gè)"Hello-World"的XML文檔范例:
<greeting>
Hello World
</greeting>
基于事件的解析器將報(bào)告為三個(gè)事件:
開始元素:greeting
CDATA項(xiàng)的開始,值為:Hello World
結(jié)束元素:greeting
基于事件的解析器不產(chǎn)生描述文檔的結(jié)構(gòu),當(dāng)然如果使用Expat,必要時(shí)它一樣可以在PHP中生成完全的原生樹結(jié)構(gòu)。在CDATA項(xiàng)中,基于事件的解析器不會(huì)得到父元素greeting的信息。然而,它提供一個(gè)更底層的訪問,這就使得可以更好地利用資源和更快地訪問。通過這種方式,就沒有必要將整個(gè)文檔放入內(nèi)存;而事實(shí)上,整個(gè)文檔甚至可以大于實(shí)際內(nèi)存值。
上面Hello-World的范例雖然包括完整的XML格式,但它是無效的,因?yàn)榧葲]有DTD(文檔類型定義)與其聯(lián)系,也沒有內(nèi)嵌DTD。但是Expat是一個(gè)不檢查有效性的解析器,因此忽略任何與文檔聯(lián)系的DTD。應(yīng)注意的是文檔仍然需要完整的格式,否則Expat(和其他符合XML標(biāo)準(zhǔn)的解析器一樣)將會(huì)隨著出錯(cuò)信息而停止。
編譯Expat
Expat可以編譯進(jìn)PHP3.0.6版本(或以上)中。從Apache1.3.22開始,Expat已經(jīng)作為Apache的一部分。在Unix系統(tǒng)中,可以通過-with-xml選項(xiàng)配置PHP將其編譯入PHP。
如果將PHP編譯為Apache的模塊,而Expat將默認(rèn)作為Apache的一部分。在Windows中,則必須要加載XML動(dòng)態(tài)連接庫。
XML范例:XMLstats
我們所要討論的范例是使用Expat來收集XML文檔的統(tǒng)計(jì)數(shù)據(jù)。
對(duì)于文檔中每個(gè)元素,以下信息都將被輸出:
該元素在文檔中使用的次數(shù)
該元素中字符數(shù)據(jù)的數(shù)量
元素的父元素
元素的子元素
注意:為了演示,我們利用PHP來產(chǎn)生一個(gè)結(jié)構(gòu)來保存元素的父元素和子元素
用于產(chǎn)生XML解析器實(shí)例的函數(shù)有哪些?
用于產(chǎn)生XML解析器實(shí)例的函數(shù)為xml_parser_create()。該實(shí)例將用于以后的所有函數(shù)。這個(gè)思路非常類似于PHP中MySQL函數(shù)的連接標(biāo)記。在解析文檔前,基于事件的解析器通常要求注冊(cè)回調(diào)函數(shù)-用于特定的事件發(fā)生時(shí)調(diào)用。Expat沒有例外事件,它定義了如下七個(gè)可能事件:
對(duì)象 XML解析函數(shù) 描述
元素 xml_set_element_handler() 元素的開始和結(jié)束
字符數(shù)據(jù) xml_set_character_data_handler() 字符數(shù)據(jù)的開始
外部實(shí)體 xml_set_external_entity_ref_handler() 外部實(shí)體出現(xiàn)
未解析外部實(shí)體 xml_set_unparsed_entity_decl_handler() 未解析的外部實(shí)體出現(xiàn)
處理指令 xml_set_processing_instruction_handler() 處理指令的出現(xiàn)
記法聲明 xml_set_notation_decl_handler() 記法聲明的出現(xiàn)
默認(rèn) xml_set_default_handler() 其它沒有指定處理函數(shù)的事件
所有的回調(diào)函數(shù)必須將解析器的實(shí)例作為其第一個(gè)參數(shù)(此外還有其它參數(shù))。
對(duì)于本文最后的范例腳本,需要注意的是它既用到了元素處理函數(shù)又用到了字符數(shù)據(jù)處理函數(shù)。元素的回調(diào)處理函數(shù)通過xml_set_element_handler()來注冊(cè)。
這個(gè)函數(shù)需要三個(gè)參數(shù):
解析器的實(shí)例
處理開始元素的回調(diào)函數(shù)的名稱
處理結(jié)束元素的回調(diào)函數(shù)的名稱
當(dāng)開始解析XML文檔時(shí),回調(diào)函數(shù)必須存在。它們必須定義為與PHP手冊(cè)中所描述的原型一致。
例如,Expat將三個(gè)參數(shù)傳遞給開始元素的處理函數(shù)。在腳本范例中,其定義如下:
function start_element($parser, $name, $attrs)
$parser是解析器標(biāo)志,$name是開始元素的名稱,$attrs為包含元素所有屬性和值的數(shù)組。
一旦開始解析XML文檔,Expat在遇到開始元素是都將調(diào)用start_element()函數(shù)并將參數(shù)傳遞過去。
XML的Case Folding選項(xiàng)
用xml_parser_set_option()函數(shù)將Case folding選項(xiàng)關(guān)閉。這個(gè)選項(xiàng)默認(rèn)是打開的,使得傳遞給處理函數(shù)的元素名自動(dòng)轉(zhuǎn)換為大寫。但XML對(duì)大小寫是敏感的(所以大小寫對(duì)統(tǒng)計(jì)XML文檔是非常重要的)。對(duì)于我們的范例,case folding選項(xiàng)必須關(guān)閉。
如何對(duì)文檔進(jìn)行解析?
在完成所有的準(zhǔn)備工作后,現(xiàn)在腳本終于可以解析XML文檔:
Xml_parse_from_file(),一個(gè)自定義的函數(shù),打開參數(shù)中指定的文件,并以4kb的大小進(jìn)行解析
xml_parse(),和xml_parse_from_file()一樣,當(dāng)發(fā)生錯(cuò)誤時(shí),即XML文檔的格式不完全時(shí),將會(huì)返回false。
我們可以使用xml_get_error_code()函數(shù)來得到最后一個(gè)錯(cuò)誤的數(shù)字代碼。將此數(shù)字代碼傳遞給xml_error_string()函數(shù)即可得到錯(cuò)誤的文本信息。輸出XML當(dāng)前的行數(shù),使得調(diào)試更容易。
當(dāng)解析文檔時(shí),對(duì)于Expat需要強(qiáng)調(diào)問題的是:如何保持文檔結(jié)構(gòu)的基本描述?
如前所述,基于事件的解析器本身并不產(chǎn)生任何結(jié)構(gòu)信息。不過標(biāo)簽(tag)結(jié)構(gòu)是XML的重要特性。例如,元素序列<book><title>表示的意思不同于<figure><title>。書名和圖名是沒有關(guān)系的,雖然它們都用到"title"這個(gè)術(shù)語。因此,為了更有效地使用基于事件的解析器處理XML,必須使用自己的棧(stacks)或列表(lists)來維護(hù)文檔的結(jié)構(gòu)信息。
為了產(chǎn)生文檔結(jié)構(gòu)的鏡像,腳本至少需要知道目前元素的父元素。用Exapt的API是無法實(shí)現(xiàn)的,它只報(bào)告目前元素的事件,而沒有任何前后關(guān)系的信息。因此,需要建立自己的棧結(jié)構(gòu)。
腳本范例使用先進(jìn)后出(FILO)的棧結(jié)構(gòu)。通過一個(gè)數(shù)組,棧將保存全部的開始元素。對(duì)于開始元素處理函數(shù),目前的元素將被array_push()函數(shù)推到棧的頂部。相應(yīng)的,結(jié)束元素處理函數(shù)通過array_pop()將最頂?shù)脑匾谱摺?/p>
對(duì)于序列<book><title></title></book>,棧的填充如下:
開始元素book:將"book"賦給棧的第一個(gè)元素($stack[0])。
開始元素title:將"title"賦給棧的頂部($stack[1])。
結(jié)束元素title:從棧中將最頂部的元素移去($stack[1])。
結(jié)束元素title:從棧中將最頂部的元素移去($stack[0])。
PHP3.0通過一個(gè)$depth變量手動(dòng)控制元素的嵌套來實(shí)現(xiàn)范例,這就使腳本看起來比較復(fù)雜。PHP4.0通過array_pop()和array_push()兩個(gè)函數(shù)來使腳本看起來更簡(jiǎn)潔。
如何收集XML文檔中的元素信息?
為了收集每個(gè)元素的信息,腳本需要記住每個(gè)元素的事件。通過使用一個(gè)全局的數(shù)組變量$elements來保存文檔中所有不同的元素。數(shù)組的項(xiàng)目是元素類的實(shí)例,有4個(gè)屬性(類的變量)
$count -該元素在文檔中被發(fā)現(xiàn)的次數(shù)
$chars -元素中字符事件的字節(jié)數(shù)
$parents -父元素
$childs - 子元素
注意:PHP的一個(gè)特性是你可以通過while(list() = each())loop遍歷整個(gè)類結(jié)構(gòu),如同你遍歷整個(gè)相應(yīng)的數(shù)組一樣。所有的類變量(當(dāng)你用PHP3.0時(shí)還有方法名)都以字符串的方式輸出。
當(dāng)發(fā)現(xiàn)一個(gè)元素時(shí),我們需要增加其相應(yīng)的記數(shù)器來跟蹤它在文檔中出現(xiàn)多少次。在相應(yīng)的$elements項(xiàng)中的記數(shù)元素也要加一。
我們同樣要讓父元素知道目前的元素是它的子元素。因此,目前元素的名稱將會(huì)加入到父元素的$childs數(shù)組的項(xiàng)目中。最后,目前元素應(yīng)該記住誰是它的父元素。所以,父元素被加入到目前元素$parents數(shù)組的項(xiàng)目中。
顯示統(tǒng)計(jì)信息
剩下的代碼在$elements數(shù)組和其子數(shù)組中循環(huán)顯示其統(tǒng)計(jì)結(jié)果。這就是最簡(jiǎn)單的嵌套循環(huán),盡管輸出正確的結(jié)果,但代碼既不簡(jiǎn)潔又沒有任何特別的技巧,它僅僅是一個(gè)你可能每天用他來完成工作的循環(huán)。
腳本范例被設(shè)計(jì)為通過PHP的CGI方式的命令行來調(diào)用。因此,統(tǒng)計(jì)結(jié)果輸出的格式為文本格式。如果你要將腳本運(yùn)用到互聯(lián)網(wǎng)上,那么你需要修改輸出函數(shù)來產(chǎn)生HTML格式。
如何用PHP&XML編制一個(gè)迷你搜索引擎實(shí)例?
讓我們首先來熟悉一下我們程序中用到的那個(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開發(fā)資源。">
www.phpbuilder.com</web>
<web url="http://www.fokus.gmd.de" memo="[英文]PHP開發(fā)手冊(cè)。 ">
PHP Manual</web>
</sub>
</sub>
</sub>
</links>
它的結(jié)構(gòu)相當(dāng)簡(jiǎn)單,根元素就是links,sub代表著一個(gè)類別,web就是一個(gè)網(wǎng)站的信息,其中包含著屬性,url代表網(wǎng)站的聯(lián)接,memo為備注信息,<web>??</web> 、<sub>??</sub>中包含的為元素的數(shù)據(jù)在這里就是類別和網(wǎng)站的名稱,這是符合上面的規(guī)定的。
現(xiàn)在我們來回答上面提出的問題:為什么要用XML來編制搜索引擎?
第一個(gè)原因就是有時(shí)候由于各種原因我們可能不能用到數(shù)據(jù)庫(MySQL或者其他);
其次,對(duì)于小數(shù)據(jù)量的搜索引擎來說,它的數(shù)據(jù)量很小,如果用數(shù)據(jù)庫來做,效率未必有多高;
最重要的一點(diǎn)是,這個(gè)搜索引擎維護(hù)起來相當(dāng)?shù)暮?jiǎn)單,并且不用編寫繁瑣的數(shù)據(jù)庫的維護(hù)的程序。例如,我們要添加一個(gè)類別或者網(wǎng)頁,只要編輯文本的文件,加上一福紈eb>???</web>或是<sub>????</sub>就可以了,而且,如果想把一個(gè)類別移動(dòng)到另一個(gè)地方的話,我們只要將這一部分的sub復(fù)制過去就行了。
下面一個(gè)最簡(jiǎn)單的用PHP顯示XML的范例。
下面的程序是將解析XML并按照樹形結(jié)構(gòu)輸出至瀏覽器,并顯示每層的元素總數(shù)。
<?php
$file = "demo.xml";// XML文件
function xml_parse_from_file($parser, $file) {// 解析XML文件的函數(shù) }
function start_element($parser, $name, $attrs) {//遇到了開元素標(biāo)記如<a href="link">就執(zhí)行這一段}
function stop_element($parser, $name) {//遇到了開元素標(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ù)和他在該層的第幾號(hào)來對(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è)子類別(如程序設(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){ //判斷是否在子樹的范圍內(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的搜索引擎到底如何做呢?
作了若干的鋪墊,下面我們就來看一下我們的搜索引擎的主要文件。
第一段為仿sina,yahoo的按照類別查詢
第二段為搜索查詢部分(把整個(gè)樹遍歷一遍)顯示符合的內(nèi)容。
xml3.php
關(guān)鍵字匹配采用eregi函數(shù),我們假設(shè)輸入的文字都是不會(huì)導(dǎo)致錯(cuò)誤的。
關(guān)鍵字:PHP、XML、規(guī)則
新文章:
- CentOS7下圖形配置網(wǎng)絡(luò)的方法
- CentOS 7如何添加刪除用戶
- 如何解決centos7雙系統(tǒng)后丟失windows啟動(dòng)項(xiàng)
- CentOS單網(wǎng)卡如何批量添加不同IP段
- CentOS下iconv命令的介紹
- Centos7 SSH密鑰登陸及密碼密鑰雙重驗(yàn)證詳解
- CentOS 7.1添加刪除用戶的方法
- CentOS查找/掃描局域網(wǎng)打印機(jī)IP講解
- CentOS7使用hostapd實(shí)現(xiàn)無AP模式的詳解
- su命令不能切換root的解決方法
- 解決VMware下CentOS7網(wǎng)絡(luò)重啟出錯(cuò)
- 解決Centos7雙系統(tǒng)后丟失windows啟動(dòng)項(xiàng)
- CentOS下如何避免文件覆蓋
- CentOS7和CentOS6系統(tǒng)有什么不同呢
- Centos 6.6默認(rèn)iptable規(guī)則詳解