按名称仅获取XML即时子元素

My question is: How can I get elements directly under a specific parent element when there are other elements with the same name as a "grandchild" of the parent element.

我正在使用Java DOM库解析XML 元素,我正在运行麻烦。这是一些(一小部分)我正在使用的xml:


  
    
      
        
        
        
      
    
    
    
    
  

As you can see, there are two places you can place the element. Either in groups or outside groups. I really want it structured this way because it's more user-friendly.

Now, whenever I call notificationElement.getElementsByTagName("file"); it gives me all the elements, including those under the element. I handle each of these kinds of files differently, so this functionality is not desirable.

我想到了两个解决方案:

  1. Get the parent element of the file element and deal with it accordingly (depending on whether it's or .
  2. Rename the second element to avoid confusion.

Neither of those solutions are as desirable as just leaving things the way they are and getting only the elements which are direct children of elements.

我对 IMPO 评论和有关“最佳”方式的答案持开放态度,但我对 DOM 解决方案非常感兴趣,因为这是该项目的其余部分正在使用。谢谢。

33
额外 编辑
意见: 1
我应该提到,这只是一个更大的XML文件的一小部分:)想要使它可读。
额外 作者 kentcdodds,
@Alex org.w3c.dom不支持XPath;他想要使用不同的库,比如org.jdom.xpath,尽管我完全同意这是更优雅的方法。
额外 作者 Charles Duffy,
为什么不使用XPath来获取两个节点的列表并对它们进行不同处理? //groups/group/file//notification/file 就足以拥有它们。或者你只需​​要一个XPath就可以获得它们全部?
额外 作者 Alex,
javax.xml.xpath 是Java Standard,所以我认为他可以使用它,不需要为了这个简单的任务而获得JDom。
额外 作者 Alex,
为什么不通过自己循环直接子对象创建这个集合,比如命中:“NodeList nodes = element.getChildNodes(); for(int i = 0; i
额外 作者 Dmitry,

8 答案

我知道你在5月@kentcdodds中发现了一些解决方案,但我刚刚遇到了一个相似的问题,我现在已经发现,我想(可能在我的用例中,但不在你的方法中)解决方案。

下面显示了一个非常简单的XML格式示例:

<?xml version="1.0" encoding="utf-8"?>

    
        
            
            
        
    
    
    


正如你可以从这段代码中看到的那样,我想要的格式可以为[关系]节点设置N层嵌套,显然,我使用Node.getChildNodes()时遇到的问题是我从所有层次的层次结构,并且没有任何关于节点深度的提示。

查看 API a> 一段时间后,我注意到实际上有两种可能有用的方法: -

总之,这两种方法似乎提供了获得节点的所有即时后代元素所需的一切。下面的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") + "
\n" ); } } } catch (Exception e) { out.println("ERROR:- " + e.toString() + "
\n"); } %>

此代码将提供以下输出,仅显示初始根节点的直接子元素。

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

无论如何希望这可以帮助别人。为最初的帖子欢呼。

19
额外
+1为一个非常简单,简单和干净的解决方案。您可以使用此技术的 for 循环来保持其优雅并保留范围: for(Node n = docEl.getFirstChild(); n!= null; n = n.getNextSibling ())</代码>。
额外 作者 krispy,
+1提供另一个完全可以接受的答案。 :)
额外 作者 kentcdodds,
欢呼@kentcdodds相当有趣的问题解决和实际找到另一种解决方案。很高兴我可以继续使用org.w3c.dom,而不必移植现有的代码。谢谢你的问题!
额外 作者 BizNuge,

您可以使用XPath来使用它,使用两条路径来获取它们并以不同的方式处理它们。

To get the nodes direct children of use //notification/file and for the ones in use //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("\n" + 
                "  \n" + 
                "    \n" + 
                "      \n" + 
                "        \n" + 
                "        \n" + 
                "        \n" + 
                "      \n" + 
                "    \n" + 
                "    \n" + 
                "    \n" + 
                "    \n" + 
                "  \n" + 
                "")));
        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"
13
额外
我正在寻找一种方法来通过路径 root/etc/foo 搜索元素,并最终创建它,或者如果这些父节点不存在,它就是父节点。我可以在儿童节点中使用比for循环更好的东西吗?我只关心第一次发生。
额外 作者 Tomáš Zato,
看起来是一个很好的答案,将来我可能会从 DOM 移动到 XPath 。但是对于这个项目来说,这是我需要做的最后一件事,我想坚持 DOM 。但是,除非我为 DOM 获得另一个答案,否则我会接受你的答案,因为这是一个很好的答案。无论哪种方式,您都可以获得+1的全面回答。
额外 作者 kentcdodds,
我找到了更好的解决方案。不工作的原因是因为 notification 元素中有很多 childNodes 。尽管我回答了这个问题。谢谢你的好回答。我将来会考虑XPath。
额外 作者 kentcdodds,
如果您需要坚持使用DOM,那么您将需要使用((Node)notificationElement).getChildNodes()遍历 NodeList ,并仅保留名称为<�代码>文件</代码>。理想情况下,您必须找到所有 notification 标签才能做到这一点。 group 标签需要完成相同的工作。
额外 作者 Alex,

Well, the DOM solution to this question is actually pretty simple, even if it's not too elegant When I iterate through the filesNodeList which is returned when I call notificationElement.getElementsByTagName("file"); I just check whether the parent node's name is "notification." If it isn't then I ignore it because that will be handled by the element. Here's my code solution:

for (int j = 0; j < filesNodeList.getLength(); j++) {
  Element fileElement = (Element) filesNodeList.item(j);
  if (!fileElement.getParentNode().getNodeName().equals("notification")) {
    continue;
  }
  ...
}
12
额外
布拉沃!!!!!!!!!!
额外 作者 madhairsilence,
@JanusTroelsen,如果您在将该项目作为元素进行投射时正在讨论第二行,那么它取决于您正在解析的DOM ...如果不是,那么您的意思是什么?
额外 作者 kentcdodds,
演员是否安全?
额外 作者 Janus Troelsen,
是的,这就是我的意思
额外 作者 Janus Troelsen,
'getParentNode'函数(和'getNodeName')在'Node'接口上可用。因此,只需检查名称,不需要投射。 (只是为了安全切换等于“通知”.equals(...))
额外 作者 Justin,
你为什么不通过element.getChildNodes()来迭代?
额外 作者 FINDarkside,

如果你坚持使用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;

}

我们的第一个任务是获得一个元素“通知”(在这种情况下是第一项 - (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
4
额外
感谢您的解决方案。我的解决方案与此类似,但我不遍历所有孩子,因为该元素中有更多的孩子,我没有在我的问题中显示,以避免信息超载。无论如何,再次感谢。 +1为好的答案。
额外 作者 kentcdodds,
@ kentcdodds.I更新我的Answer.You看到,使用XML而不使用“ID”会让你基本上只使用“getElementsByTagName”和“getChildNodes”来玩。在直接使用DOM时,您并没有其他的答案。您必须坚持使用DOM。无论解决方案如何,您可能会考虑如何访问给定节点的子节点(在本例中为“Notification “)。我的解决方案检查节点的类型,以免你不必要的工作。但是你仍然必须迭代所有的孩子。当没有”ID“时会出现这种情况:你最终得到一个集合。
额外 作者 arthur,
@arthur(off-topic)对于所有神圣的爱,请在一段时间和下一句的第一个字母之间加一些空格。这纯粹是疯狂!
额外 作者 klaar,

I had the same problem in one of my projects and wrote a little function which will return a List containing only the immediate children. Basically it checks for each node returned by getElementsByTagName if it's parentNode is actually the node we are searching childs of:

public static List getDirectChildsByTag(Element el, String sTagName) {
        NodeList allChilds = el.getElementsByTagName(sTagName);
        List 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接受的答案将返回错误的结果(例如,孙子)。当元素“组”将具有名称“通知”时返回孙子。我在我的项目中正面临着这种设置,这就是为什么我想出了我的功能。

2
额外

我编写了这个函数,通过tagName获取节点值,限制在顶层

public static String getValue(Element item, String tagToGet, String parentTagName) {
    NodeList n = item.getElementsByTagName(tagToGet);
    Node nodeToGet = null;
    for (int i = 0; i
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
0
额外