級別: 中級
Brett McLaughlin, 作家,編輯, O'Reilly Media, Inc.
2006 年 1 月 23 日
一
旦理解了 XPath 語法和位置路徑,避開 DOM 和 SAX 的開銷訪問 XML 就是可能的了,而且很容易。對于 Java?
開發人員來說,幸運的是 Java API for XML Processing(JAXP)為創建 XPath
表達式和在應用程序中使用查詢結果提供了原生的 Java 解決方案。本系列文章包括兩部分,這是第 2 部分,介紹如何創建 XPath
請求、執行請求和處理得到的節點集,都在 Java 環境中輕松地完成。
本系列的 第 1 部分
介紹了 XPath 語言及其語法。對于不熟悉 XSLT 的 Java 程序員來說可能有一點兒壓力。XPath 語法可能看起來有點兒奇怪,和基于
XML 的語法相比更像是 UNIX 的目錄路徑 —— 又稍微增加了一點兒東西。但是您很快就看到了 XPath 使得選擇 XML
文檔的特定部分非常容易。
|
突出的邏輯性
XML 使用兩種基本類型的名稱和路徑:邏輯性的 和結構性的。邏輯名是領域特定的,通常對人們有一定的含義。包含音樂唱片信息的 XML 文檔中,邏輯名可能是 cd 、album 或 artist 。結構名和 XML 的結構有關。因為它們和 XML 中的結構有關,在所有文檔中都是一樣的:element 、attribute 和 node 。顯然,如果使用邏輯名,代碼會更容易閱讀,而且對一般程序員能提供更多的信息。
|
|
在實用數據綁定 系列文章中討論 XPath,原因就在于這些選擇都使用邏輯名(請參閱 突出的邏輯性)。比方說,不必選擇根元素第二個子元素的第一個屬性,可以使用 XPath 表達式 /cds/cd[@title='August and Everything After']
。從一定意義上說,這就是 數據綁定,因為可以使用 XML 標記而不是結構來訪問數據。
本文主要討論使用 XPath 作為通過邏輯名從 XML 中訪問數據的方法,如 cd
、table
或 person
,而不用 firstElement
或 parent.getChildren(2)
。最終得到的代碼非常類似于數據綁定,使用邏輯名而非語義名,但是又沒有傳統數據綁定解決方案的初始類生成和類路徑問題。
節點知道
第 1 部分 中提到,XPath 選擇總是返回一個節點 集。和其他任何集合一樣,其中可能有零個、一個或多個成員。這里重要的概念是節點。節點可以是一段文本(比如元素的內容)、一個元素、一條處理指令或者注釋。當然,節點集就是這類事物的任意組合。
如果您是一位 DOM 程序員,就不會被節點這個概念難倒。DOM 根據其 Node
接口看待一切事物,因此您也就習慣于通過 Node
接口來處理注釋、元素和文本數據。如果不習慣 DOM,應該花點兒時間來熟悉節點。節點對于使用 XPath 至關重要。執行 XPath
表達式時,對表達式結果進行操作的代碼必須對節點操作,而不深究 XML 語義。文檔內的導航是用 XPath 表達式進行的,而不是使用 DOM
樹轉移方法從一個子節點移動到另一個。
JAXP 和 XPath 基礎
Java 編程語言的最新版本 Java 5.0 中,對 Java API for XML Processing(JAXP)和 XPath 的支持已成為標準。您可能已經熟悉 JAXP 以及 SAX、DOM 和 XSL 編程中使用的 javax.xml.parsers
和 javax.xml.transform
包。(如果不熟悉的話,請參閱本文后面列出的一些 參考資料。)對 XPath 的支持主要來自于一個新的包 javax.xml.xpath
。
檢查 JAXP 和 Java 版本
JAXP 1.3 包含在所有的 Java 5.0 下載包中。為了確保安裝了 JAXP 1.3,請在命令提示符下輸入以下命令:
應該看到類似于 清單 1 所示的結果。
清單 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 或更高,則已經有了 JAXP 1.3,可以確信您有了本文中使用的 XPath API。
簡單的 XML
還需要一些供操作的 XML。本文中所有的例子都將使用 清單 2 中所示的簡單 XML 文檔。這個文檔并不復雜,但是足以說明 90% 的 XPath 編程中所需要的各種 XPath 函數。
清單 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>
|
將該文檔保存在本地機器上便于從 Java 代碼中訪問的地方(請參閱 下載)。如果希望使用自己的 XML 文檔也完全可以,只要調整示例代碼和文檔的結構與邏輯名匹配即可。
再回到 DOM
DOM 程序員已經熟悉了節點的概念,這是理解 XPath 表達式和這些表達式的返回值不可或缺的。使用 XPath 時,DOM 程序員還有一個優勢,即在應用任何 XPath 表達式之前實際使用 DOM 獲得對 XML 文檔的訪問。這毫不奇怪:XPath 現在是 JAXP 的核心部分,而 JAXP 也提供了完整的 DOM 支持。
將 XML 轉化成 DOM 樹
使用 XPath 的第一步是將目標 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 樹中。如果曾經使用過
JAXP,或者在沒有 JAXP helper 類的情況下使用過
DOM,就會非常熟悉這種操作。(如果這些代碼看起來有些嚇人,那么可能需要查閱一下 參考資料 中列出的 DOM 文章,然后再回到本文。)
設置 XPath 上下文
第 1 部分 中提到,在對任何 XPath 表達式求值之前,必須知道開始的上下文。比方說,選擇 ID 為 23
的 artist
節點,XPath 表達式取決于上下文是 XML 文檔的根節點,還是第三個 cd
元素的第二個 track
元素。
|
上下文的縮寫形式
使用絕對路徑的 XPath 表達式常常要比通過 DOM 樹進行導航更容易。要記住,如果在 XPath 表達式前使用雙斜杠(// ),那么這個表達式就從樹的根節點開始。這樣就可以方便地繞過任何上下文,因此不需要導航到樹中的特定節點。這通常意味著 XPath 表達式可能稍微長一點,但是常常能夠避免大量的 DOM 樹轉移代碼。
|
|
顯然,需要能夠在 XML 文檔中移動來設置上下文,并相應地修改 XPath 表達式。從這里開始,真正的數據綁定 API 和 XPath 的界線漸漸分明了。在 XPath 中,雖然表達式是純邏輯性的(使用 cd
或 artist
之類的名稱),但是仍然需要使用 DOM 在 XML 中移動位置。這意味著仍然需要使用 Element
、Text
這樣的 XML 結構成分,仍然需要適當的樹遍歷。當然,如果讀入 XML 文檔,像 清單 3 那樣,那么最初的上下文就是文檔根節點,如果從根開始編寫表達式就不用太擔心樹的遍歷。
我認為這就是數據綁定!
有些讀者可能已經在撓頭了。本文是討論如何將 XPath 用于數據綁定的,但是到現在說的一直是 DOM 和結構。XPath 是數據綁定很好的替身,但是這種 API 天生具有一定程度的結構。事實上,甚至位置路徑也在某種程度上使用結構://cds/cd[@title='This Desert Life']
假設 cds
元素是目標文檔的根元素,cd
元素是根的子元素。這和使用 getCDs().getCD("This Desert Life")
之類的代碼沒有多少差別。這兩種數據綁定方法沒有顯著的不同。
至于更明顯的差異,就是在 XPath 代碼中必須處理 DOM。但是,這點不同也能夠在很大程度上隱藏起來。再看一看 清單 3,加載文檔過程中所有和 DOM 有關的工作都通過 buildDocument()
方法抽象出來。返回的 DOM Document
對象是 DOM 專有的,但是沒有多少地方需要直接操作這個對象。因此,不需要傳統數據綁定方法所需要的一些類生成代碼,XPath 就提供了數據綁定的多數 好處:
- 從 DOM 和 SAX 這樣的 API 中抽象出來
- 使用邏輯命名法而不是結構命名法
- XML 文檔和 Java 對象之間的寬松映射
這就使其成為 XML 編程中一種非常輕量的數據綁定方法。
使用 XPath 工廠
您已經看到,稍微了解一點兒 DOM 知識可以大大方便對 XPath 的學習。如果在 DOM 的基礎上再熟悉 JAXP API,那么您就遙遙領先了。比方說,在 JAXP 中無論是用 SAX 還是 DOM ,基本步驟都是:
- 創建新的解析器工廠。
- 創建新的解析器或者文檔構造器(builder)。
- 解析/構造目標 XML。
同樣的步驟也適用于 XPath 編程:
- 創建新的 XPath 工廠。
- 創建新的 XPath 求值程序(evaluator)。
- 創建和計算 XPath 表達式。
如果掌握了這些 SAX 或 DOM 背景知識,可能只需看一眼 XPath API 就能開始編程了。不過先等一等。還需要知道 XPath 一些特殊的地方,雖然您會發現本節的內容熟悉而且非常簡單。
JAXP 和工廠
如果不熟悉 JAXP 或者使用時間不長,可能就不會理解在這種 API 中工廠模式的重要性和價值。JAXP 實際上不是一種 API
而更像是一種抽象,它允許不必編寫廠商專用的代碼,就可以使用特定廠商的 SAX、DOM 和 XPath 實現。為此,需要加載一個工廠實例,如 SAXParserFactory
或 DocumentBuilderFactory
,然后 JAXP 就會把這個類連接到這些結構的廠商實現。因此雖然在代碼中直接使用 SAXParserFactory
,JAXP 必須在幕后使用 Apache Xerces 來處理真正的文檔解析。
同樣的抽象在 XPath 中更具有高瞻遠矚的性質。和豐富的 XML 解析與轉換工具相比,目前還沒有很多 XPath
處理程序。但是,通過使用工廠模式,就會發現以后如果需要可以更方便地替換 XPath 實現。具體來說,XPath
工廠允許使用不同的對象模型。默認情況下,節點集是從 DOM Node
和 NodeSet
中的 XPath 表達式返回的(在稍后的 使用 XPath 表達式
一節中將會看到)。但是,有可能希望使用 JDOM 結構或者 dom4j 提供的對象模型。雖然這些 Java/XML API 還沒有用于
JAXP 的 XPath 引擎,但是可能很快就會出現。JAXP
工廠模型意味著很容易插入這些模型。因此不要為使用工廠模式所要求的額外步驟感到惱火,它們的存在為了提供便利,增加靈活性。
|
重要的語句
本文中假設當在代碼清單中增加新的類和接口時已經添加了必要的 import 語句。除非特別說明,所有的 XPath 類和接口都在 javax.xml.xpath 包中。
|
|
創建 XPath 工廠
您已經知道,使用 XPath 時最主要的包是 javax.xml.xpath
。毫不奇怪,我們需要的工廠被稱作 XPathFactory
,也在這個包中。JAXP 老兵可能已經猜到,要使用 newInstance()
創建新的工廠,如 清單 4 所示(添加到 清單 3 中所示的代碼中)。
清單 4. 創建新的 XPath 工廠
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance(); }
|
清單 4 中沒有什么特別之處。僅僅使用默認的對象(即 DOM,至少在 1.3 版中如此,而且近期不大可能改變)創建了一個新的 XPath 工廠實例。
使用其他對象模型
因為 JAXP 已經開始接受其他 XML 對象模型,您可能希望不使用 JAXP 默認的 DOM 模型。這種情況下可以為 newInstance()
指定一個字符串統一資源標識符(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"); }
|
默認情況下,該 URI 被指定為 javax.xml.xpath.XPathConstants.DOM_OBJECT_MODEL
中的 http://java.sun.com/jaxp/xpath/dom
。因此 清單 5 中的代碼等價于 清單 6 中所示的代碼。
清單 6. 創建新的 XPath 工廠,比較麻煩的方法
public void test() throws Exception { Document doc = buildDocument();
// Create a new XPath factory XPathFactory factory = XPathFactory.newInstance( XPathConstants.DOM_OBJECT_MODEL); }
|
無論采用何種方法創建,工廠都是使用 XPath 時的第一步。因此測試類中一定要包含工廠創建代碼,以便能夠使用 XPath
類。
使用 XPath 類
創建工廠之后,還需要從工廠轉移到能夠真正計算表達式并與 XPath 環境交互的對象。XPathFactory
不能 讓您計算表達式(至少不能直接計算),它僅僅是從代碼中獲得廠商的 XPath 引擎和對象模型,而不需要很多廠商專用代碼的一種方式。因此,建立這種聯系后,還需要獲得一個能夠 處理 XPath 表達式求值的對象。這并非其他對象,正是 XPath
對象登臺亮相了。
創建 XPath 對象
可以使用工廠的 newXPath()
方法創建新的 XPath
。清單 7 表明這個步驟是多么簡單。
清單 7. 創建 XPath 類的新實例
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(); }
|
如果這些代碼看起來簡單得可笑,那就對了。所有其他的 XPath 處理工作都要從這個類開始,雖然看起來簡單,但是應該習慣于輸入這些代碼。
配置和重置 XPath 對象
一旦得到了 XPath
的實例,就可以開始通過改變上下文、處理名稱空間和計算表達式來配置它。其中一些主題超出了本文的范圍,但是如果遇到 XPath 代碼不工作的情況,可能是因為使用了錯誤配置的 XPath
對象。如果是這種情況,或者懷疑是這樣,可以通過調用 xpath.reset()
重新將該對象恢復為初始配置。這樣通常能夠消除看到的問題,在調試過程中非常有用。
使用 XPath 表達式
現在您可能已經為使用 XPath 表達式作好了充分的準備。所幸的是,到現在為止您做的準備工作使這項任務變得很容易了。只需要用字符串創建一個新的表達式(要記住,這些都只是在 XML 文檔中定位的引用,常常被稱為位置路徑)。
創建 XPath 表達式
清單 8 將一個 XPath 表達式賦給了一個字符串變量,這里沒有戲法,也沒有特殊的語法。僅僅使用一般的 Java String
,然后將 XPath 表達式賦給它去求值。
清單 8. 創建 XPath 表達式
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']"; }
|
因為 清單 8 沒有包含 XPath 專用的步驟,必須非常小心。其中也沒有任何錯誤檢查。有可能漏掉 title
屬性中的 @
符號(我第一次編寫這段代碼時就這樣做過!),不會顯示任何錯誤,只不過計算表達式時返回值為空。因此在計算和使用表達式之前一定要反復檢查。
計算 XPath 表達式
得到表達式后必須對其求值。這正是出現 XPath
對象的原因。可以使用 evaluate()
方法計算表達式。這個方法有點兒奇特,因此讓我們看看 清單 9 中的例子,了解每個參數的意義。
清單 9. 計算 XPath 表達式
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()
的第一個參數是表達式本身,第二個是表達式求值開始的上下文。清單 9 中,傳入了通過調用 buildDocument()
獲得的 DOM 文檔。如果需要不同的上下文,可以傳入文檔中的某個 Node
。但通常不這樣做,傳入 DOM 文檔元素,然后從這里開始編寫表達式是最簡單的辦法。
最后一個參數看起來有點兒奇怪,除非您非常熟悉 Java Naming and Directory Interface(JNDI)。該參數告訴 XPath 引擎期望表達式返回什么類型的值。XPathConstants
類中定義了幾種可能的值:
-
XPathConstants.BOOLEAN
用于返回布爾數據類型的表達式(映射到 Java Boolean
數據類型)
-
XPathConstants.NODE
用于返回節點數據類型的表達式(映射到 DOM org.w3c.dom.Node
數據類型)
-
XPathConstants.NODESET
用于返回節點集數據類型的表達式(映射到 DOM org.w3c.dom.NodeList
數據類型)
-
XPathConstants.NUMBER
用于返回數值數據類型的表達式(映射到 Java Double
數據類型)
-
XPathConstants.STRING
用于返回字符串數據類型的表達式(映射到 Java String
數據類型)
這些類型涵蓋了所有表達式。清單 9 中的表達式(//cds/cd[@title='August and Everything After']
)應該返回一個且只有一個節點,因此類型指定為 XPathConstants.NODE
。如果表達式返回多個節點,比如 //cds/cd
返回所有 cd
元素,應該使用 XPathConstants.NODESET
。
evaluate()
的返回值是一個 Java Object
,因此需要將其強制轉換成指定的類型。然后可以對 Node
、NodeSet
或者返回的其他類型進行操作。
|
有點兒多余
必須指定表達式返回值的對象類型,還要明確地強制轉換返回的 Object ,這似乎有點兒奇怪。這樣做是 Java 技術而不是 XPath API 的要求。要知道,Java 方法在編譯時指定自己的返回類型,evaluate() 方法必須在 Sun Java 實驗室中編譯,而不能在您每次編寫新的 Java 程序時自定義。因此返回類型必須非常靈活,即直接返回 Java Object ,這樣才能處理不同返回類型的多種表達式。但是其本身不需要進行強制類型轉換,因此必須提供預期的對象類型。有點兒羅嗦,但是 Java 語言中經常出現這種情況,用一點兒麻煩來換取運行時更大的靈活性。
|
|
預編譯 XPath 表達式
在討論不同類型的 XPath 表達式之前,有必要提一提 XPath
對象的另一種特性。如果準備反復使用同一個 XPath 表達式,無論是一個 XML 文檔還是具有相同結構的多個文檔,當然不希望重復編譯表達式的字節碼版本。
為了避免重復這一過程,XPath
提供了一個 compile()
方法,只有一個參數,即字符串 XPath 表達式。它編譯該表達式并將其作為 XPathExpression
類的實例返回。然后這個實例就代替了 XPath
,可以對 XPathExpression
實例而不是 XPath
調用 evaluate()
。XPathExpression
版本的 evaluate()
有兩個參數,即上下文(多數情況下就是 DOM 文檔對象)和返回類型,操作和 XPath
對象的版本一樣。請看 清單 10 中的簡單例子。
清單 10. 預編譯 XPath 表達式
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); }
|
經過這樣簡單的修改,您的工作有很好的回報。因為無論如何都要付出將表達式編譯成字節碼的代價,所以修改后實際增加的成本只是多了一個需要管理的對象,程序流稍微變得不那么直觀。(強調的是稍微,只要略有 Java 和 JAXP 經驗,這些代碼仍然很清晰。)除非確信表達式只使用一次,否則對表達式進行預編譯總是有益的。
處理表達式結果
處理 XPath 表達式結果的方法僅受限于您的編程知識。得到結果集后,可以做任何需要的處理。比方說,為了驗證 清單 9 和 清單 10 中的代碼確實檢索到了正確的 CD,可以增加 清單 11 中所示的代碼。
清單 11. 檢查結果
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!"); } }
|
返回的結果本身沒有多少用處,但關鍵是可以使用 DOM 對 XPath 表達式求值返回的 Node
進行操作。也可將其轉化成其他 Java/XML 格式(如 SAX)并提供事件處理程序,或者將節點序列化到磁盤中。也可將數據提取出來供應用程序的其他部分處理、迭代其跟蹤路徑或者想到的其他方法。XPath 專用的處理已經完成,因此不需要再擔心了。
當然也可將返回的這個 Node
作為新 XPath 表達式的上下文,如 清單 12 所示。
清單 12. 多表達式求值
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()); }
|
這就是使用上下文可能非常有意義的地方,可以從前一個表達式的結果開始新的 XPath 表達式,不 需要編寫大量的 DOM 代碼就能非常方便地在文檔中導航。
插入 XPath 表達式
如果理解了 XPath 并能使用不同類型的表達式,事情就更有趣了。第 1 部分 已經介紹了 XPath 必須提供的大多數功能。現在我們將在 Java 代碼中使用這些功能。
處理節點集
最常見的操作之一是處理表達式返回的節點集。再看一看 清單 2 中的示例 XML,假設需要查看 Counting Crows 的所有唱片。只要兩個 XPath 表達式就能很容易地實現,如 清單 13 所示。
清單 13. 處理節點集
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
表達式。首先使用返回字符串(實際上是一個數字,不過從文本轉化成數字并沒有什么好處,因為后面的代碼還要將其作為字符串使用)的 XPath
表達式檢索 Counting Crows 的 ID。然后使用該表達式的結果創建一個新的表達式,選擇具有此 ID 的所有 cd
元素。
第二個表達式的結果是一個節點集,映射到 DOM org.w3c.dom.NodeList
類型。循環遍歷 org.w3c.dom.NodeList
非常簡單,很容易將列表中的每個 Node
強制轉換成 org.w3c.dom.Element
,然后獲取子元素(文本節點)并輸出該文本節點的值。還使用了另外一個 XPath 表達式,這僅僅是為了強調 XPath 的方便性,說明 XPath 能夠多么輕易地避開基本上任何 DOM 樹遍歷代碼。該表達式取得當前節點的 title
屬性(要記住,在這里上下文很重要)并返回其值,然后輸出這個值。通過使用 NodeList
中的當前 Node
作為上下文,可以很好地代替 DOM 獲取每個 CD 的標題。
關于縮寫形式
細心的讀者可能注意到清單 13 中的一句代碼:
這個版本的
evaluate()
也需要上下文參數,但是
不 需要返回類型,它似乎返回一個字符串。情況確實如此,這個版本的
evaluate()
在任何情況下都返回一個字符串。有時候,比如計算的表達式返回一個節點集或者一個節點,這可能造成很大的混亂。不過在其他時候,如果確實需要一個文本表達式,這是一種很好的縮寫形式。
增加布爾表達式
XPath 的另一個特點是能夠計算布爾表達式。比如,可使用 清單 14 中的代碼來實現和 清單 13 類似的效果,這一次在迭代全部 CD 的過程中使用了 XPath 布爾表達式。
清單 14. 用 XPath 表達式返回布爾值
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 無所謂優劣,僅僅是獲得同樣信息的不同方法。不過,這兩個例子說明了 XPath 的靈活性。這些例子以及本文中的其他例子,說明了 XPath 如何處理各種數據類型,可以為您自己的編程工作帶來啟迪。
結束語
和以前相比 Java 開發人員擁有了更多的 API、技術和工具箱。但是豐富的編程能力也帶來一種危險 —— 某種 API
偏執癥。很容易認定只有那些明確貼上數據綁定標簽的 API 才能用于以邏輯而非語義的方式獲取 XML 數據。貼上 XPath 標簽的 API
只能用于傳統上使用 XPath 的應用程序中,這就是偏執癥的觀念。同樣的問題也適用于其他 API,無論是解析
XML、讀取屬性文件還是在內存中創建樹狀結構的 API。
但是,當稍微創造性地選擇 API 和編程方式時,Java 語言的強大之處常常最明顯。不要被那些隨意的標簽(如數據綁定)所蒙蔽,要看看
API 到底提供了什么。如果發現一種 API
似乎提供了有用的功能,就將其添加到代碼中,無論它是用來干什么的。和遵守所有規則但是笨拙、緩慢、功能有限的應用程序相比,以這種方式使用(或濫用)
API 的能夠工作的應用程序更應該受到贊賞。
通過本系列的兩篇文章,您看到了如何將 XPath API 作為一種相對容易的數據綁定 API 來使用,雖然一般認為 XPath 僅在
XSLT 和 XPointer 世界中出現。更好的是,Java 語言在 JAXP 中提供了一種 XPath API。Java 和 XML
程序員有誰能夠抗拒這種誘惑呢?放棄標簽的框框吧,不僅僅 JAXB 能用于數據綁定,XPath 是一個很好的起點。
下載
描述 | 名字 | 大小 | 下載方法 |
---|
Sample code for this article | x-pracdb9_code.zip | 52KB | HTTP |
參考資料
學習獲得產品和技術-
使用 IBM 試用軟件 開發您的下一個項目,可直接從 developerWorks 下載。
討論關于作者
 |

|  | Brett
McLaughlin 從 Logo 時代就開始使用計算機。(還記得那個小三角嗎?)近年來他已經成為 Java 技術和 XML
社區最知名的作家和程序員之一。他曾經在 Nextel Communications 實現過復雜的企業系統,在 Lutris
Technologies 編寫應用服務器,最近在 O'Reilly Media, Inc. 繼續撰寫和編輯這方面的圖書。在他的新書 Head Rush Ajax 中,Brett 和暢銷書作家 Eric 與 Beth Freeman 為 Ajax 帶來了獲獎的創新方法 Head First。他最近出版的 Java 1.5 Tiger: A Developer's Notebook 是關于 Java 技術最新版本的第一本著作,他的經典之作 Java and XML 仍然是在 Java 語言中使用 XML 技術的最權威的著作之一。 |