web-dev-qa-db-fra.com

Obtenir uniquement les éléments enfants immédiats XML par nom

Ma question est: Comment puis-je obtenir des éléments directement sous un élément parent spécifique lorsqu'il existe d'autres éléments portant le même nom qu'un "petit-enfant" du parent élément.

J'utilise le Java bibliothèque DOM pour analyser XML Elements et je rencontre des problèmes. Voici certains (une petite partie) du xml que j'utilise:

<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>

Comme vous pouvez le voir, il y a deux endroits où vous pouvez placer l'élément <file>. Soit en groupe, soit en groupe extérieur. Je veux vraiment qu'il soit structuré de cette façon parce qu'il est plus convivial.

Maintenant, chaque fois que j'appelle notificationElement.getElementsByTagName("file"); cela me donne tous les éléments <file>, Y compris ceux sous l'élément <group>. Je traite chacun de ces types de fichiers différemment, cette fonctionnalité n'est donc pas souhaitable.

J'ai pensé à deux solutions:

  1. Obtenez l'élément parent de l'élément de fichier et traitez-le en conséquence (selon qu'il s'agit de <notification> Ou <group>.
  2. Renommez le deuxième élément <file> Pour éviter toute confusion.

Aucune de ces solutions n'est aussi souhaitable que de laisser les choses telles quelles et d'obtenir uniquement les éléments <file> Qui sont les enfants directs des éléments <notification>.

Je suis ouvert à [~ # ~] impo [~ # ~] commentaires et réponses sur la "meilleure" façon de le faire, mais je ' Je suis vraiment intéressé par les solutions [~ # ~] dom [~ # ~] parce que c'est ce que le reste de ce projet utilise. Merci.

38
kentcdodds

Eh bien, la solution DOM à cette question est en fait assez simple, même si elle n'est pas trop élégante. Lorsque je parcours le filesNodeList qui est renvoyé lorsque j'appelle notificationElement.getElementsByTagName("file");, je vérifie simplement si le nom du nœud parent est "notification". Si ce n'est pas le cas, je l'ignore car cela sera géré par le <group> élément. Voici ma solution de code:

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

Je me rends compte que vous avez trouvé une solution à cela en mai @kentcdodds mais je viens d'avoir un problème assez similaire que j'ai trouvé, je pense (peut-être dans mon cas d'utilisation, mais pas dans le vôtre), une solution.

un exemple très simpliste de mon format XML est montré ci-dessous: -

<?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>

Comme vous pouvez le voir avec cet extrait, le format que je veux peut avoir N-niveaux d'imbrication pour les nœuds [relation], donc évidemment le problème que j'avais avec Node.getChildNodes () était que j'obtenais tous les nœuds de tous les niveaux de la hiérarchie, et sans aucune sorte d'indice quant à Node depth.

En regardant le API pendant un certain temps, j'ai remarqué qu'il y a en fait deux autres méthodes qui pourraient être utiles: -

Ensemble, ces deux méthodes semblaient offrir tout ce qui était nécessaire pour obtenir tous les éléments descendants immédiats d'un nœud. Le code jsp suivant devrait donner une idée assez basique de la façon de l'implémenter. Désolé pour le JSP. Je suis en train de rouler cela dans un bean maintenant, mais je n'ai pas eu le temps de créer une version entièrement fonctionnelle à partir du code sélectionné.

<%@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");
}

%>

Ce code donnerait la sortie suivante, montrant uniquement les éléments enfants directs du nœud racine initial.

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

J'espère que cela aidera quelqu'un de toute façon. Bravo pour le post initial.

21
BizNuge

Vous pouvez utiliser XPath pour cela, en utilisant deux chemins pour les obtenir et les traiter différemment.

Pour obtenir le <file> les nœuds dirigent les enfants de <notification> utilisation //notification/file et pour ceux de <group> utilisation //groups/group/file.

Voici un exemple simple:

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"));
        }
    }
}

Il devrait produire:

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
Alex

Si vous vous en tenez à l'API DOM

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;

}

Notre première tâche est d'obtenir un élément "Notification" (dans ce cas le premier -item (0) -) et tous ses enfants:

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

(plus tard, vous pouvez travailler avec tous les éléments en utilisant tous les éléments).

Pour chaque enfant de "Notification":

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

vous obtenez d'abord son type afin de voir s'il s'agit d'un élément:

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

Si c'est le cas, alors vous avez obtenu votre "dossier" de vos enfants, qui ne sont pas des "petits-enfants" "Notification"

et vous pouvez les consulter:

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"));

}

et le ouptut est:

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
arthur

J'ai eu le même problème dans l'un de mes projets et j'ai écrit une petite fonction qui retournera un List<Element> ne contenant que les enfants immédiats. Fondamentalement, il vérifie pour chaque nœud renvoyé par getElementsByTagName si son parentNode est en fait le nœud dont nous recherchons les enfants:

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;
    }

La réponse acceptée par kentcdodds renverra des résultats erronés (par exemple petits-enfants) s'il y a un nœud enfant appelé "notification" - par exemple renvoyer les petits-enfants lorsque l'élément "groupe" aura le nom "notification". J'étais confronté à cette configuration dans mon projet, c'est pourquoi j'ai créé ma fonction.

3
Andy

Il existe une solution Nice LINQ:

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

J'ai rencontré un problème connexe où je devais traiter uniquement les nœuds enfants immédiats même si le traitement de tous les nœuds "fichier" est similaire. Pour ma solution, je compare le nœud parent de l'élément avec le nœud en cours de traitement afin de déterminer si l'élément est un enfant immédiat.

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
KalenGi

J'ai écrit cette fonction pour obtenir la valeur du nœud par tagName, restreindre au niveau supérieur

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
Danimate