TShopping

 找回密碼
 註冊
搜索
查看: 1409|回復: 0

[轉帖] Android xml使用XPath作為數據綁定工具,第2部分

[複製鏈接]
發表於 2017-11-30 13:14:13 | 顯示全部樓層 |閱讀模式
 
Push to Facebook
本系列的第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(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. java -version
複製代碼


應該看到類似於清單1所示的結果。
清單1.命令行提示符下的Java 5.0
  1. java version "1.5.0_02"
  2. Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-56)
  3. 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文檔。這個文檔並不復雜,但是足以說明XPath編程中所需要的各種XPath函數。

清單2. XPath代碼中使用的XML示例文檔
  1. <?xml version="1.0"?>
  2. <cds>
  3. <cd title="August and Everything After" artistId="23">
  4.   <style>rock</style>
  5.   <style>alternative</style>
  6.   <tracks>
  7.    <track id="1">Round Here</track>
  8.    <track id="2">Omaha</track>
  9.   </tracks>
  10. </cd>
  11. <cd title="This Desert Life" artistId="23">
  12.   <style>rock</style>
  13.   <style>alternative</style>
  14.   <tracks>
  15.    <track id="1">Hangin' Around</track>
  16.    <track id="2">Mrs. Potter's Lullaby</track>
  17.   </tracks>
  18. </cd>
  19. <cd title="Crash" artistId="46">
  20.   <style>alternative</style>
  21.   <style>jazz</style>
  22.   <style>rock</style>
  23.   <tracks>
  24.    <track id="5">#41</track>
  25.    <track id="3">Crash Into Me</track>
  26.   </tracks>
  27. </cd>
  28. <artists>
  29.   <artist id="23" type="group">
  30.    <group-name>Counting Crows</group-name>
  31.    <website>http://www.countingcrows.com</website>
  32.    <member type="lead-singer">
  33.     <firstName>Adam</firstName>
  34.     <lastName>Duritz</lastName>
  35.     <website>http://adam.countingcrows.com</website>
  36.    </member>
  37.   </artist>
  38.   <artist id="46" type="group">
  39.    <group-name>Dave Matthews Band</group-name>
  40.    <website>http://www.dmband.com</website>
  41.    <member type="lead-singer">
  42.     <firstName>Dave</firstName>
  43.     <lastName>Matthews</lastName>
  44.    </member>
  45.    <member type="instrumentalist">
  46.     <firstName>Boyd</firstName>
  47.     <lastName>Tinsley</lastName>
  48.     <instrument>violin</instrument>
  49.     <instrument>viola</instrument>
  50.    </member>
  51.   </artist>
  52.   <artist id="101" type="solo">
  53.    <website>http://www.patdonohue.com</website>
  54.    <member>
  55.     <firstName>Pat</firstName>
  56.     <lastName>Donohue</lastName>
  57.     <instrument>acoustic guitar</instrument>
  58.    </member>
  59.   </artist>
  60. </artists>
  61. </cds>
複製代碼


將該文檔保存在本地機器上便於從Java代碼中訪問的地方(請參閱下載)。如果希望使用自己的XML文檔也完全可以,只要調整示例代碼和文檔的結構與邏輯名匹配即可。

再回到DOM
DOM程序員已經熟悉了節點的概念,這是理解XPath表達式和這些表達式的返回值不可或缺的。使用XPath時,DOM程序員還有一個優勢,即在應用任何XPath表達式之前實際使用 DOM獲得對XML文檔的訪問。這毫不奇怪:XPath現在是JAXP的核心部分,而JAXP也提供了完整的DOM支持。

將XML轉化成DOM樹
使用XPath的第一步是將目標XML文檔作為DOM樹讀入,如清單3所示。

清單3.將XML加載到DOM樹中
  1. package com.ibm.dw.xpath;
  2. import java.io.File;
  3. import javax.xml.parsers.DocumentBuilder;
  4. import javax.xml.parsers.DocumentBuilderFactory;
  5. import org.w3c.dom.Document;
  6. public class SimpleXPath {
  7.   File xmlInput;
  8.   public SimpleXPath(String xmlFilename) {
  9.     xmlInput = new File(xmlFilename);
  10.     if (!xmlInput.exists())
  11.       throw new RuntimeException("Specified file does not exist!");
  12.   }
  13.   public void test() throws Exception {
  14.     Document doc = buildDocument();
  15.   }
  16.   private Document buildDocument() throws Exception {
  17.     DocumentBuilder builder =
  18.       DocumentBuilderFactory.newInstance()
  19.                             .newDocumentBuilder();
  20.     return builder.parse(xmlInput);
  21.   }
  22.   public static void main(String[] args) {
  23.     if (args.length < 1) {
  24.       System.err.println("Usage: java com.ibm.dw.xpath.SimpleXPath " +
  25.                          "[XML filename]");
  26.       return;
  27.     }
  28.     try {
  29.       SimpleXPath tester = new SimpleXPath(args[0]);
  30.       tester.test();
  31.     } catch (Exception e) {
  32.       System.err.println("Exception occurred: " + e.getMessage());
  33.       e.printStackTrace(System.err);
  34.       return;
  35.     }
  36.   }
  37. }
複製代碼


清單3中的代碼沒有多少值得注意的地方。它僅僅從命令行接收XML,然後使用JAXP DOM API將其加載到DOM樹中。如果曾經使用過JAXP,或者在沒有JAXP幫助器的情況下使用過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,基本步驟都是:
  • 創建新的解析器工廠。
  • 創建新的解析器或者文檔構造器(製造商)。
  • 解析/構造目標XML。

同樣的步驟也適用於XPath編程:
  • 創建新的XPath工廠。
  • 創建新的XPath求值程序(evaluateator)。
  • 創建和計算XPath表達式。

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工廠
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory
  5.   XPathFactory factory = XPathFactory.newInstance();
  6. }
複製代碼


清單4中沒有什麼特別的處理。僅建立了一個新的XPath工廠實例。

使用其他對象模型
因為JAXP已經開始接受其他XML對像模型,您可能希望不使用JAXP默認的DOM模型。這種情況下可以newInstance()指定一個字符串統一資源標識符(統一資源標識符,URI),如清單5所示。

清單5.指定字符串URI
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory with an alternate object model
  5.   XPathFactory factory = XPathFactory.newInstance(
  6.       "http://jdom.org/jaxp/xpath/jdom");
  7. }
複製代碼


默認情況下,該URI被指定為javax.xml.xpath.XPathConstants.DOM_OBJECT_MODEL中的http://java.sun.com/jaxp/xpath/dom。因此清單5中的代碼等價於清單6中所示的代碼。

清單6.創建新的XPath工廠,比較麻煩的方法
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory
  5.   XPathFactory factory = XPathFactory.newInstance(
  6.       XPathConstants.DOM_OBJECT_MODEL);
  7. }
複製代碼




無論採用何種方法創建,工廠都是使用XPath時的第一步。因此測試類中一定要包含工廠創建代碼,以便能夠使用XPath類。

使用XPath類
創建工廠之後,還需要從工廠轉移到能夠真正計算表達式並與XPath環境交互的對象。XPathFactory不能讓您計算表達式(至少不能直接計算),它只是從代碼中獲得廠商的XPath引擎和對像模型,而不需要很多廠商專用代碼的一種方式。因此,建立這種聯繫後,還需要獲得一個能夠處理XPath表達式的值的對象。這並非其他對象,正是XPath對象登台亮相了。

創建XPath對象
可以使用工廠的newXPath()方法創建新的XPath。清單7表明這個步驟是多麼簡單。

清單7.創建XPath類的新實例
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory
  5.   XPathFactory factory = XPathFactory.newInstance();
  6.   // Create a new XPath instance
  7.   XPath xpath = factory.newXPath();
  8. }
複製代碼


如果這些代碼看起來簡單得可笑,那就對了。所有其他的XPath處理工作都要從這個類開始,雖然看起來簡單,但是應該習慣輸入這些代碼。

配置和重置XPath對象
一旦得到了XPath的實例,就可以開始通過改變上下文,處理名稱空間和計算表達式來配置它。其中一些主題超出了本文的範圍,但是如果遇到XPath代碼不工作的情況,可能是因為使用了錯誤的配置XPath對象。如果是這種情況,或者懷疑是這樣,通過可以調用xpath.reset()重新將該對象恢復為初始配置。這樣通常能夠消除看到的問題,在調試過程中非常有用。

使用XPath表達式
現在您可能已經為使用XPath表達式作好了充分的準備。所幸的是,到現在為止您做的準備工作使這項任務變得很容易了。只需要用字符串創建一個新的表達式要記住,這些都只是在XML文檔中定位的引用,常常被稱為位置路徑)。


創建XPath表達式
清單8將一個XPath表達式賦給了一個字符串變量,這裡沒有戲法,也沒有特殊的語法。僅僅使用一般的Java String,然後將XPath表達式賦給它去求值。

清單8.創建XPath表達式
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory
  5.   XPathFactory factory = XPathFactory.newInstance();
  6.   // Create a new XPath instance
  7.   XPath xpath = factory.newXPath();
  8.   // Create a new XPath expression
  9.   String expr = "//cds/cd[@title='August and Everything After']";
  10. }
複製代碼




因為清單8沒有包含XPath專用的步驟,必須非常小心。其中也沒有任何錯誤檢查。有可能漏掉title屬性中的@符號(我第一次編寫這段代碼時就這樣做過!),不會顯示任何錯誤,只不過計算表達式時返回值為空。因此在計算和使用表達式之前一定要反复檢查。

計算XPath表達式
得到表達式後必須對其求值。這正是出現XPath對象的原因。可以使用evaluate()方法計算表達式。這個方法有點兒奇特,因此讓我們看看清單9中的例子,了解每個參數的意義。


清單9.計算XPath表達式
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.    
  4.   // Create a new XPath factory
  5.   XPathFactory factory = XPathFactory.newInstance();
  6.   // Create a new XPath instance
  7.   XPath xpath = factory.newXPath();
  8.   // Create a new XPath expression
  9.   String expr = "//cds/cd[@title='August and Everything After']";
  10.   Node cd = (Node)xpath.evaluate(expr, doc, XPathConstants.NODE);
  11. }
複製代碼




evaluate()的第一個參數是表達式本身,第二個是表達式求值開始的上下文。清單9中,了傳入通過調用buildDocument()獲得的DOM文檔。如果需要不同的上下文,可以傳入文檔中的某個Node。但通常不這樣做,傳入DOM文檔元素,然後從這裡開始編寫表達式是最簡單的辦法。

該參數告訴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表達式
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.   // Create a new XPath factory
  4.   XPathFactory factory = XPathFactory.newInstance();
  5.   // Create a new XPath instance
  6.   XPath xpath = factory.newXPath();
  7.   // Create a new XPath expression
  8.   String expr = "//cds/cd[@title='August and Everything After']";
  9.   XPathExpression xexpr = xpath.compile(expr);
  10.   Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  11. }
複製代碼




經過這樣簡單的修改,您的工作有很好的回報。因為無論如何都要付出將表達式編譯成字節碼的代價,所以修改後實際增加的成本只是多了一個需要管理的對象,程序流稍微變得不那麼直觀。(強調的是稍微只要略有Java和JAXP經驗,這些代碼仍然很清晰。)除非確信表達式只使用一次,否則對錶達式進行預編譯總是有益的。

處理表達式結果
處理XPath表達式結果的方法僅受限於您的編程知識。得到結果集後,可以做任何需要的處理。比方說,為了驗證清單9清單10中的代碼確實檢索到了正確的CD,可以增加清單11中所示的代碼。

清單11.檢查結果
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.   // Create a new XPath factory
  4.   XPathFactory factory = XPathFactory.newInstance();
  5.   // Create a new XPath instance
  6.   XPath xpath = factory.newXPath();
  7.   // Create a new XPath expression
  8.   String expr = "//cds/cd[@title='August and Everything After']";
  9.   XPathExpression xexpr = xpath.compile(expr);
  10.   Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  11.   if (cd != null) {
  12.     Element el = (Element)cd;
  13.     System.out.println(el.getAttribute("title"));
  14.   } else {
  15.     System.err.println("Error! Node is null!");
  16.   }
  17. }
複製代碼




返回的結果本身沒有多少用處,但關鍵是可以使用DOM對XPath表達式求值返回的Node進行操作。也可以轉換成其他Java / XML格式(如SAX)並提供事件處理程序,或者將節點序列化到磁盤中。也可能數據提取出來供應用程序的其他部分處理,迭代其跟踪路徑或者想要的其他方法.XPath專用的處理已經完成,因此不需要再擔心了。
當然也可以返回這個Node作為新XPath表達式的上下文,如清單12所示。

清單12.多表達式值
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.   // Create a new XPath factory
  4.   XPathFactory factory = XPathFactory.newInstance();
  5.   // Create a new XPath instance
  6.   XPath xpath = factory.newXPath();
  7.   // Create a new XPath expression
  8.   String expr = "//cds/cd[@title='August and Everything After']";
  9.   XPathExpression xexpr = xpath.compile(expr);
  10.   Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  11.   Node track2 = (Node)xpath.evaluate(
  12.     "tracks/track[@id='2']", cd, XPathConstants.NODE);
  13.   Element e = (Element)track2;
  14.   // Test the returned value
  15.   System.out.println(e.getFirstChild().getNodeValue());
  16. }
複製代碼




這就是使用上下文可能非常有意義的地方,可以從前一個表達式的結果開始新的XPath的表達式,需要關係編寫大量的DOM代碼就能非常方便地在文檔中導航。



插入XPath表達式
如果理解了XPath並能使用不同類型的表達式,事情就更有趣了。第1部分已經介紹了XPath必須提供的大多數功能。現在我們將在Java代碼中使用這些功能。

處理節點集
最常見的操作之一是處理表達式返回的節點集。再看一看清單2中的示例XML,假設需要查看Counting Crows的所有唱片。只要兩個XPath表達式就能很容易地實現,如清單13所示。


清單13.處理節點集
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.   // Create a new XPath factory
  4.   XPathFactory factory = XPathFactory.newInstance();
  5.   // Create a new XPath instance
  6.   XPath xpath = factory.newXPath();
  7.   // Create a new XPath expression
  8.   String expr = "//cds/cd[@title='August and Everything After']";
  9.   XPathExpression xexpr = xpath.compile(expr);
  10.   Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  11.   expr = "//cds/artists/artist[group-name='Counting Crows']/@id";
  12.   String crowsID =
  13.     (String)xpath.evaluate(expr, doc, XPathConstants.STRING);
  14.   expr = "//cds/cd[@artistId='" + crowsID + "']";
  15.   NodeList crowsCDs =
  16.     (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);
  17.   expr = "@title";
  18.   XPathExpression title = xpath.compile(expr);
  19.   for (int i = 0; i < crowsCDs.getLength(); i++) {
  20.     Node current = crowsCDs.item(i);
  21.     System.out.println("Found CD titled '" +
  22.       title.evaluate(current) + "'");
  23.   }
  24. }
複製代碼



清單13中使用幾種不同的返回類型的XPath表達式。首先使用返回字符串(實際上是一個數字,不過從本文轉化成數字並沒有什麼好處,因為後面的代碼還要將其作為字符串使用)然後使用該表達式的結果創建一個新的表達式,選擇具有此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中的一句代碼:
  1. title.evaluate(current)
複製代碼


版本這個的evaluate()也。需要上下文參數,但是需要報導查看類型,它似乎返回一個字符串。情況確實如此,版本這個的evaluate()在任何情況下都報導查看一個字符串。有時候,比如計算的表達式返回一個節點集或者一個節點,這可能造成很大的混亂。不過在其他時候,如果確實需要一個文本表達式,這是一種很好的縮寫形式。

增加布爾表達式
XPath的另一個特點是能夠計算布爾表達式。比如,可以使用清單14中的代碼來實現和清單13類似的效果,這一次在迭代全部CD的過程中使用了XPath布爾表達式。

清單14.用XPath表達式返回布爾值
  1. public void test() throws Exception {
  2.   Document doc = buildDocument();
  3.   // Create a new XPath factory
  4.   XPathFactory factory = XPathFactory.newInstance();
  5.   // Create a new XPath instance
  6.   XPath xpath = factory.newXPath();
  7.   // Create a new XPath expression
  8.   String expr = "//cds/cd[@title='August and Everything After']";
  9.   XPathExpression xexpr = xpath.compile(expr);
  10.   Node cd = (Node)xexpr.evaluate(doc, XPathConstants.NODE);
  11.   expr = "//cds/artists/artist[group-name='Counting Crows']/@id";
  12.   String crowsID = (String)xpath.evaluate(expr, doc, XPathConstants.STRING);
  13.   expr = "//cds/cd";
  14.   NodeList allCDs =  
  15.     (NodeList)xpath.evaluate(expr, doc, XPathConstants.NODESET);

  16.   String expr1 = "@artistId='" + crowsID + "'";
  17.   String expr2 = "@title";
  18.   XPathExpression artist = xpath.compile(expr1);
  19.   XPathExpression title = xpath.compile(expr2);
  20.   for (int i = 0; i < allCDs.getLength(); i++) {
  21.     Node current = allCDs.item(i);
  22.     Boolean crowsCD =
  23.       (Boolean)artist.evaluate(current, XPathConstants.BOOLEAN);
  24.     if (crowsCD)
  25.       System.out.println("Found CD titled '" +
  26.         title.evaluate(current) + "'");
  27.   }
  28. }
複製代碼



清單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是一個很好的起點。


文章出處

 

臉書網友討論
*滑块验证:
您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2024-4-16 18:58 , Processed in 0.085597 second(s), 23 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表