2011-03-28 81 views
6

我花了一天的時間嘗試從以下文檔中提取一個XML節點,並且無法掌握XML名稱空間的細微差別以使其工作。XPath,XML命名空間和Java

XML文件是大的總張貼所以這裏是我所關心的部分:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?> 
<XFDL xmlns="http://www.PureEdge.com/XFDL/6.5" xmlns:custom="http://www.PureEdge.com/XFDL/Custom" xmlns:designer="http://www.PureEdge.com/Designer/6.1" xmlns:pecs="http://www.PureEdge.com/PECustomerService" xmlns:xfdl="http://www.PureEdge.com/XFDL/6.5"> 
    <globalpage sid="global"> 
     <global sid="global"> 
     <xmlmodel xmlns:xforms="http://www.w3.org/2003/xforms"> 
      <instances> 
       <xforms:instance id="metadata"> 
        <form_metadata> 
        <metadataver version="1.0"/> 
        <metadataverdate> 
         <date day="05" month="Jul" year="2005"/> 
        </metadataverdate> 
        <title> 
         <documentnbr number="2062" prefix.army="DA" scope="army" suffix=""/> 
         <longtitle>HAND RECEIPT/ANNEX NUMBER </longtitle> 
        </title> 

文檔過程繼續,以及形成一路下滑。我試圖從「documentnbr」標籤(從底部三個)中提取「數字」屬性。

,我使用做到這一點的代碼如下所示:

/*** 
    * Locates the Document Number information in the file and returns the form number. 
    * @return File's self-declared number. 
    * @throws InvalidFormException Thrown when XPath cannot find the "documentnbr" element in the file. 
    */ 
    public String getFormNumber() throws InvalidFormException 
    { 
     try{ 
      XPath xPath = XPathFactory.newInstance().newXPath(); 
      xPath.setNamespaceContext(new XFDLNamespaceContext()); 

      Node result = (Node)xPath.evaluate(QUERY_FORM_NUMBER, doc, XPathConstants.NODE); 
      if(result != null) { 
       return result.getNodeValue(); 
      } else { 
       throw new InvalidFormException("Unable to identify form."); 
      } 

     } catch (XPathExpressionException err) { 
      throw new InvalidFormException("Unable to find form number in file."); 
     } 

    } 

哪裏QUERY_FORM_NUMBER是我的XPath表達式,並XFDLNamespaceContext實現NamespaceContext,看起來像這樣:

public class XFDLNamespaceContext implements NamespaceContext { 

    @Override 
    public String getNamespaceURI(String prefix) { 
     if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix"); 
     else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) 
      return "http://www.PureEdge.com/XFDL/6.5"; 
     else if ("custom".equals(prefix)) 
      return "http://www.PureEdge.com/XFDL/Custom"; 
     else if ("designer".equals(prefix)) 
      return "http://www.PureEdge.com/Designer/6.1"; 
     else if ("pecs".equals(prefix)) 
      return "http://www.PureEdge.com/PECustomerService"; 
     else if ("xfdl".equals(prefix)) 
      return "http://www.PureEdge.com/XFDL/6.5";  
     else if ("xforms".equals(prefix)) 
      return "http://www.w3.org/2003/xforms"; 
     else  
      return XMLConstants.NULL_NS_URI; 
    } 

    @Override 
    public String getPrefix(String arg0) { 
     // TODO Auto-generated method stub 
     return null; 
    } 

    @Override 
    public Iterator getPrefixes(String arg0) { 
     // TODO Auto-generated method stub 
     return null; 
    } 

} 

我已經嘗試了許多不同的XPath查詢,但我一直覺得這應該工作:

protected static final String QUERY_FORM_NUMBER = 
     "/globalpage/global/xmlmodel/xforms:instances/instance" + 
     "/form_metadata/title/documentnbr[number]"; 

不幸的是,它不起作用,我不斷得到一個空回報。

我已經完成了相當數量的閱讀here,herehere,但沒有任何證據足以說明我的工作。

當我明白這一點時,我幾乎肯定我要面子,但我真的很機智地結束了我失蹤的事情。

感謝您閱讀所有這些內容,並提前感謝您的幫助。

-Andy

回答

5

啊哈,我試着調試你的表達式+讓它工作。你錯過了一些事情。這個XPath表達式應該這樣做:

/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number 
  1. 您需要包括根元素(XFDL在這種情況下)
  2. 我沒有最終需要在因某種原因表達式中使用的任何命名空間。不知道爲什麼。如果是這種情況,那麼NamespaceContext.getNamespaceURI()永遠不會被調用。如果我將instance替換爲xforms:instance,那麼getNamespaceURI()將以xforms作爲輸入參數被調用一次,但程序會引發異常。
  3. 屬性值的語法是@attr,而不是[attr]

我的完整的示例代碼:

import java.io.File; 
import java.io.IOException; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.Map; 

import javax.xml.XMLConstants; 
import javax.xml.namespace.NamespaceContext; 
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.XPathExpressionException; 
import javax.xml.xpath.XPathFactory; 

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

public class XPathNamespaceExample { 
    static public class MyNamespaceContext implements NamespaceContext { 
     final private Map<String, String> prefixMap; 
     MyNamespaceContext(Map<String, String> prefixMap) 
     { 
      if (prefixMap != null) 
      { 
       this.prefixMap = Collections.unmodifiableMap(new HashMap<String, String>(prefixMap)); 
      } 
      else 
      { 
       this.prefixMap = Collections.emptyMap(); 
      } 
     } 
     public String getPrefix(String namespaceURI) { 
      // TODO Auto-generated method stub 
      return null; 
     } 
     public Iterator getPrefixes(String namespaceURI) { 
      // TODO Auto-generated method stub 
      return null; 
     } 
     public String getNamespaceURI(String prefix) { 
       if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix"); 
       else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) 
        return "http://www.PureEdge.com/XFDL/6.5"; 
       else if ("custom".equals(prefix)) 
        return "http://www.PureEdge.com/XFDL/Custom"; 
       else if ("designer".equals(prefix)) 
        return "http://www.PureEdge.com/Designer/6.1"; 
       else if ("pecs".equals(prefix)) 
        return "http://www.PureEdge.com/PECustomerService"; 
       else if ("xfdl".equals(prefix)) 
        return "http://www.PureEdge.com/XFDL/6.5";  
       else if ("xforms".equals(prefix)) 
        return "http://www.w3.org/2003/xforms"; 
       else  
        return XMLConstants.NULL_NS_URI; 
     } 


    } 

    protected static final String QUERY_FORM_NUMBER = 
     "/XFDL/globalpage/global/xmlmodel/xforms:instances/instance" + 
     "/form_metadata/title/documentnbr[number]"; 

    public static void main(String[] args) { 
     try 
     { 
      DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); 
      DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); 
      Document doc = docBuilder.parse(new File(args[0])); 
      System.out.println(extractNodeValue(doc, "/XFDL/globalpage/@sid")); 
      System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/@id")); 
      System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number")); 
     } catch (SAXException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } catch (ParserConfigurationException e) { 
      e.printStackTrace(); 
     } 
    } 

    private static String extractNodeValue(Document doc, String expression) { 
     try{ 

      XPath xPath = XPathFactory.newInstance().newXPath(); 
      xPath.setNamespaceContext(new MyNamespaceContext(null)); 

      Node result = (Node)xPath.evaluate(expression, doc, XPathConstants.NODE); 
      if(result != null) { 
       return result.getNodeValue(); 
      } else { 
       throw new RuntimeException("can't find expression"); 
      } 

     } catch (XPathExpressionException err) { 
      throw new RuntimeException(err); 
     } 
    } 
} 
+0

完美解決方案,我還是不太明白的命名空間,但在現在最少的代碼工作。 非常感謝。 – MrWizard54 2011-03-29 11:16:36

+8

@Jason:「由於某種原因,我最終沒有在表達式中使用任何名稱空間。」在標準的Java實現中,默認情況下,DocumentBuilderFactory會生成namespace-_unaware_分析器。在生成DocumentBuilder之前添加'dbfac.SetNamespaceAware(true)'可能會改變結果。 – 2013-03-08 15:18:09

3

SAX(可替代的XPath)版本:

SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); 
final String[] number = new String[1]; 
DefaultHandler handler = new DefaultHandler() 
{   
    @Override 
    public void startElement(String uri, String localName, String qName, 
    Attributes attributes) throws SAXException 
    { 
     if (qName.equals("documentnbr")) 
      number[0] = attributes.getValue("number"); 
    } 
}; 
saxParser.parse("input.xml", handler); 
System.out.println(number[0]); 

我看到它的複雜使用XPath名稱空間,因爲它應該(我的想法)。這裏是我的(簡單)的代碼:

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

NamespaceContextMap contextMap = new NamespaceContextMap(); 
contextMap.put("custom", "http://www.PureEdge.com/XFDL/Custom"); 
contextMap.put("designer", "http://www.PureEdge.com/Designer/6.1"); 
contextMap.put("pecs", "http://www.PureEdge.com/PECustomerService"); 
contextMap.put("xfdl", "http://www.PureEdge.com/XFDL/6.5"); 
contextMap.put("xforms", "http://www.w3.org/2003/xforms"); 
contextMap.put("", "http://www.PureEdge.com/XFDL/6.5"); 

xpath.setNamespaceContext(contextMap); 
String expression = "//:documentnbr/@number"; 
InputSource inputSource = new InputSource("input.xml"); 
String number; 
number = (String) xpath.evaluate(expression, inputSource, XPathConstants.STRING); 
System.out.println(number); 

您可以從here(GPL許可證)獲得NamespaceContextMap類(不是我)。還有6376058錯誤。

+0

如果我沒有在應用程序的其他地方使用DOM/XPath,我會走這條路,但是現在我已經朝着另一個方向前進了。 在應用程序內部混合是否存在傳統觀點? – MrWizard54 2011-03-29 11:15:06

+0

您可以使用帶有第二個代碼的DOM/XPath(xpath.evalute也需要Document對象)。在我看來,最好使用NamespaceContextMap類(imho應該在JDK中)。 – 2011-04-06 23:33:10

2

看看XPathAPI庫。這是一種更簡單的方式來使用XPath,而不會干擾低級別的Java API,特別是在處理名稱空間時。

的代碼即可獲得number屬性將是:

String num = XPathAPI.selectSingleNodeAsString(doc, '//documentnbr/@number'); 

命名空間從根節點(doc在這種情況下)自動提取。如果你需要明確定義,你可以使用這個額外的命名空間:

Map<String, String> nsMap = new HashMap<String, String>(); 
nsMap.put("xforms", "http://www.w3.org/2003/xforms"); 

String num = 
    XPathAPI.selectSingleNodeAsString(doc, '//documentnbr/@number', nsMap); 

(聲明:我是圖書館的作者)