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

31

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

Я использую 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, потому что это то, что использует весь этот проект. Спасибо.

  • 0
    Почему бы вам не использовать XPath, чтобы получить оба списка узлов и относиться к ним по-разному? //groups/group/file и //notification/file будет достаточно, чтобы иметь их. Или вы хотите, чтобы только один XPath получил их все?
  • 0
    Почему бы не создать эту коллекцию своими собственными циклами с помощью прямых дочерних элементов, таких как хиты: "NodeList node = element.getChildNodes (); for (int i = 0; i <node.getLength (); i ++) {// if проверка пути элемента - добавить его в коллекцию} "?
Показать ещё 3 комментария
Теги:
dom
xml-parsing
parsing

8 ответов

12
Лучший ответ

Хорошо, решение 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;
  }
  ...
}
  • 1
    Браво!!!!!!!!!!
  • 0
    литье безопасно?
Показать ещё 4 комментария
18

Я понимаю, что вы нашли что-то в этом решении в мае @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

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

  • 0
    +1 за другой вполне приемлемый ответ на вопрос. :)
  • 0
    Cheers @kentcdodds Довольно интересная проблема для решения и поиска другого решения на самом деле. очень рад, что я могу продолжать использовать org.w3c.dom без необходимости переносить существующий код. Спасибо за вопрос!
Показать ещё 1 комментарий
13

Вы можете использовать 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"
  • 0
    Похоже, хороший ответ, и в будущем я могу перейти с DOM на XPath . Но для этого проекта это последнее, что мне нужно сделать, и я хочу придерживаться DOM . Однако, если я не получу другой ответ для DOM , я приму ваш, потому что это хороший ответ. В любом случае, вы получите +1 за такой исчерпывающий ответ.
  • 0
    Если вам нужно придерживаться DOM, то вам нужно будет NodeList с помощью ((Node)notificationElement).getChildNodes() и оставить только тот, чьи имена являются file . В идеале для этого вам нужно найти все теги notification . То же самое необходимо сделать для group тегов.
Показать ещё 2 комментария
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
  • 0
    спасибо за решение. Мое решение похоже на это, но я не перебираю все дочерние элементы, потому что в этом элементе гораздо больше дочерних элементов, которые я не отображал в своем вопросе, просто чтобы избежать информационной перегрузки. Во всяком случае, еще раз спасибо. +1 за хороший ответ.
  • 0
    @kentcdodds.Ikentcdodds.I обновить мой ответ. Вы видите, что работа с XML без использования «ID» оставляет вас в основном только с «getElementsByTagName» и «getChildNodes» для игры. По моему мнению, у вас нет других ответов при работе напрямую с DOM. Извините, что вам нужно придерживаться DOM. Каким бы ни было решение, оно, вероятно, сводится к тому, как вы получаете доступ к дочерним узлам (в данном случае «Уведомление»). "). Мое решение проверяет тип Node, чтобы избавить вас от лишней работы. Но вам все равно придется перебирать ВСЕХ детей. Вот что происходит, когда нет" ID ": вы получаете коллекцию.
Показать ещё 1 комментарий
2

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

0

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню