2012-05-21 28 views
33

我的問題是:當父元素的「grandchild」有相同名稱的其他元素時,如何直接在特定父元素下獲取元素。通過名稱獲取XML只是直接的子元素

我正在使用Java DOM library來解析XML Elements,我遇到了麻煩。下面是XML的一些(一小部分),我使用的是:

<notifications> 
    <notification> 
    <groups> 
     <group name="zip-group.zip" zip="true"> 
     <file location="C:\valid\directory\" /> 
     <file location="C:\another\valid\file.doc" /> 
     <file location="C:\valid\file\here.txt" /> 
     </group> 
    </groups> 
    <file location="C:\valid\file.txt" /> 
    <file location="C:\valid\file.xml" /> 
    <file location="C:\valid\file.doc" /> 
    </notification> 
</notifications> 

正如你可以看到,有兩個地方可以放置<file>元素。無論是在小組還是在外面的小組。我真的希望它這樣構造,因爲它更加用戶友好。

現在,只要我致電notificationElement.getElementsByTagName("file");,它會給我所有<file>元素,包括那些在<group>元素下的元素。我以不同的方式處理這些文件中的每一種,所以這種功能是不可取的。

我想過兩種解決方案:

  1. 獲取的文件元素的父元素與它相應的處理(取決於它是否是<notification><group>
  2. 重命名第二<file>元素,以避免混淆

這兩種解決方案都不如想象的那樣只是讓事物保持原樣,並且只獲得直接子的<file>元素仁<notification>元素。

我願意對「最好」的方式做到這一點IMPO意見和答案,但我在DOM解決方案很感興趣,因爲這是這個項目的其餘部分使用。謝謝。

+0

爲什麼不使用XPath來獲取這兩個節點列表並將它們區別對待? '// groups/group/file'和'// notification/file'就足以擁有它們。或者你只需​​要一個XPath就可以獲得它們全部? – Alex

+0

爲什麼不通過你自己循環直接的孩子來創建這個集合,比如命中:「NodeList nodes = element.getChildNodes(); for(int i = 0; i Dmitry

+0

@Alex org.w3c.dom不支持XPath;他想要使用不同的庫,比如org.jdom.xpath,儘管我完全同意這是更優雅的方法。 –

回答

12

好了,DOM解決這個問題其實很簡單,即使它不是太高雅了。當我遍歷通過filesNodeList,當我呼叫notificationElement.getElementsByTagName("file");時返回,我只是檢查父節點的名稱是否爲「通知」。如果不是,那麼我會忽略它,因爲這將由<group>元素處理。這裏是我的代碼如下:

for (int j = 0; j < filesNodeList.getLength(); j++) { 
    Element fileElement = (Element) filesNodeList.item(j); 
    if (!fileElement.getParentNode().getNodeName().equals("notification")) { 
    continue; 
    } 
    ... 
} 
+1

Bravo !!!!!!!!!! – madhairsilence

+0

是演員安全嗎? –

+0

@JanusTroelsen,如果您在將項目作爲元素進行投票時討論第二行,那麼這取決於您正在解析的DOM ......如果不是,那麼您的意思是什麼? – kentcdodds

13

您可以使用XPath來使用它,使用兩個路徑來獲取它們並以不同方式處理它們。

要獲得<file>節點<notification>使用//notification/file併爲<group>使用//groups/group/file的那些直接孩子。

這是一個簡單的示例:

public class SO10689900 { 
    public static void main(String[] args) throws Exception { 
     DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 
     Document doc = db.parse(new InputSource(new StringReader("<notifications>\n" + 
       " <notification>\n" + 
       " <groups>\n" + 
       "  <group name=\"zip-group.zip\" zip=\"true\">\n" + 
       "  <file location=\"C:\\valid\\directory\\\" />\n" + 
       "  <file location=\"C:\\this\\file\\doesn't\\exist.grr\" />\n" + 
       "  <file location=\"C:\\valid\\file\\here.txt\" />\n" + 
       "  </group>\n" + 
       " </groups>\n" + 
       " <file location=\"C:\\valid\\file.txt\" />\n" + 
       " <file location=\"C:\\valid\\file.xml\" />\n" + 
       " <file location=\"C:\\valid\\file.doc\" />\n" + 
       " </notification>\n" + 
       "</notifications>"))); 
     XPath xpath = XPathFactory.newInstance().newXPath(); 
     XPathExpression expr1 = xpath.compile("//notification/file"); 
     NodeList nodes = (NodeList)expr1.evaluate(doc, XPathConstants.NODESET); 
     System.out.println("Files in //notification"); 
     printFiles(nodes); 

     XPathExpression expr2 = xpath.compile("//groups/group/file"); 
     NodeList nodes2 = (NodeList)expr2.evaluate(doc, XPathConstants.NODESET); 
     System.out.println("Files in //groups/group"); 
     printFiles(nodes2); 
    } 

    public static void printFiles(NodeList nodes) { 
     for (int i = 0; i < nodes.getLength(); ++i) { 
      Node file = nodes.item(i); 
      System.out.println(file.getAttributes().getNamedItem("location")); 
     } 
    } 
} 

它應該輸出:

Files in //notification 
location="C:\valid\file.txt" 
location="C:\valid\file.xml" 
location="C:\valid\file.doc" 
Files in //groups/group 
location="C:\valid\directory\" 
location="C:\this\file\doesn't\exist.grr" 
location="C:\valid\file\here.txt" 
+0

看起來像一個很好的答案,並且將來我可能會從'DOM'移動到'XPath'。但對於這個項目,這是我需要做的最後一件事,我想堅持'DOM'。然而,除非我得到另一個'DOM'的答案,否則我會接受你的答案,因爲這是一個很好的答案。無論哪種方式,您都可以獲得+1的全面回答。 – kentcdodds

+0

如果您需要堅持使用DOM,那麼您將需要使用'((Node)notificationElement).getChildNodes()'遍歷'NodeList'並只保留名稱爲'file'的那個。理想情況下,你將不得不找到所有的'通知'標籤來做到這一點。 'group'標籤需要完成相同的工作。 – Alex

+0

我找到了更好的解決方案。不工作的原因是因爲'notification'元素中有很多'childNodes'。儘管我回答了這個問題。謝謝你的好回答。我將來會考慮XPath。 – kentcdodds

4

如果你堅持使用DOM API

NodeList nodeList = doc.getElementsByTagName("notification") 
    .item(0).getChildNodes(); 

// get the immediate child (1st generation) 
for (int i = 0; i < nodeList.getLength(); i++) 
    switch (nodeList.item(i).getNodeType()) { 
     case Node.ELEMENT_NODE: 

      Element element = (Element) nodeList.item(i); 
      System.out.println("element name: " + element.getNodeName()); 
      // check the element name 
      if (element.getNodeName().equalsIgnoreCase("file")) 
      { 

       // do something with you "file" element (child first generation) 

       System.out.println("element name: " 
        + element.getNodeName() + " attribute: " 
        + element.getAttribute("location")); 

      } 
    break; 

} 

我們的首要任務是獲得元素「的通知」(在這種情況下,第一-Item(0) - )及其所有子女:

NodeList nodeList = doc.getElementsByTagName("notification") 
    .item(0).getChildNodes(); 

(稍後,您可以使用獲取所有元素來處理所有元素)。

對於「通知」的每一個孩子:

for (int i = 0; i < nodeList.getLength(); i++) 

你第一次爲了看它是否它的類型是一個元素:

switch (nodeList.item(i).getNodeType()) { 
    case Node.ELEMENT_NODE: 
     //....... 
     break; 
} 

如果是的話,那麼你有你兒童「檔案」,這是不是大孩子「通知」

和你可以檢查出來:

if (element.getNodeName().equalsIgnoreCase("file")) 
{ 

    // do something with you "file" element (child first generation) 

    System.out.println("element name:" 
     + element.getNodeName() + " attribute: " 
     + element.getAttribute("location")); 

} 

和ouptut是:

element name: file 
element name:file attribute: C:\valid\file.txt 
element name: file 
element name:file attribute: C:\valid\file.xml 
element name: file 
element name:file attribute: C:\valid\file.doc 
+0

感謝您的解決方案。我的解決方案與此類似,但我不遍歷所有孩子,因爲該元素中有更多的孩子,我沒有在我的問題中顯示,以避免信息超載。無論如何,再次感謝。 +1爲好的答案。 – kentcdodds

+0

@ kentcdodds.I更新我的Answer.You看到,使用XML而不使用「ID」會讓你基本上只用「getElementsByTagName」和「getChildNodes」來玩。在直接使用DOM時,您並沒有其他的答案。您必須堅持使用DOM。無論解決方案如何,您可能會考慮如何訪問給定節點的子節點(在本例中爲「Notification 「)。我的解決方案檢查節點的類型,以免你不必要的工作。但是你仍然必須迭代所有的孩子。當沒有」ID「時會出現這種情況:你最終得到一個集合。 – arthur

+0

@arthur(題外話)對於所有神聖的愛,請在句點和下一句的第一個字母之間加上一些空格。這純粹是瘋狂! – klaar

19

我知道你找到了解決的東西,這在五月@kentcdodds但我有一個相當類似的問題,我現在已經發現了,我覺得(也許在我的用例,但不在你的),一個解決方案。

我的XML格式的一個非常簡單的例子如下所示: -

<?xml version="1.0" encoding="utf-8"?> 
<rels> 
    <relationship num="1"> 
     <relationship num="2"> 
      <relationship num="2.1"/> 
      <relationship num="2.2"/> 
     </relationship> 
    </relationship> 
    <relationship num="1.1"/> 
    <relationship num="1.2"/> 

</rels> 

正如你希望從這個片段中看到,我想要的格式可以嵌套[關係]節點N個層次,所以很顯然,我使用Node.getChildNodes()時遇到的問題是我從層次結構的所有級別獲取所有節點,並且沒有任何關於節點深度的提示。

望着API了一段時間,我發現實際上有兩個其他的方法可能是一些使用的: -

總之,這些兩種方法似乎提供了獲取節點的所有直接後代元素所需的一切。下面的jsp代碼應該給出如何實現這個的基本概念。對不起,JSP。我現在將它轉換成一個bean,但沒有時間從分開的代碼創建完整的工作版本。

<%@page import="javax.xml.parsers.DocumentBuilderFactory, 
       javax.xml.parsers.DocumentBuilder, 
       org.w3c.dom.Document, 
       org.w3c.dom.NodeList, 
       org.w3c.dom.Node, 
       org.w3c.dom.Element, 
       java.io.File" %><% 
try { 

    File fXmlFile = new File(application.getRealPath("/") + "/utils/forms-testbench/dom-test/test.xml"); 
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); 
    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); 
    Document doc = dBuilder.parse(fXmlFile); 
    doc.getDocumentElement().normalize(); 

    Element docEl = doc.getDocumentElement();  
    Node childNode = docEl.getFirstChild();  
    while(childNode.getNextSibling()!=null){   
     childNode = childNode.getNextSibling();   
     if (childNode.getNodeType() == Node.ELEMENT_NODE) {   
      Element childElement = (Element) childNode;    
      out.println("NODE num:-" + childElement.getAttribute("num") + "<br/>\n");   
     }  
    } 

} catch (Exception e) { 
    out.println("ERROR:- " + e.toString() + "<br/>\n"); 
} 

%> 

此代碼將給出以下輸出,僅顯示初始根節點的直接子元素。

NODE num:-1 
NODE num:-1.1 
NODE num:-1.2 

希望這可以幫助別人。爲最初的帖子歡呼。

+0

+1爲這個問題提供了另一個完全可以接受的答案。 :) – kentcdodds

+0

乾杯@kentcdodds相當有趣的問題解決和找到另一種解決方案,實際上。很高興我可以繼續使用org.w3c.dom,而不必移植現有的代碼。謝謝你的問題! – BizNuge

+2

+ +1爲一個非常簡單,簡單和乾淨的解決方案。你可以使用'for'循環來保持它的優雅性並保留範圍:'for(Node n = docEl.getFirstChild(); n!= null; n = n.getNextSibling())'。 – krispy

0

我寫了這個函數來獲取通過標記名節點值,限制到頂級

public static String getValue(Element item, String tagToGet, String parentTagName) { 
    NodeList n = item.getElementsByTagName(tagToGet); 
    Node nodeToGet = null; 
    for (int i = 0; i<n.getLength(); i++) { 
     if (n.item(i).getParentNode().getNodeName().equalsIgnoreCase(parentTagName)) { 
      nodeToGet = n.item(i); 
     } 
    } 
    return getElementValue(nodeToGet); 
} 

public final static String getElementValue(Node elem) { 
    Node child; 
    if (elem != null) { 
     if (elem.hasChildNodes()) { 
      for (child = elem.getFirstChild(); child != null; child = child 
        .getNextSibling()) { 
       if (child.getNodeType() == Node.TEXT_NODE) { 
        return child.getNodeValue(); 
       } 
      } 
     } 
    } 
    return ""; 
} 
0

我遇到了一個相關的問題,我需要處理只是直接子節點,即使所有的「文件處理「節點是相似的。對於我的解決方案,我將元素的父節點與正在處理的節點進行比較,以確定元素是否爲直接子元素。

NodeList fileNodes = parentNode.getElementsByTagName("file"); 
for(int i = 0; i < fileNodes.getLength(); i++){ 
      if(parentNode.equals(fileNodes.item(i).getParentNode())){ 
       if (fileNodes.item(i).getNodeType() == Node.ELEMENT_NODE) { 

        //process the child node... 
       } 
      } 
     } 
0

有一個很好的解決方案LINQ:

For Each child As XmlElement In From cn As XmlNode In xe.ChildNodes Where cn.Name = "file" 
    ... 
Next 
2

我在項目中的一個有同樣的問題,並寫了一個小函數會返回一個List<Element>只包含直接子項。 基本上它檢查是否有getElementsByTagName返回的每個節點,如果它的parentNode其實就是我們正在尋找的孩子的節點:

public static List<Element> getDirectChildsByTag(Element el, String sTagName) { 
     NodeList allChilds = el.getElementsByTagName(sTagName); 
     List<Element> res = new ArrayList<>(); 

     for (int i = 0; i < allChilds.getLength(); i++) { 
      if (allChilds.item(i).getParentNode().equals(el)) 
       res.add((Element) allChilds.item(i)); 
     } 

     return res; 
    } 

通過kentcdodds接受的答案會返回錯誤的結果(如grandchilds)如果有一個叫childnode「通知「 - 例如當元素「組」將具有名稱「通知」時返回孫子。我在我的項目中正面臨着這種設置,這就是爲什麼我想出了我的功能。