實用數(shù)據(jù)綁定: 使用 XPath 作為數(shù)據(jù)綁定工具,第 1 部分:使用 XPath 選擇 XML 文檔部分
級別: 中級
Brett McLaughlin, 作家,編輯, O'Reilly Media, Inc.
2005 年 12 月 22 日
通 常人們不把 XPath 看作是一種數(shù)據(jù)綁定 API。除了作為其他規(guī)范的一部分以外,XPath 甚至還沒有引起 XML 世界的過多關(guān)注。但是只要真正理解了 XPath 是什么以及如何使用它,特別是在 Java? 編程環(huán)境中,它就會成為一種強大的數(shù)據(jù)綁定工具,常常優(yōu)于傳統(tǒng)的數(shù)據(jù)綁定 API 如 JAXB 或 JaxMe。Brett McLaughlin 的 “實用數(shù)據(jù)綁定” 專欄首先用兩期文章探討了作為數(shù)據(jù)綁定工具的 XPath。
到目前為止,本專欄一直專注于數(shù)據(jù)綁定的傳統(tǒng)定義和用法:將 XML 文檔轉(zhuǎn)換成 Java 表示并用于通常的 Java 方法中(比如 getName()
和 setAddress()
)。
然后將 Java 對象再轉(zhuǎn)換成 XML 表示,通常序列化(保存)到磁盤。我還討論過另一種方向的轉(zhuǎn)換,即把 Java 對象轉(zhuǎn)換成 XML,
然后使用轉(zhuǎn)換的 XML(可以通過網(wǎng)絡(luò)連接發(fā)送出去,或者作為應(yīng)用程序中另一個消費 XML
的組件的輸入)。這都是非常有效和實用的數(shù)據(jù)綁定的用例,但是還沒有包括所有的可能性。本文和下一期文章將介紹另一種方法,即使用 XPath
技術(shù)進行數(shù)據(jù)綁定。
如果您使用數(shù)據(jù)綁定很長時間了,通常會提供一個約束集(比如 XML 模式或 DTD),用 API 生成表示該約束集的類。然后使用這些類加載 XML 數(shù)據(jù),將其中的數(shù)據(jù)序列化為 XML。這是一種完善的解決方案,但是并非惟一的不使用 SAX 或 DOM 等底層 API 從 XML 文檔中讀取數(shù)據(jù)的方法。XPath,您可能聽說過這種規(guī)范,就是另一種方法。即使您沒有 聽說過 XPath,也可能在某些時候已經(jīng)用到過它。通過本文,您將了解 XPath 是什么,知道如何使用它,看看它為什么像一種數(shù)據(jù)綁定 API,甚至可以漂亮地完成其他數(shù)據(jù)綁定任務(wù)很難實現(xiàn)的工作。
XPath 是最常用而不為人知的 XML 技術(shù)之一。它實際上是最 常用的 XML 技術(shù) —— XML 轉(zhuǎn)換必不可少的組成部分。在將來的 Web 中它也起著重要的作用,因為它對 XLink 和(特別是) XPointer 至關(guān)重要。甚至 XForms 也暗中要用到它。
那么人們?yōu)槭裁床话?XPath 看作一種獨立的技術(shù)呢?很大程度上是因為其他語言中獨立處理 XPath 的 API 還剛剛出現(xiàn)。但是如果使用過 XSLT 或 XPointer,或者處理過更先進的 XML Schema,那么您可能已經(jīng)走到前面了。
如果使用過 XSLT,幾乎可以肯定曾經(jīng)看到過 XPath 而且至少在一定程度上看著面熟,即使您并沒有意識到。看一看 清單 1,這是一個非常簡單的 XSL 樣式表的一部分。
清單 1. XSL 樣式表中的 XPath
|
比方說,當您看到 select="address"
或者 select="zipCode"
的時候,您看到的就是應(yīng)用中的 XPath。包圍在引號中的文本就是 XPath 表達式,盡管非常簡單,要讓樣式表工作卻是不可或缺的。
實際上,即使編寫最簡單的 XML 轉(zhuǎn)換,如果樣式表中沒有 select
會怎么樣?根本不可能!這是因為 XPath 是 XML 轉(zhuǎn)換從而也是 XSLT 的完整的一部分。如果您認為自己是一位 XSL 專家,您可能比自己認為的更熟悉 XPath。
XPointer 是另一種大量使用 XPath 的 API,盡管不像 XSL 那么常用,但是也逐漸展露頭角。清單 2 展示了一篇文檔中指向另一篇文檔的鏈接。
清單 2. XPointer 上下文中 XPath 的用法
|
![]() |
|
這是一段稍微復(fù)雜的 XPath。它選擇了根元素 cds
中的 cd
元素,該元素有一個 title
屬性等于 August and Everything After
,這些內(nèi)容都在 cd.xml 文檔中。討論這些似乎有點為時過早,但是先不要擔心語法。關(guān)鍵是要看到 XPointer 和 XSL 一樣大量使用了 XPath,事實上沒有它 XPointer 就無法存在。再說一次,XPath 是用于選擇數(shù)據(jù)的重要組件。
XForms 是一種相對較新的 XML 技術(shù),比不上 XSL 普及,甚至還比不上 XPointer 或 XLink。但是仍然值得提一下,因為它在 input
元素的 ref
屬性中使用 XPath 表達式。可以設(shè)置一個 input 并將其綁定到 XML 文檔中的特定元素(或?qū)傩裕?清單 3 所示。
清單 3. XForms 組件使用 XPath 引用綁定到表單的 XML 文檔
|
清單 3 中的 XForms 語句將輸入控件綁定到 XHTML 文檔的特定元素。在這里 XPath 同樣是指定具體元素的關(guān)鍵。后面將提到它允許使用一些非常高級的選擇條件。
在很大程度上 XForms 是一種尚未得到支持的技術(shù),但是當使用 XForms 的時候,現(xiàn)在學習的 XPath 是一種很好的工具。將這些關(guān)于 XSL、XLink 和 XInclude 的知識結(jié)合起來就夠了!
![]() ![]() |
![]()
|
現(xiàn)在您已經(jīng)確信 XPath 非常有用而且無處不在了,下面開始學習語法吧。如果剛接觸 XPath,本課程將幫助您開始學習和領(lǐng)會 XPath 的結(jié)構(gòu)。如果是一位 XSL 或 XLink 和 XPointer 老手,也可了解為什么 要構(gòu)造那些奇怪的路徑。您可能已經(jīng)知道獲取感興趣的數(shù)據(jù)的其他方法,也許是更好的方法。
首先要認識到稱 XPath 為一種語言有點過于鄭重其事。它實際上是選擇和處理 XML 文檔中的內(nèi)容的一種語法。即使在 XPath 中使用的函數(shù)和運算符也都與選擇有關(guān)。在 XPath 中不會創(chuàng)建變量,比方說不可能運行 一個 XPath 程序。沒有這些東西。如果您開始認識到 XPath 是一種巧妙的、有益的選擇 XML 元素和屬性的方法,然后使用選擇的值,您就已經(jīng)領(lǐng)先于大多數(shù) XML 開發(fā)人員了。XPath 不是要對 XML 做 什么事情,而是要從 XML 中取得一些內(nèi)容。
![]() |
|
對于 XPath 來說第一步是找到一個引用元素的句柄。可是在選擇元素之前需要理解當前上下文。上下文就是在 XML 文檔中的位置。比方說,您可能在根元素上,那根元素就是上下文。當然也可能在第一個 person
元素的第二個 address
元素上。在開始移動和選擇內(nèi)容之前,需要知道上下文。
只要理解了上下文,就可以掌握 XPath 語法了。以 清單 4 中的 XHTML 文檔為例。
清單 4. Head First Lounge 的 XHTML
|
如果在 清單 4 中的上下文是 html
元素,就可以直接使用名稱選擇一個子元素。比如選擇 body
元素可使用 XPath 表達式 body
。如果想訪問嵌套在 body
中的 h1
元素則使用 body/h1
。如果認為這有點類似于目錄路徑,那么就對了。選擇元素使用 元素名/子元素名
這樣的形式。
不過在前進之前要認識到 XPath 表達式可能返回多個 元素。結(jié)果元素集稱為節(jié)點集。(事實上,用 XPath 選擇的所有實體 —— 元素、屬性和值 —— 都稱為節(jié)點。)比如下面的路徑:
|
清單 4 中 body
有五個不同的 p
子元素,因此那個表達式返回包含五個節(jié)點的節(jié)點集(正如您所期望的,不 只是第一個 p
元素)。注意,有時候您得到了預(yù)期的結(jié)果,但是很可能比您打算 要求的多。
還有更多需要注意的地方。現(xiàn)在給出的表達式僅僅返回實際的元素。可能還要獲得這些節(jié)點的值。如果需要元素的值(假設(shè)元素包含文本數(shù)據(jù)),需要使用 text()
函數(shù)。要獲得第一個 h1
的文本,應(yīng)使用 body/h1/text()
,對于清單 4 將返回 Our Elixirs
。
當然可以選擇的不僅僅是元素。前面已經(jīng)提到還可以選擇屬性。選擇屬性的時候要在屬性名前使用 @
符號,其他方面和元素都一樣。
再回到 清單 4 這個例子,可以使用 head/meta/@http-equiv
返回 meta
元素 http-equiv
屬性的值。類似地,head/link/@type
返回 link
元素的 type
屬性。
必須知道,選擇屬性的時候您得到的是它的值,而不像元素那樣返回節(jié)點。因此對于屬性選擇器,返回值(雖然不確切但是很有用)是一個值,對于一個元素則是一個節(jié)點集(包含一個節(jié)點)。
目前為止看到的都是理想的情況,特別是使用文檔的根作為上下文比較簡單。但是顯然情況并非總是如此。這正是目錄結(jié)構(gòu)的真正有用的地方。要從當前上下文移動到上一級,只要使用 ..
運算符。比方說,假設(shè)上下文是 body
元素,現(xiàn)在需要獲得網(wǎng)頁的標題(包含在文檔 head
下的 title
元素中),可以使用 ../head/title/text()
。如果您開始感覺到就像是改變 UNIX 或 Mac OS X 終端上的目錄,這就對了!
如果上下文是 body
中第二個 p
的 img
元素,要得到標題該怎么辦呢?可以使用 ../../../head/title/text()
。但是數(shù)這些 ../
很快就變得單調(diào)乏味了。因此能直接跳到根元素就好了,再想一想 UNIX,使用 /
返回到根元素就毫不奇怪了。同樣的選擇器更簡單的形式為 /html/head/title/text()
。雖然沒有縮短,但是更清楚了。有了 ../
和 /
之后,基本上就能移動到需要的任何地方了。
考慮到使用一個 XPath 表達式可以選擇多個節(jié)點,事情就更有趣了。您可以使用對應(yīng)多個節(jié)點的簡單表達式,比如 /html/body/h2
來進行多重選擇。但是也可使用通配符 進一步精化這些選擇。XPath 中有三個統(tǒng)配符:
-
*
與任何元素匹配,無論元素名是什么 -
node()
與所有的節(jié)點類型匹配(元素、文本節(jié)點、注釋、屬性,等等) -
@*
與所有的屬性節(jié)點匹配,無論屬性名是什么
比如,可以使用 /html/body/*
選擇 body
元素的所有直接子元素。也可用 /html/head/meta/@*
選擇 meta
標記的所有屬性。
所有這些情況下,要記住您得到的是一個節(jié)點集,因此不應(yīng)該滿足于處理某個值就結(jié)束(除非您已經(jīng)選擇了一個屬性,稍后要講到)。但是,只要使用方法、函數(shù)或模板處理多個節(jié)點,這些技術(shù)就很重要。
![]() ![]() |
![]()
|
基本的東西很好,但有時候還需要點特別的讓您身邊的同事眼花繚亂的東西,或者需要一點特殊的 功能。這種情況下這些基礎(chǔ)可能就不夠了。雖然很難巨細無遺地介紹 XPath,但下面給出一些高級技巧,可以幫助您在 XPath 應(yīng)用程序中取得需要的節(jié)點(或節(jié)點集)。
到目前為止,看到的路徑都是選擇一個節(jié)點,并假設(shè)您知道該節(jié)點在何處。比方說,您知道 title
、img
或 p
在文檔中什么位置,只需要導(dǎo)航到這些元素。但有時候可能希望打破這些結(jié)構(gòu),僅僅擷取某種元素而不管其位置(或者無論給定的起始上下文的位置)。為此可使用后代選擇器。
后代選擇器用雙斜線 //
表示。使用它告訴 XPath 選擇所有指定的節(jié)點,無論嵌套得多深。比如這個簡單的特定于 XHTML 的 XPath:
|
XPath 選擇嵌套在 XHTML 的 body
元素中的所有table
元素,不論直接嵌套在 body
中(如 /html/body/table
),還是嵌套了多層(如 /html/body/table/tr/td/table
)。這種情況下,嵌套的 table
和頂層 table
同時被選中。
![]() |
|
當與屬性結(jié)合使用的時候就變得很有趣了(使用 @
)。比方說,假設(shè)要選擇具有 id
屬性的所有元素。可使用 //@id
,首先跳回根元素然后選擇文檔中的所有 id
屬性。但是實際上要訪問的是元素而不是屬性,因此需要從屬性上移一層到包含這些屬性的元素:
|
您應(yīng)該嘗試結(jié)合這些不同的方法,會看到一些很有意思的結(jié)果。
假設(shè)有一個條件希望作為匹配的基礎(chǔ),比如需要 class
屬性值為 greentea
的 p
元素。如果知道如何使用方括號,這一點在 XPath 中很容易做到。下面是一個例子:
|
方括號用于指定條件,可使用 =
甚至 <
和 >
。也許要用一個范圍更廣的表達式:
|
明白了嗎?甚至還能更進一步,選擇屬于 greentea
類的所有 元素而不論其元素類型是什么:
|
這個表達式可能看起來有點奇怪,但很容易解釋。首先,//
意味著從根元素開始選擇所有的元素(與后面的選擇器及條件匹配)而不論其嵌套的位置。然后 @class
選擇文檔中所有的 class
屬性。后面的 [.="greentea"]
有點神秘。="greentea"
部分容易理解,它用 greentea
匹配等式左側(cè)的值。在這里就是 .
,這個標志還沒有見到過。但是再想一想目錄,..
選擇父節(jié)點(或者父目錄),.
則選擇當前節(jié)點。因此 //@class[.="greentea"]
選擇值為 greentea
的所有 class
屬性。然后再移動到這些屬性所在的元素:
|
現(xiàn)在看起來有點不可思議,但是應(yīng)該熟悉這類奇怪的表達式。當需要訪問特定的元素、屬性或節(jié)點集時它們很有用。
隨著越來越多的使用屬性選擇器,最終將與處理節(jié)點一樣經(jīng)常地使用值(來自屬性)。如果處理過非常典型的 XML,一定會遇到數(shù)字。XML 文檔常常把數(shù)字值放在屬性中(有時候在元素中)。因此這一節(jié)討論 /people/person[firstname = "John"]/@born
和 /people/person/numChildren/text()
這類表達式的結(jié)果(當然,使用元素表示子女的個數(shù)不夠典型,不過就用下面的例子吧)。
這時候您會發(fā)現(xiàn) XPath 的計算能力很有用。可以像其他編程語言中那樣使用 +
、-
和 *
。此外 div
用于除法,mod
則表示求模(兩數(shù)相除的余數(shù))。比如,假設(shè) XML 文檔中包含用四位數(shù)表示的出生年份,現(xiàn)在需要改成兩位數(shù)的形式,首先用下面的表達式取得實際的出生年份:
|
這樣就得到了出生年份(可能是 1976 或者 1945)。然后只需要減去 1900:
|
當然這種方法很有限,新千年出生的孩子和那些歷史人物都會造成這一公式的失敗。因此應(yīng)該用 mod
:
|
(順便說一下,應(yīng)該告訴那些研究 Y2K 的人您省略了年份的前兩位數(shù)。)
最后,XPath 還提供了一些很棒的字符串處理功能。XML 基本上都是文本,屬性值和元素中的數(shù)據(jù)通常是文本,因此不用奇怪 XPath 支持某些字符串操作。下面僅僅是 XPath 提供的處理字符串的少數(shù)函數(shù):
-
string()
將數(shù)據(jù)轉(zhuǎn)換成字符串格式,如果還不是字符串的話。 -
starts-with(full-string, start-string)
返回一個 Boolean 值,檢查full-string
是否以start-string
開始。 -
contains(full-string, contains-string)
返回一個 boolean 值,檢查full-string
是否包含contains-string
。 -
string-length(string)
返回string
的長度。 -
normalize-space(string)
去掉string
兩端和內(nèi)部的空格。
大部分函數(shù)的功能都很明確。starts-with("McLaughlin", "Mc")
返回 true
,contains("McLaughlin", "augh")
同樣如此。string-length("Brett")
顯然返回 5
,normalize-space(" Brett McLaughlin ")
則返回 "Brett McLaughlin"
。是不是很簡單?當然這些可用于所有 XPath 表達式的返回值,比如 /html/body/p[class='greentea']
。因此要獲取 清單 4 中 p
的文本可用 normalize-space()
:
|
更妙的是可用 string()
取出多個元素的文本。比如,如果希望取得 清單 4 所示 XHTML 中所有p
元素的所有 文本,可用:
|
分析最后一個例子可以了解很多內(nèi)容:
-
//p
選擇文檔中所有的p
元素,無論其位于何處。 -
string(//p)
獲取這些元素的內(nèi)容組成一個大字符串。但是字符串中包含很多無用的空白,因此還要進一步處理。 -
normalize-space(string(//p))
規(guī)范化內(nèi)容中的空白字符,得到您希望的文本。
![]() ![]() |
![]()
|
這是一篇關(guān)于數(shù)據(jù)綁定的文章嗎?
現(xiàn)
在您可能已經(jīng)忘記正在閱讀的是一篇關(guān)于數(shù)據(jù)綁定的文章。但是先不要忙于跟過去幾年中您讀過的其他 X* 文檔和 API
比較。您讀到的實際上是另一種數(shù)據(jù)綁定方法。考慮這樣一種觀點,數(shù)據(jù)綁定是在 Java 代碼中保留 XML
文檔邏輯(或者語義,如果您喜歡的話)意義。如果 XML 文檔中的 address
元素包含 street
、city
和 state
子元素,想要用 getAddress().getStreet()
這樣的形式得到這些元素的值,比如:
|
這就是某種基本的數(shù)據(jù)綁定形式。但是在 XPath 中也能做到同樣的事!可以利用 address/street/text()
或者 /person[last-name="Gosling"]/address[@type="work"]/city
這樣的 XPath 表達式來得到街道名。初看起來與前一個例子不同,但是它仍然合理地使用了 XML 數(shù)據(jù),您要的是 person、address 和 street 而不是第一個子元素、第二個文本節(jié)點 或者屬性。這一點至關(guān)重要。XPath 按照邏輯而不是結(jié)構(gòu)來處理 XML。基本上數(shù)據(jù)綁定也是如此,按照邏輯處理數(shù)據(jù),而不用擔心其結(jié)構(gòu)。
為了避免誤導(dǎo),需要指出使用 XPath 仍然需要對結(jié)構(gòu)有所了解。比如,@
運算符只用于屬性,因此必須知道 type
(地址的一部分)用元素還是屬性表示。在傳統(tǒng)的數(shù)據(jù)綁定中,只需要調(diào)用 getAddress().getType()
而不需要管結(jié)構(gòu)的層次。但是,這點小小的代價是值得的,因為不需要處理大量的生成類、額外的類路徑問題、等待封送和解除封送處理以及傳統(tǒng)數(shù)據(jù)綁定的其他缺點。
只 剩下這個等式中需要在 Java 語言中增加的部分:接受 XML 文檔和 XPath 表達式,以 Java 友好的方式獲得表達式的結(jié)果。這些內(nèi)容將在第 2 部分介紹,很快就要把 XPath 與 Java 編程中其他以 XML 為中心的工具一起使用了。很多情況下您會發(fā)現(xiàn),與使用生成類和 JAXB 這樣的 API 的工具相比,XPath 是一種更好的 數(shù)據(jù)綁定工具。
![]() ![]() |
![]()
|
-
參與論壇討論。
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
-
XML Path Language (XPath)
Version 1.0:閱讀 W3C 推薦標準。
-
XML Path Language (XPath) 2.0:這是 XPath 1.0 的超集,增加了更豐富的數(shù)據(jù)類型支持,使用 XML Schema 驗證文檔時可使用這些類型信息。
- “用于數(shù)據(jù)的 XML: XPath 2.0 有哪些新特性?”(developerWorks,2002 年 9 月):了解 XPath 1.x 和 2.0 的區(qū)別。
- “XPath 入門”(developerWorks,2004 年 3 月):需要了解或復(fù)習 XPath,可以閱讀 Bertrand Portier 撰寫的這篇入門教程。
-
The Extensible Stylesheet Language Family (XSL):XPath 是 XSL 和 XSLT 的重要部分。進一步了解 W3C 的 XSL 規(guī)范家族,包括 XSL Transformations (XSLT) Version 1.0。
-
XQuery 1.0 and XPath 2.0 Data Model (XDM):伴隨著 XPath 2.0,XQuery 希望定義能同時用于兩種規(guī)范的數(shù)據(jù)模型。
- “Toward an XPath API”:通過 Leigh Dodds 的這篇文章了解導(dǎo)致最初的 1.0 XPath API 誕生的原因。
-
Java and XML
:Brett McLaughlin 撰寫的這本書的第三版(O'Reilly Media, Inc.)專門用一章討論 XPath。
-
XML in a Nutshell
:Elliotte Rusty Harold 和 W. Scott Means 撰寫的這部 XML 資料大全(O'Reilly Media, Inc.)專門有一章討論 XPath。
-
developerWorks XML 專區(qū):可以找到更多 XML 資源,包括文章、教程、技巧和標準。
-
developerWorks Java 技術(shù)專區(qū):數(shù)百篇文章、教程和技巧可以幫助您掌握大部分 Java 語言技術(shù)和相關(guān)應(yīng)用。
- IBM Certified Solution Developer —— XML 和相關(guān)技術(shù):了解如何通過認證。
![]() ![]() |
![]()
|
![]() |
||
|
![]() |
Brett McLaughlin 從 Logo 時代就開始使用計算機。(還記得那個小三角嗎?)近年來他已經(jīng)成為 Java 技術(shù)和 XML 社區(qū)最知名的作家和程序員之一。他曾經(jīng)在 Nextel Communications 實現(xiàn)過復(fù)雜的企業(yè)系統(tǒng),在 Lutris Technologies 編寫應(yīng)用程序服務(wù)器,最近在 O'Reilly Media, Inc. 繼續(xù)撰寫和編輯這方面的圖書。在他的新書 Head Rush Ajax 中,Brett 與暢銷書作家 Eric 及 Beth Freeman 為 Ajax 帶來了獲獎的創(chuàng)新方法 Head First。最近的著作 Java 1.5 Tiger: A Developer's Notebook 是關(guān)于這一 Java 技術(shù)新版本的第一本書籍,經(jīng)典著作 Java and XML 仍然是在 Java 語言中使用 XML 技術(shù)的權(quán)威圖書。 |
posted on 2006-03-18 20:47 Vincent.Chen 閱讀(594) 評論(0) 編輯 收藏 所屬分類: AJAX