在本文的第一部分中,我們介紹了XPath並討論了各種各樣的從簡單到復雜的XPath查詢。 通過把XPath查詢應用到XML示例文件,我們詳細說明了各種重要的XPath定義比如location step、context node、location path、axes和node - test。 我們然后討論了多個簡單查詢組合成的復雜的XPath查詢。 我們還討論了無線二進制XML(WBXML)--XML在無線應用領域的對應物--的抽象結構。 最后我們介紹一個簡單的XPath處理引擎的設計。
在這一部分里,我們打算討論XPath的更進一步的特性--在一個XML文件上執行復雜檢索的操作。 我們將討論謂詞或者過濾器查詢以及在XPath中的函數的使用。我們將介紹各種的用於處理WSDL和WML的XPath查詢。 我們還將增強我們的XPath引擎的功能,使之包括謂詞、函數和不同的數據類型。
過濾查詢和謂詞
讓我們從一個將返回任何XML文件當中的根節點的簡單查詢開始:
./node()
我們可以更進一步,使用另一個簡單查詢,選擇根節點的全部的直接子節點:
./node()/*
如果你想要得到所有的是根節點的直接子節點並且只有一個type屬性的節點,那么該怎么辦呢? 那么就使用下面的這個查詢:
./node()/*[attribute::type]
在代碼段1中,這個查詢將返回binding元素。 由此可見,寫在方括號之內的代碼attribute::query擔負一個過濾器的功能。 XPath中的過濾器被稱作謂詞(predicate),寫在方括號內。 一個謂詞作用在一個結點集上--在這個例子中,結點集由根節點的所有的直接子節點組成---應用過濾條件(在這里,結點肯定有一個type屬性)到結點集上。 產生的結果就是一個經過過濾的結點集。
謂詞可以從簡單到很復雜。 也許XPath謂詞的簡單形式就像下面的查詢中的只是一個數字,返回根元素的第二個子節點(message元素):
./node()/*[2]
查詢語句./node()/message[attribute::name="TotalBill"]/text() 將尋找根元素的一個屬性name值為TotalBill的特定的message子節點。 查詢將返回特定的message元素的所有文本結點。 這個查詢將返回代碼段1中兩個message元素中的第二個。
XPath 函數
假定你想要回答下面對代碼段1中的WSDL文件所提出的問題:
1. 最后一個operation元素的name屬性的值是什么?
2.定義元素有多少個message子元素?
3. 根元素的第一個子元素的名稱是什么?
last()函數
last()函數將總是指向結點集的最后一個結點。 下面的這個查詢,當被應用於代碼段1中的WSDL文件的時候,將返回第二message元素(即 名稱是TotalBill的message元素):
./node()/message[last()]
注意下面的這條查詢也返回相同的message元素:
./node()/message[2]
這兩個查詢之間唯一的區別就是我們使用數字2來代替last()方法。 事實上在本例中last()函數返回的值就是2(特定location step的結點集中的結點數)。 把這兩個相同的查詢應用到代碼段2中的WSDL文件,這次你會發現兩個查詢沒有返回相同的結果。 代碼段2中有三個message元素,所以現在last()函數返回數字3。
注意本討論中的last()函數總是返回一個數字。
position()函數
如果你把下面的這些查詢應用到代碼段2中的WSDL文件,
./node()/message[1]/part
./node()/message[2]/part
./node()/message[3]/part
它們將分別返回message元素的第一個、第二個和第三個part子元素。 由此可見節點集中的每個節點都有一個位置。 第一個節點的位置是1,第二個節點的位置是2,以此類推。
如果你想要得到除第二個以外的所有的message元素,你該怎么辦? 你可以使用position()函數取得一個節點的位置。 下面的這條查詢將返回代碼段2中的第一個和第三個message元素:
./node()/message[position()!=2]
position()函數只是返回指定值所表示的節點的位置。 謂詞[position()!=2] 把所有的message元素的位置和2做比較,然后找出位置不是2的節點。
count()函數
代碼段1中的portType元素有多少個message子元素? 數一數你就發現有兩個message元素。 在XPath中解決"多少個"這種問題是一個二步的操作。 首先,寫一個用來找到你想要統計的所有的子元素的XPath查詢。 然后地像下面給出的那樣,把 XPath查詢傳送到count()函數中:
步驟1: ./node()/message
步驟2: count(./node()/message)
count()函數統計XPath查詢所得到的節點集中的節點數,並返回這個節點數。
name()、local-name()和namespace-uri()函數
如果把下面的查詢應用到代碼段1中的WSDL文件的話,那么會出現什么情況呢?
./node()/*[5]
它返回根元素的第五個子元素(即service元素)。 service元素本身是一個完整結構,也包含子元素。 因此,這個XPath查詢的返回值實際上是一個XML節點而不僅僅是一個元素名。
name()函數返回XML節點的名稱。 例如,下面的查詢應用到代碼段1中將返回字符串"service":
name(./node()/*[5])
同樣,下面的查詢將返回字符串"wsd:definitions"(使用域名空間前綴的根元素的全名):
name(./node())
local-name()和names
pace-uri()函數與name()函數類似,除了local-name方法只返回不帶域名空間前綴的元素的局部名稱,而namespace-uri函數僅僅返回域名空間URI。舉例來說,請在代碼段1中試驗下面的查詢:
local-name(./node())
namespace-uri(./node())
第一個查詢返回一個字符串" definitions",而第二個查詢返回" http://schemas.XMLsoap.org/wsdl/ "。
String函數
我們已經知道name()、local-name()和namespace-uri()函數返回字符串。 XPath提供了許多函數用於處理字符串,比如string()、 substring()、substring-before()、 substring-after()、 concat()、starts-with()等等。 下面給出了一個例子來演示一下如何使用string()函數:
string(./node()/*[2]/part/attribute::name)
上面的這條查詢將尋找根元素的第二個子元素,然后它將得到根元素的第二子元素的所有的part子元素。 接着它將尋找part子元素的name屬性,最后它把name屬性的值轉換為一個字符串格式。 當把這條語句應用到代碼段1中的時候,它將輸出bill。
XPath也提供一些布爾函數,返回"true/false",研究一下下面的這條查詢:
boolean(./node()/message)
當把它應用到代碼段1的時候,它返回true。 這是因為boolean()函數判斷一個XPath查詢產生的節點集是否為空(在我們的例子中,根元素包含兩個message子元素)。 如果是空,boolean()函數返回false,否則返回true。
一個復雜的WSDL處理實例
下面的WSDL處理方案使用了我們前面討論過的所有的XPath概念。 這個方案的檢索要求如下:
尋找一個service元素,這個元素是definitions元素(根元素)的一個直接子元素,並且name屬性與definitions元素的name屬性匹配。 然后察看service元素,尋找一個port元素,這個port元素的binding屬性與definitions元素的直接子元素binding的name屬性匹配。
這個WSDL過程可以用四步完成:
1. 查找definitions元素的name屬性值。 下面給出的XPath查詢(從代碼段中返回字符串BillingService)執行這步操作:
string(//node()[1]/@name)
2. 然后查找name屬性匹配definitions元素的name的service元素。 下面的查詢將返回所需要的service元素:
./node()[1]/service[@name=string(//node()[1]/@name)]
3. 然后查找binding元素的name屬性值:
string(//node()[1]/binding/@name)
4. 最后尋找需要的port元素:
./node()[1]/service[@name=string(//node()[1]/@name)]
/port[@binding=string(//node()[1]/binding/@name)]
這個實例說明XPath謂詞可以包含簡單邏輯條件,函數調用乃至完整的XPath查詢。
使用XPath處理WML
WML是WAP Forum定義的一種XML語言。 WML為小型設備的顯示提供了一種表現格式。 WML對於一個小型設備就好像HTML對於一台個人電腦一樣。
想象一下,一個WML文件是由一組卡片(card)組成,每個卡片由一個card元素封裝。 代碼段3是一個簡單WML文件,只包含兩個card元素。
下面的XPath查詢將返回代碼段3中包含在第一個卡片之內(這卡片id是" first")的所有的p(paragraph)元素:
./node()/card[string(@id)="first"]/p
下面這個查詢返回第二個卡片中的第一段的文本內容:
string(./node()/card[string(@id)="second"]/p[1]/text())
實現XPath謂詞與函數
我們現在將看看如何在我們前面的那個簡單的XPath引擎中插入謂詞與函數的支持。
四個偽代碼類XPathExpression(代碼段4)、XPathLocationStep(代碼段5)、XPathResult(代碼段6)和Predicate(代碼段7)組成了支持謂詞與函數的更新的版本。 我們在上一篇文章介紹的XPath引擎的基礎上,增加了下列功能,使之更加強大:
1. XPath可以返回各種類型的數據。 XPath可以返回節點、字符串、數字和布爾變量。 我們設計的XPath引擎只支持XML節點作為返回數據類型。 我們現在已經提供了一個名為XPathResult(見代碼段6)的類來支持不同的數據類型。 基於我們的設計的實現需要擴展為每種數據類型分別地擴展XPathResult。
2. 更新的設計現在包括一個支撐函數的結構。 一個函數調用可以發生在一個XPath查詢開始時,也可以發生在任何XPath location step。 因此,XPathExpression類(代碼段4)和XPathLocationStep類(代碼段5)現在都添加了對函數調用的支持。
3. 我們還提供了一個單獨的類用於支持謂詞(見代碼段7)。 一個謂詞可以只由一個邏輯條件組成也可以由一個完整的XPath查詢組成。 因此,Predicate類構造器將判斷謂詞到底是一個完整的查詢還是僅僅只是一個條件。 如果是一個完整的XPath查詢,Predicate表達式將實例化一個新的XPathExpression對象,否則它將只是取得邏輯條件的值。
小結
在前面,我們討論XPath中謂詞與函數的語法和使用。 我們介紹WSDL和WML處理的實例並說明了如何構成更加復雜的XPath查詢。 最后,我們增強了在第一篇文章中介紹的XPath引擎的功能。