Подтвердить что ты не робот

Получить только XML-элементы с непосредственными дочерними элементами по имени

Мой вопрос: Как я могу получить элементы непосредственно под определенным родительским элементом, когда есть другие элементы с тем же именем, что и "внук" родительского элемента.

Я использую 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>. Либо в группах, либо за пределами групп. Я действительно хочу, чтобы он был структурирован таким образом, потому что он более удобен для пользователя.

Теперь, когда я вызываю notificationElement.getElementsByTagName("file");, он дает мне все элементы <file>, в том числе элементы под элементом <group>. Я обрабатываю каждый из этих файлов по-разному, поэтому эта функциональность нежелательна.

Я подумал о двух решениях:

  • Получите родительский элемент элемента файла и обработайте его соответствующим образом (в зависимости от того, является ли он <notification> или <group>.
  • Переименуйте второй элемент <file>, чтобы избежать путаницы.

Ни одно из этих решений не так желательно, как просто оставляя вещи так, как они есть, и получает только те элементы <file>, которые являются прямыми дочерними элементами <notification>.

Я открыт для IMPO комментариев и ответов о "наилучшем" способе сделать это, но меня действительно интересуют решения DOM, потому что это то, что использует весь этот проект. Спасибо.

4b9b3361

Ответ 1

Хорошо, решение DOM для этого вопроса на самом деле довольно просто, даже если оно не слишком элегантно. Когда я повторяю через filesNodeList, который возвращается, когда я вызываю notificationElement.getElementsByTagName("file");, я просто проверяю, является ли родительское node имя "уведомление". Если это не так, я игнорирую его, потому что это будет обрабатываться элементом <group>. Здесь мое решение для кода:

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

Ответ 2

Я понимаю, что вы нашли что-то в этом решении в мае @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-уровни вложенности для узлов [relationship], поэтому, очевидно, проблема, с которой я столкнулась с Node.getChildNodes(), состояла в том, что я получал все узлы со всех уровней иерархии и без какого-либо намека на глубину Node.

Посмотрев на API, я заметил, что на самом деле есть два других метода, которые могут быть использования: -

Вместе эти два метода, казалось, предлагали все, что требовалось для получения всех непосредственных элементов-потомков 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.

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

Надеюсь, это кому-то поможет. Приветствия за начальную должность.

Ответ 3

Вы можете использовать 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"

Ответ 4

Если вы используете 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;

}

Наша первая задача - получить элемент "Уведомление" (в этом случае первый-элемент (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

Ответ 5

У меня была такая же проблема в одном из моих проектов, и я написал небольшую функцию, которая вернет List<Element>, содержащую только непосредственных детей. В основном он проверяет каждый node, возвращаемый getElementsByTagName, если parentNode на самом деле является node, мы ищем дочерние элементы:

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 вернет неверные результаты (например, внуки), если есть дочерний узел, называемый "уведомлением" - например. возвращая внуков, когда элемент "группа" будет иметь имя "уведомление". Я столкнулся с этой настройкой в ​​своем проекте, поэтому я придумал свою функцию.

Ответ 6

Я написал эту функцию, чтобы получить значение node по имени 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 "";
}

Ответ 7

Я столкнулся с связанной проблемой, когда мне нужно было обрабатывать только непосредственные дочерние узлы, даже если обработка всех "файловых" узлов аналогична. Для моего решения я сравниваю родительский элемент node с node, который обрабатывается, чтобы определить, является ли элемент непосредственным дочерним.

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

Ответ 8

Существует отличное решение LINQ:

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