45

私の質問は、親要素の「孫」と同じ名前の他の要素がある場合、特定の親要素のすぐ下に要素を取得するにはどうすればよいですか。

Java DOMライブラリを使用してXML要素を解析していますが、問題が発生しています。これが私が使用している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. <file>混乱を避けるために、2番目の要素の名前を変更します。

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

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

4

9 に答える 9

23

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()で発生した問題は、すべてのレベルのすべてのノードを取得していたことです。階層であり、ノードの深さに関するヒントはありません。

APIをしばらく見てみると、実際には他に2つの方法があることに気づきました。

一緒に、これらの2つの方法は、ノードの直系の子孫要素をすべて取得するために必要なすべてを提供するように見えました。次の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

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

于 2012-06-20T13:28:12.110 に答える
15

これには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"
于 2012-05-21T17:58:27.557 に答える
14

まあ、この質問に対する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;
  }
  ...
}
于 2012-05-21T18:22:34.990 に答える
5

DOMAPIを使い続ける場合

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

}

そして、出力は次のとおりです。

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
于 2012-05-21T18:24:27.700 に答える
4

私のプロジェクトの1つで同じ問題が発生し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によって受け入れられた回答は、「notification」という子ノードがある場合、間違った結果(たとえば、孫)を返します。たとえば、要素「group」の名前が「notification」の場合に孫を返します。私は自分のプロジェクトでその設定に直面していたので、自分の機能を思いついたのです。

于 2016-10-25T19:38:05.303 に答える
0

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 "";
}
于 2013-08-06T21:03:12.677 に答える
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...
                }
            }
        }
于 2013-11-26T04:33:28.617 に答える
0

優れたLINQソリューションがあります。

For Each child As XmlElement In From cn As XmlNode In xe.ChildNodes Where cn.Name = "file"
    ...
Next
于 2015-11-27T12:19:19.863 に答える
0

これを行うために、Kotlinで拡張関数を作成することになりました

fun Element.childrenWithTagName(name: String): List<Node> = childNodes
    .asList()
    .filter { it.nodeName == name }

発信者は次のように使用できます。

val meta = target.newChildElement("meta-coverage")
source.childrenWithTagName("counter").forEach {
    meta.copyElementWithAttributes(it)
}

リストとして実装:


fun NodeList.asList(): List<Node> = InternalNodeList(this)

private class InternalNodeList(
    private val list: NodeList,
    override val size: Int = list.length
) : RandomAccess, AbstractList<Node>() {
    override fun get(index: Int): Node = list.item(index)
}

于 2021-04-28T19:39:21.587 に答える