2014-02-12 35 views
4

我需要使用Java應用程序中的XPath表達式來查詢XML文檔。我創建了以下類,它接受文件(本地硬盤上XML文檔的位置)和XPath查詢,並返回給定文檔上給定查詢的結果。具有名稱空間的文檔的Java XPath解析器的奇怪行爲

import java.io.File; 
import java.io.IOException; 

import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException; 
import javax.xml.xpath.XPath; 
import javax.xml.xpath.XPathConstants; 
import javax.xml.xpath.XPathException; 
import javax.xml.xpath.XPathExpression; 
import javax.xml.xpath.XPathFactory; 

import org.w3c.dom.Document; 
import org.w3c.dom.Node; 
import org.xml.sax.SAXException; 

public class XPathResolver 
{ 
    public String resolveXPath(File xmlFile, String xpathExpr) throws XPathException, ParserConfigurationException, SAXException, IOException 
    { 
     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
     DocumentBuilder builder = factory.newDocumentBuilder(); 
     Document doc = builder.parse(xmlFile); 

     XPathFactory xPathfactory = XPathFactory.newInstance(); 
     XPath xpath = xPathfactory.newXPath(); 

     XPathExpression expr = xpath.compile(xpathExpr); 

     return (String) expr.evaluate(doc, XPathConstants.STRING); 
    } 
} 

現在假設我有以下XML文檔。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<Document> 
    <DocumentFormat>Email</DocumentFormat> 
    <FileFormat>PDF</FileFormat> 
</Document> 

評價都/Document/FileFormat//FileFormat回報PDF(如預期)。

但是,現在假設一個具有名稱空間前綴的文檔,如下所示。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<Document xmlns:file="http://www.example.com/xml/file"> 
    <DocumentFormat>Email</DocumentFormat> 
    <file:FileFormat>PDF</file:FileFormat> 
</Document> 

現在/Document/FileFormat回報PDF,但//FileFormat不返回任何東西。

爲什麼我的代碼在命名空間前綴爲空的情況下不能返回預期的輸出,我該如何解決?

+0

您實際上沒有告訴我們您正在使用哪種XPath處理器。如果切換到另一個,請檢查您是否得到不同的答案。 –

回答

3

我想你的例子有JDK 1.7.0.51,可以確認你的結果。這看起來有點奇怪,但DocumentBuilderFactory的默認行爲是不能識別名稱空間。

所以,你必須打開它在第一次:

factory.setNamespaceAware(true); 

然後第二個文件沒有用於XPath表達式沒有結果如預期。

您必須將您的表情更改爲:/Document/file:FileFormat//file:FileFormat。 最後一步,您必須註冊一個NamespaceContext實現,該實現將XPath表達式中使用的名稱空間前綴映射到名稱空間URI。不幸的是,沒有默認的實現。

public String resolveXPath(File xmlFile, String xpathExpr) throws XPathException, ParserConfigurationException, SAXException, IOException, XPathExpressionException 
{ 
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 

    // Turn namespace aware on 
    factory.setNamespaceAware(true); 
    DocumentBuilder builder = factory.newDocumentBuilder(); 
    Document doc = builder.parse(xmlFile); 

    XPathFactory xPathfactory = XPathFactory.newInstance(); 
    XPath xpath = xPathfactory.newXPath(); 

    // Set the NamespaceContext   
    xpath.setNamespaceContext(new MyNamespaceContext()); 


    XPathExpression expr = xpath.compile(xpathExpr); 

    return (String) expr.evaluate(doc, XPathConstants.STRING); 
} 

class MyNamespaceContext implements NamespaceContext { 

    private Map<String, String> ns; 
    private Map<String, String> nsReverted; 

    public MyNamespaceContext() { 
    ns = new TreeMap<String, String>(); 

    // Default namespaces and prefixes according to the documentation 
    ns.put(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI); 
    ns.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI); 
    ns.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI); 

    // Now our self defined namespace 
    ns.put("file", "http://www.example.com/xml/file"); 


    nsReverted = new TreeMap<String, String>(); 
    for(Entry<String, String> entry : ns.entrySet()) { 
     nsReverted.put(entry.getValue(), entry.getValue()); 
    } 
    } 

    @Override 
    public String getNamespaceURI(String prefix) { 
    if(prefix == null) { 
     throw new IllegalArgumentException(); 
    } 
    final String uri = ns.get(prefix); 
    return uri == null ? XMLConstants.NULL_NS_URI : uri; 
    } 

    @Override 
    public String getPrefix(String namespaceURI) { 
    if(namespaceURI == null) { 
     throw new IllegalArgumentException(); 
    } 
    return nsReverted.get(namespaceURI); 
    } 

    @Override 
    public Iterator getPrefixes(String namespaceURI) { 
    return ns.keySet().iterator(); 
    } 

}