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