日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網營銷解決方案
Java語言的XPathAPI

從 Java 程序中查詢 XML

創(chuàng)新互聯(lián)建站主要從事網站設計、網站建設、網頁設計、企業(yè)做網站、公司建網站等業(yè)務。立足成都服務福綿,十載網站建設經驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:18980820575

Elliotte Harold (elharo@metalab.unc.edu), 副教授, Polytechnic University

簡介: XPath 表達式比繁瑣的文檔對象模型(DOM)導航代碼要容易編寫得多。如果需要從 XML 文檔中提取信息,最快捷、最簡單的辦法就是在 Java? 程序中嵌入 XPath 表達式。Java 5 推出了 javax.xml.xpath 包,這是一個用于 XPath 文檔查詢的獨立于 XML 對象模型的庫。

如果要告訴別人買一加侖牛奶,您會怎么說?“請去買一加侖牛奶回來” 還是 “從前門出去,向左轉,走三個街區(qū)向右轉,再走半個街區(qū)向右轉進入商店。走向四號通道,沿通道走五米向左,拿一瓶一加侖裝的牛奶然后到收銀臺付款。再沿原路回家。” 簡直太可笑了。只要在 “請去買一加侖牛奶回來” 的基礎上稍加指示,多數(shù)成人都能自己買回牛奶來。

查詢語言和計算機搜索與此類似。直接說 “找一個 Cryptonomicon 的副本” 要比編寫搜索某個數(shù)據(jù)庫的詳細邏輯容易得多。由于搜索操作的邏輯非常相似,可以發(fā)明一種通用語言讓您使用 “找到 Neal Stephenson 的所有著作” 這樣的命令,然后編寫對特定數(shù)據(jù)存儲執(zhí)行此類查詢的引擎。

XPath

在眾多查詢語言之中,結構化查詢語言(SQL)是一種針對查詢特定類型的關系庫而設計和優(yōu)化的語言。其他不那么常見的查詢語言還有對象查詢語言(OQL)和 XQuery。但本文的主題是 XPath,一種為查詢 XML 文檔而設計的查詢語言。比如,下面這個簡單的 XPath 查詢可以在文檔中找到作者為 Neal Stephenson 的所有圖書的標題:

 
 
 
  1. //book[author="Neal Stephenson"]/title 

作為對照,查詢同樣信息的純 DOM 搜索代碼如 清單 1 所示:

清單 1. 找到 Neal Stephenson 所有著作 title 元素的 DOM 代碼

 
 
 
  1. ArrayList result = new ArrayList(); 
  2. NodeList books = doc.getElementsByTagName("book"); 
  3. for (int i = 0; i < books.getLength(); i++) { 
  4.     Element book = (Element) books.item(i); 
  5.     NodeList authors = book.getElementsByTagName("author"); 
  6.     boolean stephenson = false; 
  7.     for (int j = 0; j < authors.getLength(); j++) { 
  8.         Element author = (Element) authors.item(j); 
  9.         NodeList children = author.getChildNodes(); 
  10.         StringBuffer sb = new StringBuffer(); 
  11.         for (int k = 0; k < children.getLength(); k++) { 
  12.             Node child = children.item(k); 
  13.             // really should to do this recursively 
  14.             if (child.getNodeType() == Node.TEXT_NODE) { 
  15.                 sb.append(child.getNodeValue()); 
  16.             } 
  17.         } 
  18.         if (sb.toString().equals("Neal Stephenson")) { 
  19.             stephenson = true; 
  20.             break; 
  21.         } 
  22.     } 
  23.     if (stephenson) { 
  24.         NodeList titles = book.getElementsByTagName("title"); 
  25.         for (int j = 0; j < titles.getLength(); j++) { 
  26.             result.add(titles.item(j)); 
  27.         } 
  28.     } 

不論您是否相信,清單 1 中的 DOM 顯然不如簡單的 XPath 表達式通用或者健壯。您愿意編寫、調試和維護哪一個?我想答案很明顯。

但是雖然有很強的表達能力,XPath 并不是 Java 語言,事實上 XPath 不是一種完整的編程語言。有很多東西用 XPath 表達不出來,甚至有些查詢也無法表達。比方說,XPath 不能查找國際標準圖書編碼(ISBN)檢驗碼不匹配的所有圖書,或者找出境外帳戶數(shù)據(jù)庫顯示欠帳的所有作者。幸運的是,可以把 XPath 結合到 Java 程序中,這樣就能發(fā)揮兩者的優(yōu)勢了:Java 做 Java 所擅長的,XPath 做 XPath 所擅長的。

直到最近,Java 程序執(zhí)行 XPath 查詢所需要的應用程序編程接口(API)還因形形色色的 XPath 引擎而各不相同。Xalan 有一種 API,Saxon 使用另一種,其他引擎則使用其他的 API。這意味著代碼往往把您限制到一種產品上。理想情況下,最好能夠試驗具有不同性能特點的各種引擎,而不會帶來不適當?shù)穆闊┗蛘咧匦戮帉懘a。

于是,Java 5 推出了 javax.xml.xpath 包,提供一個引擎和對象模型獨立的 XPath 庫。這個包也可用于 Java 1.3 及以后的版本,但需要單獨安裝 Java API for XML Processing (JAXP) 1.3。Xalan 2.7 和 Saxon 8 以及其他產品包含了這個庫的實現(xiàn)。

一個簡單的例子

我將舉例說明如何使用它。然后再討論一些細節(jié)問題。假設要查詢一個圖書列表,尋找 Neal Stephenson 的著作。具體來說,這個圖書列表的形式如 清單 2 所示:

清單 2. 包含圖書信息的 XML 文檔

 
 
 
  1.  
  2.      
  3.         Snow Crash 
  4.         Neal Stephenson 
  5.         Spectra 
  6.         0553380958 
  7.         14.95 
  8.      
  9.      
  10.         Burning Tower 
  11.         Larry Niven 
  12.         Jerry Pournelle 
  13.         Pocket 
  14.         0743416910 
  15.         5.99 
  16.      
  17.      
  18.         Zodiac 
  19.         Neal Stephenson 
  20.         Spectra 
  21.         0553573862 
  22.         7.50 
  23.      
  24.      
  25.  

抽象工廠

XPathFactory 是一個抽象工廠。抽象工廠設計模式使得這一種 API 能夠支持不同的對象模型,如 DOM、JDOM 和 XOM。為了選擇不同的模型,需要向XPathFactory.newInstance() 方法傳遞標識對象模型的統(tǒng)一資源標識符(URI)。比如 http://xom.nu/ 可以選擇 XOM。但實際上,到目前為止 DOM 是該 API 支持的惟一對象模型。

查找所有圖書的 XPath 查詢非常簡單://book[author="Neal Stephenson"]。為了找出這些圖書的標題,只要增加一步,表達式就變成了 //book[author="Neal Stephenson"]/title。最后,真正需要的是title 元素的文本節(jié)點孩子。這就要求再增加一步,完整的表達式就是//book[author="Neal Stephenson"]/title/text()。

現(xiàn)在我提供一個簡單的程序,它從 Java 語言中執(zhí)行這個查詢,然后把找到的所有圖書的標題打印出來。首先,需要將文檔加載到一個 DOMDocument 對象中。為了簡化起見,假設該文檔在當前工作目錄的 books.xml 文件中。下面的簡單代碼片段解析文檔并建立對應的Document 對象:

 
 
 
  1. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
  2. factory.setNamespaceAware(true); // never forget this! 
  3. DocumentBuilder builder = factory.newDocumentBuilder(); 
  4. Document doc = builder.parse("books.xml"); 

到目前為止,這僅僅是標準的 JAXP 和 DOM,沒有什么新鮮的。

接下來創(chuàng)建 XPathFactory:

 
 
 
  1. XPathFactory factory = XPathFactory.newInstance(); 

然后使用這個工廠創(chuàng)建 XPath 對象:

 
 
 
  1. XPath xpath = factory.newXPath(); 

XPath 對象編譯 XPath 表達式:

 
 
 
  1. PathExpression expr = xpath.compile("http://book[author='Neal Stephenson']/title/text()"); 

直接求值

如果 XPath 表達式只使用一次,可以跳過編譯步驟直接對XPath 對象調用 evaluate() 方法。但是,如果同一個表達式要重復使用多次,編譯可能更快一些。

最后,計算 XPath 表達式得到結果。表達式是針對特定的上下文節(jié)點計算的,在這個例子中是整個文檔。還必須指定返回類型。這里要求返回一個節(jié)點集:

 
 
 
  1. Object result = expr.evaluate(doc, XPathConstants.NODESET); 

可以將結果強制轉化成 DOM NodeList,然后遍歷列表得到所有的標題:

 
 
 
  1. NodeList nodes = (NodeList) result; 
  2. for (int i = 0; i < nodes.getLength(); i++) { 
  3. System.out.println(nodes.item(i).getNodeValue()); 

清單 4 把上述片段組合到了一個程序中。還要注意,這些方法可能拋出一些檢查異常,這些異常必須在 throws 子句中聲明,但是我在上面把它們掩蓋起來了:

清單 4. 用固定的 XPath 表達式查詢 XML 文檔的完整程序

 
 
 
  1. import java.io.IOException; 
  2. import org.w3c.dom.*; 
  3. import org.xml.sax.SAXException; 
  4. import javax.xml.parsers.*; 
  5. import javax.xml.xpath.*; 
  6. public class XPathExample { 
  7.   public static void main(String[] args) 
  8.    throws ParserConfigurationException, SAXException, 
  9.           IOException, XPathExpressionException { 
  10.     DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); 
  11.     domFactory.setNamespaceAware(true); // never forget this! 
  12.     DocumentBuilder builder = domFactory.newDocumentBuilder(); 
  13.     Document doc = builder.parse("books.xml"); 
  14.     XPathFactory factory = XPathFactory.newInstance(); 
  15.     XPath xpath = factory.newXPath(); 
  16.     XPathExpression expr 
  17.      = xpath.compile("http://book[author='Neal Stephenson']/title/text()"); 
  18.     Object result = expr.evaluate(doc, XPathConstants.NODESET); 
  19.     NodeList nodes = (NodeList) result; 
  20.     for (int i = 0; i < nodes.getLength(); i++) { 
  21.         System.out.println(nodes.item(i).getNodeValue()); 
  22.     } 
  23.   } 

XPath 數(shù)據(jù)模型

每當混合使用諸如 XPath 和 Java 這樣兩種不同的語言時,必定會有某些將兩者粘合在一起的明顯接縫。并非一切都很合拍。XPath 和 Java 語言沒有同樣的類型系統(tǒng)。XPath 1.0 只有四種基本數(shù)據(jù)類型:

◆ node-set

◆ number

◆ boolean

◆ string

當然,Java 語言有更多的數(shù)據(jù)類型,包括用戶定義的對象類型。

多數(shù) XPath 表達式,特別是位置路徑,都返回節(jié)點集。但是還有其他可能。比如,XPath 表達式 count(//book) 返回文檔中的圖書數(shù)量。XPath 表達式 count(//book[@author="Neal Stephenson"]) > 10 返回一個布爾值:如果文檔中 Neal Stephenson 的著作超過 10 本則返回 true,否則返回 false。

evaluate() 方法被聲明為返回 Object。實際返回什么依賴于 XPath 表達式的結果以及要求的類型。一般來說,XPath 的

◆ number 映射為 java.lang.Double

◆ string 映射為 java.lang.String

◆ boolean 映射為 java.lang.Boolean

◆ node-set 映射為 org.w3c.dom.NodeList

XPath 2

前面一直假設您使用的是 XPath 1.0。XPath 2 大大擴展和修改了類型系統(tǒng)。Java XPath API 支持 XPath 2 所需的主要修改是為返回 XPath 2 新數(shù)據(jù)類型增加常量。

在 Java 中計算 XPath 表達式時,第二個參數(shù)指定需要的返回類型。有五種可能,都在 javax.xml.xpath.XPathConstants 類中命名了常量:

XPathConstants.NODESET
XPathConstants.BOOLEAN
XPathConstants.NUMBER
XPathConstants.STRING
XPathConstants.NODE

最后一個 XPathConstants.NODE 實際上沒有匹配的 XPath 類型。只有知道 XPath 表達式只返回一個節(jié)點或者只需要一個節(jié)點時才使用它。如果 XPath 表達式返回了多個節(jié)點并且指定了 XPathConstants.NODE,則 evaluate() 按照文檔順序返回第一個節(jié)點。如果 XPath 表達式選擇了一個空集并指定了 XPathConstants.NODE,則 evaluate() 返回 null。

如果不能完成要求的轉換,evaluate() 將拋出 XPathException。

名稱空間上下文

若 XML 文檔中的元素在名稱空間中,查詢該文檔的 XPath 表達式必須使用相同的名稱空間。XPath 表達式不一定要使用相同的前綴,只需要名稱空間 URI 相同即可。事實上,如果 XML 文檔使用默認名稱空間,那么盡管目標文檔沒有使用前綴,XPath 表達式也必須使用前綴。

但是,Java 程序不是 XML 文檔,因此不能用一般的名稱空間解析。必須提供一個對象將前綴映射到名稱空間 URI。該對象是javax.xml.namespace.NamespaceContext 接口的實例。比如,假設圖書文檔放在 http://www.example.com/books 名稱空間中,如 清單 5 所示:

清單 5. 使用默認名稱空間的 XML 文檔

 
 
 
  1.  
  2.      
  3.         Snow Crash 
  4.         Neal Stephenson 
  5.         Spectra 
  6.         0553380958 
  7.         14.95 
  8.      
  9.      
  10.  

查找 Neal Stephenson 全部著作標題的 XPath 表達式就要改為 //pre:book[pre:author="Neal Stephenson"]/pre:title/text()。但是,必須將前綴 pre 映射到 URI http://www.example.com/books。NamespaceContext 接口在 Java 軟件開發(fā)工具箱(JDK)或 JAXP 中沒有默認實現(xiàn)似乎有點笨,但確實如此。不過,自己實現(xiàn)也不難。清單 6 對一個名稱空間給出了簡單的實現(xiàn)。還需要映射xml 前綴。

清單 6. 綁定一個名稱空間和默認名稱空間的簡單上下文

 
 
 
  1. import java.util.Iterator; 
  2. import javax.xml.*; 
  3. import javax.xml.namespace.NamespaceContext; 
  4. public class PersonalNamespaceContext implements NamespaceContext { 
  5.     public String getNamespaceURI(String prefix) { 
  6.         if (prefix == null) throw new NullPointerException("Null prefix"); 
  7.         else if ("pre".equals(prefix)) return "http://www.example.org/books"; 
  8.         else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI; 
  9.         return XMLConstants.NULL_NS_URI; 
  10.     } 
  11.     // This method isn't necessary for XPath processing. 
  12.     public String getPrefix(String uri) { 
  13.         throw new UnsupportedOperationException(); 
  14.     } 
  15.     // This method isn't necessary for XPath processing either. 
  16.     public Iterator getPrefixes(String uri) { 
  17.         throw new UnsupportedOperationException(); 
  18.     } 

使用映射存儲綁定和增加 setter 方法實現(xiàn)名稱空間上下文的重用也不難。

創(chuàng)建 NamespaceContext 對象后,在編譯表達式之前將其安裝到 XPath 對象上。以后就可以像以前一樣是用這些前綴查詢了。比如:

清單 7. 使用名稱空間的 XPath 查詢

 
 
 
  1. XPathFactory factory = XPathFactory.newInstance(); 
  2. XPath xpath = factory.newXPath(); 
  3. xpath.setNamespaceContext(new PersonalNamespaceContext()); 
  4. XPathExpression expr 
  5.   = xpath.compile("http://pre:book[pre:author='Neal Stephenson']/pre:title/text()"); 
  6.  
  7. Object result = expr.evaluate(doc, XPathConstants.NODESET); 
  8. NodeList nodes = (NodeList) result; 
  9. for (int i = 0; i < nodes.getLength(); i++) { 
  10.     System.out.println(nodes.item(i).getNodeValue()); 

函數(shù)求解器

有時候,在 Java 語言中定義用于 XPath 表達式的擴展函數(shù)很有用。這些函數(shù)可以執(zhí)行用純 XPath 很難或者無法執(zhí)行的任務。不過必須是真正的函數(shù),而不是隨意的方法。就是說不能有副作用。(XPath 函數(shù)可以按照任意的順序求值任意多次。)

通過 Java XPath API 訪問的擴展函數(shù)必須實現(xiàn) javax.xml.xpath.XPathFunction 接口。這個接口只聲明了一個方法 evaluate:

 
 
 
  1. public Object evaluate(List args) throws XPathFunctionException 

該方法必須返回 Java 語言能夠轉換到 XPath 的五種類型之一:

◆ String

◆ Double

◆ Boolean

◆ Nodelist

◆ Node

比如,清單 8 顯示了一個擴展函數(shù),它檢查 ISBN 的校驗和并返回 Boolean。這個校驗和的基本規(guī)則是前九位數(shù)的每一位乘上它的位置(即第一位數(shù)乘上 1,第二位數(shù)乘上 2,依次類推)。將這些數(shù)加起來然后取除以 11 的余數(shù)。如果余數(shù)是 10,那么最后一位數(shù)就是 X。

清單 8. 檢查 ISBN 的 XPath 擴展函數(shù)

 
 
 
  1. import java.util.List; 
  2. import javax.xml.xpath.*; 
  3. import org.w3c.dom.*; 
  4. public class ISBNValidator implements XPathFunction { 
  5.   // This class could easily be implemented as a Singleton. 
  6.   public Object evaluate(List args) throws XPathFunctionException { 
  7.     if (args.size() != 1) { 
  8.       throw new XPathFunctionException("Wrong number of arguments to valid-isbn()"); 
  9.     } 
  10.     String isbn; 
  11.     Object o = args.get(0); 
  12.     // perform conversions 
  13.     if (o instanceof String) isbn = (String) args.get(0); 
  14.     else if (o instanceof Boolean) isbn = o.toString(); 
  15.     else if (o instanceof Double) isbn = o.toString(); 
  16.     else if (o instanceof NodeList) { 
  17.         NodeList list = (NodeList) o; 
  18.         Node node = list.item(0); 
  19.         // getTextContent is available in Java 5 and DOM 3. 
  20.         // In Java 1.4 and DOM 2, you'd need to recursively 
  21.         // accumulate the content. 
  22.         isbn= node.getTextContent(); 
  23.     } 
  24.     else { 
  25.         throw new XPathFunctionException("Could not convert argument type"); 
  26.     } 
  27.     char[] data = isbn.toCharArray(); 
  28.     if (data.length != 10) return Boolean.FALSE; 
  29.     int checksum = 0; 
  30.     for (int i = 0; i < 9; i++) { 
  31.         checksum += (i+1) * (data[i]-'0'); 
  32.     } 
  33.     int checkdigit = checksum % 11; 
  34.     if (checkdigit + '0' == data[9] || (data[9] == 'X' && checkdigit == 10)) { 
  35.         return Boolean.TRUE; 
  36.     } 
  37.     return Boolean.FALSE; 
  38.   } 

下一步讓這個擴展函數(shù)能夠在 Java 程序中使用。為此,需要在編譯表達式之前向 XPath 對象安裝javax.xml.xpath.XPathFunctionResolver。函數(shù)求解器將函數(shù)的 XPath 名稱和名稱空間 URI 映射到實現(xiàn)該函數(shù)的 Java 類。清單 9是一個簡單的函數(shù)求解器,將擴展函數(shù) valid-isbn 和名稱空間 http://www.example.org/books 映射到 清單 8 中的類。比如,XPath 表達式 //book[not(pre:valid-isbn(isbn))] 可以找到 ISBN 校驗和不匹配的所有圖書。

清單 9. 識別 valid-isbn 擴展函數(shù)的上下文

 
 
 
  1. import javax.xml.namespace.QName; 
  2. import javax.xml.xpath.*; 
  3. public class ISBNFunctionContext implements XPathFunctionResolver { 
  4.   private static final QName name 
  5.    = new QName("http://www.example.org/books", "valid-isbn"); 
  6.   public XPathFunction resolveFunction(QName name, int arity) { 
  7.       if (name.equals(ISBNFunctionContext.name) && arity == 1) { 
  8.           return new ISBNValidator(); 
  9.       } 
  10.       return null; 
  11.   } 

由于擴展函數(shù)必須有名稱空間,所以計算包含擴展函數(shù)的表達式時必須使用 NamespaceResolver,即便查詢的文檔沒有使用任何名稱空間。由于 XPathFunctionResolver、XPathFunction 和 NamespaceResolver 都是接口,如果方便的話可以將它們放在所有的類中。

結束語

用 SQL 和 XPath 這樣的聲明性語言編寫查詢,要比使用 Java 和 C 這樣的命令式語言容易得多。但是,用 Java 和 C 這樣的圖靈完整語言編寫復雜的邏輯,又比 SQL 和 XPath 這樣的聲明性語言容易得多。所幸的是,通過使用 Java Database Connectivity (JDBC) 和javax.xml.xpath 之類的 API 可以將兩者結合起來。隨著世界上越來越多的數(shù)據(jù)轉向 XML,javax.xml.xpath 將與 java.sql 一樣變得越來越重要。


網站名稱:Java語言的XPathAPI
文章位置:http://www.dlmjj.cn/article/dpijoog.html