我的问题是:当存在与父元素的“孙子”同名的其他元素时,如何直接在特定父元素下获取元素。
我正在使用Java DOM library来解析XML Elements而我遇到了麻烦。这是我正在使用的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 :(得分:21)
我意识到你在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 一段时间后,我注意到其他两种方法实际上可能会有用: -
这两种方法似乎提供了获取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 num:-1
NODE num:-1.1
NODE num:-1.2
希望这对任何人都有帮助。欢呼为最初的职位。
答案 1 :(得分:13)
您可以使用XPath,使用两个路径获取它们并以不同方式处理它们。
要让<file>
个节点指向使用<notification>
的{{1}}的孩子,使用//notification/file
的{{1}}使用<group>
。{/ p>
这是一个简单的示例:
//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"));
}
}
}
答案 2 :(得分:12)
嗯,这个问题的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;
}
...
}
答案 3 :(得分:4)
如果您坚持使用DOM API
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;
}
我们的第一个任务是获得一个元素“Notification”(在本例中是第一个-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"));
}
并且ouptut是:
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
答案 4 :(得分:3)
我在其中一个项目中遇到了同样的问题并编写了一个函数,它将返回仅包含直接子项的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;
}
如果有一个叫做#34;通知&#34;的子节点,kentcdodds接受的答案将返回错误的结果(例如孙子)。 - 例如当元素&#34; group&#34;返回孙子时将有#34;通知&#34;的名称。我在项目中遇到了这个设置,这就是为什么我想出了我的功能。
答案 5 :(得分: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 "";
}
答案 6 :(得分:0)
我遇到了一个相关问题,我需要处理直接子节点,即使所有“文件”节点的处理方式相似。对于我的解决方案,我将Element的父节点与正在处理的节点进行比较,以确定Element是否是直接子节点。
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...
}
}
}
答案 7 :(得分:0)
有一个很好的LINQ解决方案:
For Each child As XmlElement In From cn As XmlNode In xe.ChildNodes Where cn.Name = "file"
...
Next
答案 8 :(得分: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)
}