級(jí)別: 中級(jí)
Brett McLaughlin, 作家,編輯, O'Reilly Media, Inc.
2006 年 1 月 23 日
一
旦理解了 XPath 語法和位置路徑,避開 DOM 和 SAX 的開銷訪問 XML 就是可能的了,而且很容易。對(duì)于 Java?
開發(fā)人員來說,幸運(yùn)的是 Java API for XML Processing(JAXP)為創(chuàng)建 XPath
表達(dá)式和在應(yīng)用程序中使用查詢結(jié)果提供了原生的 Java 解決方案。本系列文章包括兩部分,這是第 2 部分,介紹如何創(chuàng)建 XPath
請(qǐng)求、執(zhí)行請(qǐng)求和處理得到的節(jié)點(diǎn)集,都在 Java 環(huán)境中輕松地完成。
本系列的 第 1 部分
介紹了 XPath 語言及其語法。對(duì)于不熟悉 XSLT 的 Java 程序員來說可能有一點(diǎn)兒壓力。XPath 語法可能看起來有點(diǎn)兒奇怪,和基于
XML 的語法相比更像是 UNIX 的目錄路徑 —— 又稍微增加了一點(diǎn)兒東西。但是您很快就看到了 XPath 使得選擇 XML
文檔的特定部分非常容易。
|
突出的邏輯性
XML 使用兩種基本類型的名稱和路徑:邏輯性的 和結(jié)構(gòu)性的。邏輯名是領(lǐng)域特定的,通常對(duì)人們有一定的含義。包含音樂唱片信息的 XML 文檔中,邏輯名可能是 cd 、album 或 artist 。結(jié)構(gòu)名和 XML 的結(jié)構(gòu)有關(guān)。因?yàn)樗鼈兒?XML 中的結(jié)構(gòu)有關(guān),在所有文檔中都是一樣的:element 、attribute 和 node 。顯然,如果使用邏輯名,代碼會(huì)更容易閱讀,而且對(duì)一般程序員能提供更多的信息。
|
|
在實(shí)用數(shù)據(jù)綁定 系列文章中討論 XPath,原因就在于這些選擇都使用邏輯名(請(qǐng)參閱 突出的邏輯性)。比方說,不必選擇根元素第二個(gè)子元素的第一個(gè)屬性,可以使用 XPath 表達(dá)式 /cds/cd[@title='August and Everything After']
。從一定意義上說,這就是 數(shù)據(jù)綁定,因?yàn)榭梢允褂?XML 標(biāo)記而不是結(jié)構(gòu)來訪問數(shù)據(jù)。
本文主要討論使用 XPath 作為通過邏輯名從 XML 中訪問數(shù)據(jù)的方法,如 cd
、table
或 person
,而不用 firstElement
或 parent.getChildren(2)
。最終得到的代碼非常類似于數(shù)據(jù)綁定,使用邏輯名而非語義名,但是又沒有傳統(tǒng)數(shù)據(jù)綁定解決方案的初始類生成和類路徑問題。
節(jié)點(diǎn)知道
第 1 部分 中提到,XPath 選擇總是返回一個(gè)節(jié)點(diǎn) 集。和其他任何集合一樣,其中可能有零個(gè)、一個(gè)或多個(gè)成員。這里重要的概念是節(jié)點(diǎn)。節(jié)點(diǎn)可以是一段文本(比如元素的內(nèi)容)、一個(gè)元素、一條處理指令或者注釋。當(dāng)然,節(jié)點(diǎn)集就是這類事物的任意組合。
如果您是一位 DOM 程序員,就不會(huì)被節(jié)點(diǎn)這個(gè)概念難倒。DOM 根據(jù)其 Node
接口看待一切事物,因此您也就習(xí)慣于通過 Node
接口來處理注釋、元素和文本數(shù)據(jù)。如果不習(xí)慣 DOM,應(yīng)該花點(diǎn)兒時(shí)間來熟悉節(jié)點(diǎn)。節(jié)點(diǎn)對(duì)于使用 XPath 至關(guān)重要。執(zhí)行 XPath
表達(dá)式時(shí),對(duì)表達(dá)式結(jié)果進(jìn)行操作的代碼必須對(duì)節(jié)點(diǎn)操作,而不深究 XML 語義。文檔內(nèi)的導(dǎo)航是用 XPath 表達(dá)式進(jìn)行的,而不是使用 DOM
樹轉(zhuǎn)移方法從一個(gè)子節(jié)點(diǎn)移動(dòng)到另一個(gè)。
JAXP 和 XPath 基礎(chǔ)
Java 編程語言的最新版本 Java 5.0 中,對(duì) Java API for XML Processing(JAXP)和 XPath 的支持已成為標(biāo)準(zhǔn)。您可能已經(jīng)熟悉 JAXP 以及 SAX、DOM 和 XSL 編程中使用的 javax.xml.parsers
和 javax.xml.transform
包。(如果不熟悉的話,請(qǐng)參閱本文后面列出的一些 參考資料。)對(duì) XPath 的支持主要來自于一個(gè)新的包 javax.xml.xpath
。
檢查 JAXP 和 Java 版本
JAXP 1.3 包含在所有的 Java 5.0 下載包中。為了確保安裝了 JAXP 1.3,請(qǐng)?jiān)诿钐崾痉螺斎胍韵旅睿?/p>
應(yīng)該看到類似于 清單 1 所示的結(jié)果。
清單 1. 命令行提示符下的 Java 5.0
java version "1.5.0_02" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-56) Java HotSpot(TM) Client VM (build 1.5.0_02-36, mixed mode, sharing)
|
如果版本是 1.5 或更高,則已經(jīng)有了 JAXP 1.3,可以確信您有了本文中使用的 XPath API。
簡(jiǎn)單的 XML
還需要一些供操作的 XML。本文中所有的例子都將使用 清單 2 中所示的簡(jiǎn)單 XML 文檔。這個(gè)文檔并不復(fù)雜,但是足以說明 90% 的 XPath 編程中所需要的各種 XPath 函數(shù)。
清單 2. XPath 代碼中使用的 XML 示例文檔
<?xml version="1.0"?>
<cds> <cd title="August and Everything After" artistId="23"> <style>rock</style> <style>alternative</style> <tracks> <track id="1">Round Here</track> <track id="2">Omaha</track> </tracks> </cd>
<cd title="This Desert Life" artistId="23"> <style>rock</style> <style>alternative</style> <tracks> <track id="1">Hangin' Around</track> <track id="2">Mrs. Potter's Lullaby</track> </tracks> </cd>
<cd title="Crash" artistId="46"> <style>alternative</style> <style>jazz</style> <style>rock</style> <tracks> <track id="5">#41</track> <track id="3">Crash Into Me</track> </tracks> </cd>
<artists> <artist id="23" type="group"> <group-name>Counting Crows</group-name> <website>http://www.countingcrows.com</website> <member type="lead-singer"> <firstName>Adam</firstName> <lastName>Duritz</lastName> <website>http://adam.countingcrows.com</website> </member> </artist>
<artist id="46" type="group"> <group-name>Dave Matthews Band</group-name> <website>http://www.dmband.com</website> <member type="lead-singer"> <firstName>Dave</firstName> <lastName>Matthews</lastName> </member> <member type="instrumentalist"> <firstName>Boyd</firstName> <lastName>Tinsley</lastName> <instrument>violin</instrument> <instrument>viola</instrument> </member> </artist>
<artist id="101" type="solo"> <website>http://www.patdonohue.com</website> <member> <firstName>Pat</firstName> <lastName>Donohue</lastName> <instrument>acoustic guitar</instrument> </member> </artist> </artists> </cds>
|
將該文檔保存在本地機(jī)器上便于從 Java 代碼中訪問的地方(請(qǐng)參閱 下載)。如果希望使用自己的 XML 文檔也完全可以,只要調(diào)整示例代碼和文檔的結(jié)構(gòu)與邏輯名匹配即可。
再回到 DOM
DOM 程序員已經(jīng)熟悉了節(jié)點(diǎn)的概念,這是理解 XPath 表達(dá)式和這些表達(dá)式的返回值不可或缺的。使用 XPath 時(shí),DOM 程序員還有一個(gè)優(yōu)勢(shì),即在應(yīng)用任何 XPath 表達(dá)式之前實(shí)際使用 DOM 獲得對(duì) XML 文檔的訪問。這毫不奇怪:XPath 現(xiàn)在是 JAXP 的核心部分,而 JAXP 也提供了完整的 DOM 支持。
將 XML 轉(zhuǎn)化成 DOM 樹
使用 XPath 的第一步是將目標(biāo) XML 文檔作為 DOM 樹讀入,如 清單 3 所示。
清單 3. 將 XML 加載到 DOM 樹中
package com.ibm.dw.xpath;
import java.io.File; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document;
public class SimpleXPath {
File xmlInput;
public SimpleXPath(String xmlFilename) { xmlInput = new File(xmlFilename); if (!xmlInput.exists()) throw new RuntimeException("Specified file does not exist!"); }
public void test() throws Exception { Document doc = buildDocument(); }
private Document buildDocument() throws Exception { DocumentBuilder builder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); return builder.parse(xmlInput); }
public static void main(String[] args) { if (args.length < 1) { System.err.println("Usage: java com.ibm.dw.xpath.SimpleXPath " + "[XML filename]"); return; }
try { SimpleXPath tester = new SimpleXPath(args[0]); tester.test(); } catch (Exception e) { System.err.println("Exception occurred: " + e.getMessage()); e.printStackTrace(System.err); return; } } }
|
清單 3
中的代碼沒有多少值得注意的地方。它僅僅從命令行接收 XML,然后使用 JAXP DOM API 將其加載到 DOM 樹中。如果曾經(jīng)使用過
JAXP,或者在沒有 JAXP helper 類的情況下使用過
DOM,就會(huì)非常熟悉這種操作。(如果這些代碼看起來有些嚇人,那么可能需要查閱一下 參考資料 中列出的 DOM 文章,然后再回到本文。)
設(shè)置 XPath 上下文
第 1 部分 中提到,在對(duì)任何 XPath 表達(dá)式求值之前,必須知道開始的上下文。比方說,選擇 ID 為 23
的 artist
節(jié)點(diǎn),XPath 表達(dá)式取決于上下文是 XML 文檔的根節(jié)點(diǎn),還是第三個(gè) cd
元素的第二個(gè) track
元素。
|
上下文的縮寫形式
使用絕對(duì)路徑的 XPath 表達(dá)式常常要比通過 DOM 樹進(jìn)行導(dǎo)航更容易。要記住,如果在 XPath 表達(dá)式前使用雙斜杠(// ),那么這個(gè)表達(dá)式就從樹的根節(jié)點(diǎn)開始。這樣就可以方便地繞過任何上下文,因此不需要導(dǎo)航到樹中的特定節(jié)點(diǎn)。這通常意味著 XPath 表達(dá)式可能稍微長(zhǎng)一點(diǎn),但是常常能夠避免大量的 DOM 樹轉(zhuǎn)移代碼。
|
|
顯然,需要能夠在 XML 文檔中移動(dòng)來設(shè)置上下文,并相應(yīng)地修改 XPath 表達(dá)式。從這里開始,真正的數(shù)據(jù)綁定 API 和 XPath 的界線漸漸分明了。在 XPath 中,雖然表達(dá)式是純邏輯性的(使用 cd
或 artist
之類的名稱),但是仍然需要使用 DOM 在 XML 中移動(dòng)位置。這意味著仍然需要使用 Element
、Text
這樣的 XML 結(jié)構(gòu)成分,仍然需要適當(dāng)?shù)臉浔闅v。當(dāng)然,如果讀入 XML 文檔,像 清單 3 那樣,那么最初的上下文就是文檔根節(jié)點(diǎn),如果從根開始編寫表達(dá)式就不用太擔(dān)心樹的遍歷。
我認(rèn)為這就是數(shù)據(jù)綁定!
有些讀者可能已經(jīng)在撓頭了。本文是討論如何將 XPath 用于數(shù)據(jù)綁定的,但是到現(xiàn)在說的一直是 DOM 和結(jié)構(gòu)。XPath 是數(shù)據(jù)綁定很好的替身,但是這種 API 天生具有一定程度的結(jié)構(gòu)。事實(shí)上,甚至位置路徑也在某種程度上使用結(jié)構(gòu)://cds/cd[@title='This Desert Life']
假設(shè) cds
元素是目標(biāo)文檔的根元素,cd
元素是根的子元素。這和使用 getCDs().getCD("This Desert Life")
之類的代碼沒有多少差別。這兩種數(shù)據(jù)綁定方法沒有顯著的不同。
至于更明顯的差異,就是在 XPath 代碼中必須處理 DOM。但是,這點(diǎn)不同也能夠在很大程度上隱藏起來。再看一看 清單 3,加載文檔過程中所有和 DOM 有關(guān)的工作都通過 buildDocument()
方法抽象出來。返回的 DOM Document
對(duì)象是 DOM 專有的,但是沒有多少地方需要直接操作這個(gè)對(duì)象。因此,不需要傳統(tǒng)數(shù)據(jù)綁定方法所需要的一些類生成代碼,XPath 就提供了數(shù)據(jù)綁定的多數(shù) 好處:
- 從 DOM 和 SAX 這樣的 API 中抽象出來
- 使用邏輯命名法而不是結(jié)構(gòu)命名法
- XML 文檔和 Java 對(duì)象之間的寬松映射
這就使其成為 XML 編程中一種非常輕量的數(shù)據(jù)綁定方法。
使用 XPath 工廠
您已經(jīng)看到,稍微了解一點(diǎn)兒 DOM 知識(shí)可以大大方便對(duì) XPath 的學(xué)習(xí)。如果在 DOM 的基礎(chǔ)上再熟悉 JAXP API,那么您就遙遙領(lǐng)先了。比方說,在 JAXP 中無論是用 SAX 還是 DOM ,基本步驟都是:
- 創(chuàng)建新的解析器工廠。
- 創(chuàng)建新的解析器或者文檔構(gòu)造器(builder)。
- 解析/構(gòu)造目標(biāo) XML。
同樣的步驟也適用于 XPath 編程:
- 創(chuàng)建新的 XPath 工廠。
- 創(chuàng)建新的 XPath 求值程序(evaluator)。
- 創(chuàng)建和計(jì)算 XPath 表達(dá)式。
如果掌握了這些 SAX 或 DOM 背景知識(shí),可能只需看一眼 XPath API 就能開始編程了。不過先等一等。還需要知道 XPath 一些特殊的地方,雖然您會(huì)發(fā)現(xiàn)本節(jié)的內(nèi)容熟悉而且非常簡(jiǎn)單。
JAXP 和工廠
如果不熟悉 JAXP 或者使用時(shí)間不長(zhǎng),可能就不會(huì)理解在這種 API 中工廠模式的重要性和價(jià)值。JAXP 實(shí)際上不是一種 API
而更像是一種抽象,它允許不必編寫廠商專用的代碼,就可以使用特定廠商的 SAX、DOM 和 XPath 實(shí)現(xiàn)。為此,需要加載一個(gè)工廠實(shí)例,如 SAXParserFactory
或 DocumentBuilderFactory
,然后 JAXP 就會(huì)把這個(gè)類連接到這些結(jié)構(gòu)的廠商實(shí)現(xiàn)。因此雖然在代碼中直接使用 SAXParserFactory
,JAXP 必須在幕后使用 Apache Xerces 來處理真正的文檔解析。
同樣的抽象在 XPath 中更具有高瞻遠(yuǎn)矚的性質(zhì)。和豐富的 XML 解析與轉(zhuǎn)換工具相比,目前還沒有很多 XPath
處理程序。但是,通過使用工廠模式,就會(huì)發(fā)現(xiàn)以后如果需要可以更方便地替換 XPath 實(shí)現(xiàn)。具體來說,XPath
工廠允許使用不同的對(duì)象模型。默認(rèn)情況下,節(jié)點(diǎn)集是從 DOM Node
和 NodeSet
中的 XPath 表達(dá)式返回的(在稍后的 使用 XPath 表達(dá)式
一節(jié)中將會(huì)看到)。但是,有可能希望使用 JDOM 結(jié)構(gòu)或者 dom4j 提供的對(duì)象模型。雖然這些 Java/XML API 還沒有用于
JAXP 的 XPath 引擎,但是可能很快就會(huì)出現(xiàn)。JAXP
工廠模型意味著很容易插入這些模型。因此不要為使用工廠模式所要求的額外步驟感到惱火,它們的存在為了提供便利,增加靈活性。
|
重要的語句
本文中假設(shè)當(dāng)在代碼清單中增加新的類和接口時(shí)已經(jīng)添加了必要的 import 語句。除非特別說明,所有的 XPath 類和接口都在 javax.xml.xpath 包中。
|
|
創(chuàng)建 XPath 工廠
您已經(jīng)知道,使用 XPath 時(shí)最主要的包是 javax.xml.xpath
。毫不奇怪,我們需要的工廠被稱作 XPathFactory
,也在這個(gè)包中。JAXP 老兵可能已經(jīng)猜到,要使用 newInstance()
創(chuàng)建新的工廠,如 清單 4 所示(添加到 清單 3 中所示的代碼中)。
清單 4. 創(chuàng)建新的 XPath 工廠
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); }
|
清單 4 中沒有什么特別之處。僅僅使用默認(rèn)的對(duì)象(即 DOM,至少在 1.3 版中如此,而且近期不大可能改變)創(chuàng)建了一個(gè)新的 XPath 工廠實(shí)例。
使用其他對(duì)象模型
因?yàn)?JAXP 已經(jīng)開始接受其他 XML 對(duì)象模型,您可能希望不使用 JAXP 默認(rèn)的 DOM 模型。這種情況下可以為 newInstance()
指定一個(gè)字符串統(tǒng)一資源標(biāo)識(shí)符(Uniform Resource Identifier,URI),如 清單 5 所示。
清單 5. 指定字符串 URI
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory with an alternate object model XPathFactory factory = XPathFactory.newInstance( "http://jdom.org/jaxp/xpath/jdom"); }
|
默認(rèn)情況下,該 URI 被指定為 javax.xml.xpath.XPathConstants.DOM_OBJECT_MODEL
中的 http://java.sun.com/jaxp/xpath/dom
。因此 清單 5 中的代碼等價(jià)于 清單 6 中所示的代碼。
清單 6. 創(chuàng)建新的 XPath 工廠,比較麻煩的方法
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance( XPathConstants.DOM_OBJECT_MODEL); }
|
無論采用何種方法創(chuàng)建,工廠都是使用 XPath 時(shí)的第一步。因此測(cè)試類中一定要包含工廠創(chuàng)建代碼,以便能夠使用 XPath
類。
使用 XPath 類
創(chuàng)建工廠之后,還需要從工廠轉(zhuǎn)移到能夠真正計(jì)算表達(dá)式并與 XPath 環(huán)境交互的對(duì)象。XPathFactory
不能 讓您計(jì)算表達(dá)式(至少不能直接計(jì)算),它僅僅是從代碼中獲得廠商的 XPath 引擎和對(duì)象模型,而不需要很多廠商專用代碼的一種方式。因此,建立這種聯(lián)系后,還需要獲得一個(gè)能夠 處理 XPath 表達(dá)式求值的對(duì)象。這并非其他對(duì)象,正是 XPath
對(duì)象登臺(tái)亮相了。
創(chuàng)建 XPath 對(duì)象
可以使用工廠的 newXPath()
方法創(chuàng)建新的 XPath
。清單 7 表明這個(gè)步驟是多么簡(jiǎn)單。
清單 7. 創(chuàng)建 XPath 類的新實(shí)例
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath(); }
|
如果這些代碼看起來簡(jiǎn)單得可笑,那就對(duì)了。所有其他的 XPath 處理工作都要從這個(gè)類開始,雖然看起來簡(jiǎn)單,但是應(yīng)該習(xí)慣于輸入這些代碼。
配置和重置 XPath 對(duì)象
一旦得到了 XPath
的實(shí)例,就可以開始通過改變上下文、處理名稱空間和計(jì)算表達(dá)式來配置它。其中一些主題超出了本文的范圍,但是如果遇到 XPath 代碼不工作的情況,可能是因?yàn)槭褂昧隋e(cuò)誤配置的 XPath
對(duì)象。如果是這種情況,或者懷疑是這樣,可以通過調(diào)用 xpath.reset()
重新將該對(duì)象恢復(fù)為初始配置。這樣通常能夠消除看到的問題,在調(diào)試過程中非常有用。
使用 XPath 表達(dá)式
現(xiàn)在您可能已經(jīng)為使用 XPath 表達(dá)式作好了充分的準(zhǔn)備。所幸的是,到現(xiàn)在為止您做的準(zhǔn)備工作使這項(xiàng)任務(wù)變得很容易了。只需要用字符串創(chuàng)建一個(gè)新的表達(dá)式(要記住,這些都只是在 XML 文檔中定位的引用,常常被稱為位置路徑)。
創(chuàng)建 XPath 表達(dá)式
清單 8 將一個(gè) XPath 表達(dá)式賦給了一個(gè)字符串變量,這里沒有戲法,也沒有特殊的語法。僅僅使用一般的 Java String
,然后將 XPath 表達(dá)式賦給它去求值。
清單 8. 創(chuàng)建 XPath 表達(dá)式
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; }
|
因?yàn)?清單 8 沒有包含 XPath 專用的步驟,必須非常小心。其中也沒有任何錯(cuò)誤檢查。有可能漏掉 title
屬性中的 @
符號(hào)(我第一次編寫這段代碼時(shí)就這樣做過?。粫?huì)顯示任何錯(cuò)誤,只不過計(jì)算表達(dá)式時(shí)返回值為空。因此在計(jì)算和使用表達(dá)式之前一定要反復(fù)檢查。
計(jì)算 XPath 表達(dá)式
得到表達(dá)式后必須對(duì)其求值。這正是出現(xiàn) XPath
對(duì)象的原因??梢允褂?evaluate()
方法計(jì)算表達(dá)式。這個(gè)方法有點(diǎn)兒奇特,因此讓我們看看 清單 9 中的例子,了解每個(gè)參數(shù)的意義。
清單 9. 計(jì)算 XPath 表達(dá)式
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; Node cd = (Node)xpath.evaluate(expr, doc, XPathConstants.NODE); }
|
evaluate()
的第一個(gè)參數(shù)是表達(dá)式本身,第二個(gè)是表達(dá)式求值開始的上下文。清單 9 中,傳入了通過調(diào)用 buildDocument()
獲得的 DOM 文檔。如果需要不同的上下文,可以傳入文檔中的某個(gè) Node
。但通常不這樣做,傳入 DOM 文檔元素,然后從這里開始編寫表達(dá)式是最簡(jiǎn)單的辦法。
最后一個(gè)參數(shù)看起來有點(diǎn)兒奇怪,除非您非常熟悉 Java Naming and Directory Interface(JNDI)。該參數(shù)告訴 XPath 引擎期望表達(dá)式返回什么類型的值。XPathConstants
類中定義了幾種可能的值:
-
XPathConstants.BOOLEAN
用于返回布爾數(shù)據(jù)類型的表達(dá)式(映射到 Java Boolean
數(shù)據(jù)類型)
-
XPathConstants.NODE
用于返回節(jié)點(diǎn)數(shù)據(jù)類型的表達(dá)式(映射到 DOM org.w3c.dom.Node
數(shù)據(jù)類型)
-
XPathConstants.NODESET
用于返回節(jié)點(diǎn)集數(shù)據(jù)類型的表達(dá)式(映射到 DOM org.w3c.dom.NodeList
數(shù)據(jù)類型)
-
XPathConstants.NUMBER
用于返回?cái)?shù)值數(shù)據(jù)類型的表達(dá)式(映射到 Java Double
數(shù)據(jù)類型)
-
XPathConstants.STRING
用于返回字符串?dāng)?shù)據(jù)類型的表達(dá)式(映射到 Java String
數(shù)據(jù)類型)
這些類型涵蓋了所有表達(dá)式。清單 9 中的表達(dá)式(//cds/cd[@title='August and Everything After']
)應(yīng)該返回一個(gè)且只有一個(gè)節(jié)點(diǎn),因此類型指定為 XPathConstants.NODE
。如果表達(dá)式返回多個(gè)節(jié)點(diǎn),比如 //cds/cd
返回所有 cd
元素,應(yīng)該使用 XPathConstants.NODESET
。
evaluate()
的返回值是一個(gè) Java Object
,因此需要將其強(qiáng)制轉(zhuǎn)換成指定的類型。然后可以對(duì) Node
、NodeSet
或者返回的其他類型進(jìn)行操作。
|
有點(diǎn)兒多余
必須指定表達(dá)式返回值的對(duì)象類型,還要明確地強(qiáng)制轉(zhuǎn)換返回的 Object ,這似乎有點(diǎn)兒奇怪。這樣做是 Java 技術(shù)而不是 XPath API 的要求。要知道,Java 方法在編譯時(shí)指定自己的返回類型,evaluate() 方法必須在 Sun Java 實(shí)驗(yàn)室中編譯,而不能在您每次編寫新的 Java 程序時(shí)自定義。因此返回類型必須非常靈活,即直接返回 Java Object ,這樣才能處理不同返回類型的多種表達(dá)式。但是其本身不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換,因此必須提供預(yù)期的對(duì)象類型。有點(diǎn)兒羅嗦,但是 Java 語言中經(jīng)常出現(xiàn)這種情況,用一點(diǎn)兒麻煩來?yè)Q取運(yùn)行時(shí)更大的靈活性。
|
|
預(yù)編譯 XPath 表達(dá)式
在討論不同類型的 XPath 表達(dá)式之前,有必要提一提 XPath
對(duì)象的另一種特性。如果準(zhǔn)備反復(fù)使用同一個(gè) XPath 表達(dá)式,無論是一個(gè) XML 文檔還是具有相同結(jié)構(gòu)的多個(gè)文檔,當(dāng)然不希望重復(fù)編譯表達(dá)式的字節(jié)碼版本。
為了避免重復(fù)這一過程,XPath
提供了一個(gè) compile()
方法,只有一個(gè)參數(shù),即字符串 XPath 表達(dá)式。它編譯該表達(dá)式并將其作為 XPathExpression
類的實(shí)例返回。然后這個(gè)實(shí)例就代替了 XPath
,可以對(duì) XPathExpression
實(shí)例而不是 XPath
調(diào)用 evaluate()
。XPathExpression
版本的 evaluate()
有兩個(gè)參數(shù),即上下文(多數(shù)情況下就是 DOM 文檔對(duì)象)和返回類型,操作和 XPath
對(duì)象的版本一樣。請(qǐng)看 清單 10 中的簡(jiǎn)單例子。
清單 10. 預(yù)編譯 XPath 表達(dá)式
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; XPathExpression xexpr = xpath.compile(expr); Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE); }
|
經(jīng)過這樣簡(jiǎn)單的修改,您的工作有很好的回報(bào)。因?yàn)闊o論如何都要付出將表達(dá)式編譯成字節(jié)碼的代價(jià),所以修改后實(shí)際增加的成本只是多了一個(gè)需要管理的對(duì)象,程序流稍微變得不那么直觀。(強(qiáng)調(diào)的是稍微,只要略有 Java 和 JAXP 經(jīng)驗(yàn),這些代碼仍然很清晰。)除非確信表達(dá)式只使用一次,否則對(duì)表達(dá)式進(jìn)行預(yù)編譯總是有益的。
處理表達(dá)式結(jié)果
處理 XPath 表達(dá)式結(jié)果的方法僅受限于您的編程知識(shí)。得到結(jié)果集后,可以做任何需要的處理。比方說,為了驗(yàn)證 清單 9 和 清單 10 中的代碼確實(shí)檢索到了正確的 CD,可以增加 清單 11 中所示的代碼。
清單 11. 檢查結(jié)果
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; XPathExpression xexpr = xpath.compile(expr); Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
if (cd != null) { Element el = (Element)cd; System.out.println(el.getAttribute("title")); } else { System.err.println("Error! Node is null!"); } }
|
返回的結(jié)果本身沒有多少用處,但關(guān)鍵是可以使用 DOM 對(duì) XPath 表達(dá)式求值返回的 Node
進(jìn)行操作。也可將其轉(zhuǎn)化成其他 Java/XML 格式(如 SAX)并提供事件處理程序,或者將節(jié)點(diǎn)序列化到磁盤中。也可將數(shù)據(jù)提取出來供應(yīng)用程序的其他部分處理、迭代其跟蹤路徑或者想到的其他方法。XPath 專用的處理已經(jīng)完成,因此不需要再擔(dān)心了。
當(dāng)然也可將返回的這個(gè) Node
作為新 XPath 表達(dá)式的上下文,如 清單 12 所示。
清單 12. 多表達(dá)式求值
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; XPathExpression xexpr = xpath.compile(expr); Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
Node track2 = (Node)xpath.evaluate( "tracks/track[@id='2']", cd, XPathConstants.NODE); Element e = (Element)track2;
// Test the returned value System.out.println(e.getFirstChild().getNodeValue()); }
|
這就是使用上下文可能非常有意義的地方,可以從前一個(gè)表達(dá)式的結(jié)果開始新的 XPath 表達(dá)式,不 需要編寫大量的 DOM 代碼就能非常方便地在文檔中導(dǎo)航。
插入 XPath 表達(dá)式
如果理解了 XPath 并能使用不同類型的表達(dá)式,事情就更有趣了。第 1 部分 已經(jīng)介紹了 XPath 必須提供的大多數(shù)功能?,F(xiàn)在我們將在 Java 代碼中使用這些功能。
處理節(jié)點(diǎn)集
最常見的操作之一是處理表達(dá)式返回的節(jié)點(diǎn)集。再看一看 清單 2 中的示例 XML,假設(shè)需要查看 Counting Crows 的所有唱片。只要兩個(gè) XPath 表達(dá)式就能很容易地實(shí)現(xiàn),如 清單 13 所示。
清單 13. 處理節(jié)點(diǎn)集
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; XPathExpression xexpr = xpath.compile(expr); Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
expr = "http://cds/artists/artist[group-name='Counting Crows']/@id"; String crowsID = (String)xpath.evaluate(expr, doc, XPathConstants.STRING); expr = "http://cds/cd[@artistId='" + crowsID + "']"; NodeList crowsCDs = (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);
expr = "@title"; XPathExpression title = xpath.compile(expr); for (int i = 0; i < crowsCDs.getLength(); i++) { Node current = crowsCDs.item(i); System.out.println("Found CD titled '" + title.evaluate(current) + "'"); } }
|
清單 13
中使用幾種不同返回類型的 XPath
表達(dá)式。首先使用返回字符串(實(shí)際上是一個(gè)數(shù)字,不過從文本轉(zhuǎn)化成數(shù)字并沒有什么好處,因?yàn)楹竺娴拇a還要將其作為字符串使用)的 XPath
表達(dá)式檢索 Counting Crows 的 ID。然后使用該表達(dá)式的結(jié)果創(chuàng)建一個(gè)新的表達(dá)式,選擇具有此 ID 的所有 cd
元素。
第二個(gè)表達(dá)式的結(jié)果是一個(gè)節(jié)點(diǎn)集,映射到 DOM org.w3c.dom.NodeList
類型。循環(huán)遍歷 org.w3c.dom.NodeList
非常簡(jiǎn)單,很容易將列表中的每個(gè) Node
強(qiáng)制轉(zhuǎn)換成 org.w3c.dom.Element
,然后獲取子元素(文本節(jié)點(diǎn))并輸出該文本節(jié)點(diǎn)的值。還使用了另外一個(gè) XPath 表達(dá)式,這僅僅是為了強(qiáng)調(diào) XPath 的方便性,說明 XPath 能夠多么輕易地避開基本上任何 DOM 樹遍歷代碼。該表達(dá)式取得當(dāng)前節(jié)點(diǎn)的 title
屬性(要記住,在這里上下文很重要)并返回其值,然后輸出這個(gè)值。通過使用 NodeList
中的當(dāng)前 Node
作為上下文,可以很好地代替 DOM 獲取每個(gè) CD 的標(biāo)題。
關(guān)于縮寫形式
細(xì)心的讀者可能注意到清單 13 中的一句代碼:
這個(gè)版本的
evaluate()
也需要上下文參數(shù),但是
不 需要返回類型,它似乎返回一個(gè)字符串。情況確實(shí)如此,這個(gè)版本的
evaluate()
在任何情況下都返回一個(gè)字符串。有時(shí)候,比如計(jì)算的表達(dá)式返回一個(gè)節(jié)點(diǎn)集或者一個(gè)節(jié)點(diǎn),這可能造成很大的混亂。不過在其他時(shí)候,如果確實(shí)需要一個(gè)文本表達(dá)式,這是一種很好的縮寫形式。
增加布爾表達(dá)式
XPath 的另一個(gè)特點(diǎn)是能夠計(jì)算布爾表達(dá)式。比如,可使用 清單 14 中的代碼來實(shí)現(xiàn)和 清單 13 類似的效果,這一次在迭代全部 CD 的過程中使用了 XPath 布爾表達(dá)式。
清單 14. 用 XPath 表達(dá)式返回布爾值
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); // Create a new XPath instance XPath xpath = factory.newXPath();
// Create a new XPath expression String expr = "http://cds/cd[@title='August and Everything After']"; XPathExpression xexpr = xpath.compile(expr); Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE); expr = "http://cds/artists/artist[group-name='Counting Crows']/@id"; String crowsID = (String)xpath.evaluate(expr, doc, XPathConstants.STRING); expr = "http://cds/cd"; NodeList allCDs = (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);
String expr1 = "@artistId='" + crowsID + "'"; String expr2 = "@title"; XPathExpression artist = xpath.compile(expr1); XPathExpression title = xpath.compile(expr2); for (int i = 0; i < allCDs.getLength(); i++) { Node current = allCDs.item(i); Boolean crowsCD = (Boolean)artist.evaluate(current, XPathConstants.BOOLEAN); if (crowsCD) System.out.println("Found CD titled '" + title.evaluate(current) + "'") } }
|
清單 13 和 清單 14 無所謂優(yōu)劣,僅僅是獲得同樣信息的不同方法。不過,這兩個(gè)例子說明了 XPath 的靈活性。這些例子以及本文中的其他例子,說明了 XPath 如何處理各種數(shù)據(jù)類型,可以為您自己的編程工作帶來啟迪。
結(jié)束語
和以前相比 Java 開發(fā)人員擁有了更多的 API、技術(shù)和工具箱。但是豐富的編程能力也帶來一種危險(xiǎn) —— 某種 API
偏執(zhí)癥。很容易認(rèn)定只有那些明確貼上數(shù)據(jù)綁定標(biāo)簽的 API 才能用于以邏輯而非語義的方式獲取 XML 數(shù)據(jù)。貼上 XPath 標(biāo)簽的 API
只能用于傳統(tǒng)上使用 XPath 的應(yīng)用程序中,這就是偏執(zhí)癥的觀念。同樣的問題也適用于其他 API,無論是解析
XML、讀取屬性文件還是在內(nèi)存中創(chuàng)建樹狀結(jié)構(gòu)的 API。
但是,當(dāng)稍微創(chuàng)造性地選擇 API 和編程方式時(shí),Java 語言的強(qiáng)大之處常常最明顯。不要被那些隨意的標(biāo)簽(如數(shù)據(jù)綁定)所蒙蔽,要看看
API 到底提供了什么。如果發(fā)現(xiàn)一種 API
似乎提供了有用的功能,就將其添加到代碼中,無論它是用來干什么的。和遵守所有規(guī)則但是笨拙、緩慢、功能有限的應(yīng)用程序相比,以這種方式使用(或?yàn)E用)
API 的能夠工作的應(yīng)用程序更應(yīng)該受到贊賞。
通過本系列的兩篇文章,您看到了如何將 XPath API 作為一種相對(duì)容易的數(shù)據(jù)綁定 API 來使用,雖然一般認(rèn)為 XPath 僅在
XSLT 和 XPointer 世界中出現(xiàn)。更好的是,Java 語言在 JAXP 中提供了一種 XPath API。Java 和 XML
程序員有誰能夠抗拒這種誘惑呢?放棄標(biāo)簽的框框吧,不僅僅 JAXB 能用于數(shù)據(jù)綁定,XPath 是一個(gè)很好的起點(diǎn)。
下載
描述 | 名字 | 大小 | 下載方法 |
---|
Sample code for this article | x-pracdb9_code.zip | 52KB | HTTP |
參考資料
學(xué)習(xí)獲得產(chǎn)品和技術(shù)-
使用 IBM 試用軟件 開發(fā)您的下一個(gè)項(xiàng)目,可直接從 developerWorks 下載。
討論關(guān)于作者
 |

|  | Brett
McLaughlin 從 Logo 時(shí)代就開始使用計(jì)算機(jī)。(還記得那個(gè)小三角嗎?)近年來他已經(jīng)成為 Java 技術(shù)和 XML
社區(qū)最知名的作家和程序員之一。他曾經(jīng)在 Nextel Communications 實(shí)現(xiàn)過復(fù)雜的企業(yè)系統(tǒng),在 Lutris
Technologies 編寫應(yīng)用服務(wù)器,最近在 O'Reilly Media, Inc. 繼續(xù)撰寫和編輯這方面的圖書。在他的新書 Head Rush Ajax 中,Brett 和暢銷書作家 Eric 與 Beth Freeman 為 Ajax 帶來了獲獎(jiǎng)的創(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)威的著作之一。 |