XML文档采用树形结构存储数据,每个元素、属性、文本都属于树的节点,遍历所有节点就是按照一定顺序访问树中的每个节点,获取其名称、值、属性等信息。在实际开发中,根据XML文档的大小和解析需求,可以选择不同的遍历方式。
XML节点结构基础
XML文档的节点类型主要分为元素节点、属性节点、文本节点、注释节点等,其中元素节点是构成文档结构的核心,属性节点依附于元素节点存在,文本节点是元素节点包含的内容。基于DOM解析模型时,整个XML文档会被加载为一棵节点树,根节点是文档节点,其下包含所有子节点。
基于DOM的递归遍历方法
递归遍历是最直观的遍历所有节点的方法,核心思路是先处理当前节点,再递归处理当前节点的所有子节点,直到没有子节点为止。这种方式适合XML文档结构不复杂、文档大小适中的场景。
Python实现示例
使用Python内置的xml.dom.minidom库可以实现DOM解析和递归遍历,代码如下:
import xml.dom.minidom
def traverse_node(node, depth=0):
# 打印当前节点的信息,depth表示节点深度,用于缩进显示
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
print(' ' * depth + f"元素节点: {node.nodeName}")
# 遍历元素的属性
if node.hasAttributes():
for i in range(node.attributes.length):
attr = node.attributes.item(i)
print(' ' * (depth + 1) + f"属性: {attr.name} = {attr.value}")
elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
# 去除空白文本节点
text = node.data.strip()
if text:
print(' ' * depth + f"文本节点: {text}")
elif node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
print(' ' * depth + f"注释节点: {node.data}")
# 递归遍历所有子节点
if node.hasChildNodes():
for child in node.childNodes:
traverse_node(child, depth + 1)
# 示例XML内容
xml_content = """
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="编程">
<title lang="zh">Python入门教程</title>
<author>张三</author>
<price>59.9</price>
</book>
<book category="小说">
<title lang="zh">平凡的世界</title>
<author>路遥</author>
<price>39.8</price>
</book>
</bookstore>
"""
# 解析XML
dom = xml.dom.minidom.parseString(xml_content)
# 从根节点开始遍历
traverse_node(dom.documentElement)
Java实现示例
Java中使用javax.xml.parsers包的DOM解析器也可以实现递归遍历,代码如下:
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class XmlTraverseDemo {
public static void traverseNode(Node node, int depth) {
// 处理元素节点
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(" ".repeat(depth) + "元素节点: " + node.getNodeName());
// 遍历元素属性
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
System.out.println(" ".repeat(depth + 1) + "属性: " + attr.getNodeName() + " = " + attr.getNodeValue());
}
}
} else if (node.getNodeType() == Node.TEXT_NODE) {
String text = node.getTextContent().trim();
if (!text.isEmpty()) {
System.out.println(" ".repeat(depth) + "文本节点: " + text);
}
} else if (node.getNodeType() == Node.COMMENT_NODE) {
System.out.println(" ".repeat(depth) + "注释节点: " + node.getTextContent());
}
// 递归处理子节点
NodeList childNodes = node.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
traverseNode(childNodes.item(i), depth + 1);
}
}
public static void main(String[] args) throws Exception {
String xmlContent = "<?xml version="1.0" encoding="UTF-8"?>n" +
"<bookstore>n" +
" <book category="编程">n" +
" <title lang="zh">Java核心技术</title>n" +
" <author>李四</author>n" +
" <price>89.5</price>n" +
" </book>n" +
"</bookstore>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new ByteArrayInputStream(xmlContent.getBytes()));
traverseNode(document.getDocumentElement(), 0);
}
}
基于迭代的遍历方法
递归遍历在XML文档层级很深时可能会出现栈溢出的问题,此时可以使用迭代的方式,借助栈或队列来存储待处理的节点,实现深度优先或广度优先遍历。
Python迭代遍历示例
使用栈实现深度优先的迭代遍历,代码如下:
import xml.dom.minidom
def traverse_node_iterative(root):
# 栈中存储节点和对应的深度
stack = [(root, 0)]
while stack:
node, depth = stack.pop()
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
print(' ' * depth + f"元素节点: {node.nodeName}")
if node.hasAttributes():
for i in range(node.attributes.length):
attr = node.attributes.item(i)
print(' ' * (depth + 1) + f"属性: {attr.name} = {attr.value}")
elif node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
text = node.data.strip()
if text:
print(' ' * depth + f"文本节点: {text}")
# 将子节点逆序入栈,保证遍历顺序和递归一致
if node.hasChildNodes():
children = node.childNodes
for i in range(len(children) - 1, -1, -1):
stack.append((children[i], depth + 1))
# 复用之前的xml_content
dom = xml.dom.minidom.parseString(xml_content)
traverse_node_iterative(dom.documentElement)
遍历注意事项
- 空白文本节点处理:XML中的换行、缩进会被解析为文本节点,遍历时通常需要过滤掉只包含空白字符的文本节点,避免干扰结果。
- 文档大小适配:如果XML文档非常大,不建议使用DOM模型一次性加载到内存,可以选择SAX等流式解析方式,边解析边处理节点,减少内存占用。
- 节点类型判断:遍历时需要根据节点类型做不同的处理,避免遗漏属性、注释等类型的节点。