web-dev-qa-db-ja.com

名前でXMLのみの直接の子要素を取得する

私の質問は次のとおりです:親の「孫」と同じ名前の他の要素がある場合、特定の親要素の下に直接要素を取得する方法素子。

Java DOMライブラリー を使用してXMLを解析しています Elements で、問題が発生しています。ここにsome(ごく一部)使用している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>_要素を配置できる場所は2つあります。グループまたは外部グループのいずれか。使いやすいので、このように構造化してほしいです。

これで、notificationElement.getElementsByTagName("file");を呼び出すたびに、_<file>_要素の下の要素を含め、すべての_<group>_要素が提供されます。これらの種類のファイルはそれぞれ異なる方法で処理するため、この機能は望ましくありません。

私は2つの解決策を考えました:

  1. ファイル要素の親要素を取得し、それに応じて処理します(_<notification>_か_<group>_かによって異なります)。
  2. 混乱を避けるために、2番目の_<file>_要素の名前を変更します。

これらのソリューションはどちらも、物事をそのままにして、_<file>_要素の直接の子である_<notification>_要素のみを取得するほど望ましいものではありません。

私は[〜#〜] impo [〜#〜]コメントとこれを行うための「最良の」方法についての回答を受け入れていますが、私はm[〜#〜] dom [〜#〜]ソリューションに本当に興味があります。これがこのプロジェクトの残りの部分で使用されているからです。ありがとう。

38
kentcdodds

さて、この質問に対するDOMソリューションは実際には非常にシンプルです。エレガントではありませんが、notificationElement.getElementsByTagName("file");を呼び出したときに返されるfilesNodeListを反復処理するとき、名前は「通知」です。そうでない場合は、<group>要素によって処理されるため、無視します。コードソリューションは次のとおりです。

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

5月の@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()で発生した問題は、すべてのレベルのすべてのノードを取得することでした階層、およびNode depth。

[〜#〜] api [〜#〜]をしばらく見て、実際には他に2つあることに気づきました何らかの用途があるかもしれないメソッド:-

一緒に、これらの2つのメソッドは、Nodeのすべての直接の子孫要素を取得するために必要なすべてを提供するように思われました。次の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

とにかくこれが誰かを助けることを願っています。最初の投稿に乾杯。

21
BizNuge

これにはXPathを使用し、2つのパスを使用してそれらを取得し、異なる方法で処理できます。

<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"
13
Alex

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;

}

最初のタスクは、要素 "Notification"(この場合は最初の-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;  
}

もしそうなら、あなたはあなたの子供 "file"を手に入れた、それは孫の "Notification"ではない

そして、あなたはそれらをチェックアウトすることができます:

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

}

そして、その出力は:

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

私のプロジェクトの1つで同じ問題があり、直接の子のみを含むList<Element>を返す小さな関数を作成しました。基本的に、parentNodeが実際に子を検索しているノードである場合、getElementsByTagNameによって返される各ノードをチェックします。

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

「notification」と呼ばれる子ノードがある場合、kentcdoddsが受け入れた答えは間違った結果(例えば孫)を返します。要素「グループ」の名前が「通知」の場合、孫を返します。私は自分のプロジェクトでそのセットアップに直面していました。それが私が自分の機能を思いついた理由です。

3
Andy

Nice LINQソリューションがあります:

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

すべての「ファイル」ノードの処理が類似していても、直接の子ノードだけを処理する必要があるという関連問題が発生しました。私のソリューションでは、Elementの親ノードを処理中のノードと比較して、Elementが直接の子かどうかを判断します。

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

TagNameでノード値を取得し、トップレベルに制限するために、この関数を作成しました

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