Мой вопрос: Как я могу получить элементы непосредственно под определенным родительским элементом, когда есть другие элементы с тем же именем, что и "внук" родительского элемента.
Я использую 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, потому что это то, что использует весь этот проект. Спасибо.
Хорошо, решение 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;
}
...
}
Я понимаю, что вы нашли что-то в этом решении в мае @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
Надеюсь, это кому-то поможет. Приветствия за начальную должность.
Вы можете использовать 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"
DOM
на XPath
. Но для этого проекта это последнее, что мне нужно сделать, и я хочу придерживаться DOM
. Однако, если я не получу другой ответ для DOM
, я приму ваш, потому что это хороший ответ. В любом случае, вы получите +1 за такой исчерпывающий ответ.
NodeList
с помощью ((Node)notificationElement).getChildNodes()
и оставить только тот, чьи имена являются file
. В идеале для этого вам нужно найти все теги notification
. То же самое необходимо сделать для group
тегов.
Если вы используете 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
У меня была такая же проблема в одном из моих проектов, и я написал небольшую функцию, которая вернет 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 вернет неверные результаты (например, внуки), если есть дочерний узел, называемый "уведомлением" - например. возвращая внуков, когда элемент "группа" будет иметь имя "уведомление". Я столкнулся с этой настройкой в своем проекте, поэтому я придумал свою функцию.
Существует отличное решение LINQ:
For Each child As XmlElement In From cn As XmlNode In xe.ChildNodes Where cn.Name = "file"
...
Next
Я столкнулся с связанной проблемой, когда мне нужно было обрабатывать только непосредственные дочерние узлы, даже если обработка всех "файловых" узлов аналогична. Для моего решения я сравниваю родительский элемент 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...
}
}
}
Я написал эту функцию, чтобы получить значение 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 "";
}
//groups/group/file
и//notification/file
будет достаточно, чтобы иметь их. Или вы хотите, чтобы только один XPath получил их все?