XML作为常用的数据交换格式,经常会通过嵌套列表节点存储结构化的多条数据,比如存储商品列表、用户信息列表等场景,提取这类嵌套列表节点是XML解析过程中的常见需求。

XML嵌套列表节点的结构特点
XML的嵌套列表节点通常表现为同一个父节点下存在多个同名的子节点,子节点内部可能还会包含更深层的嵌套结构。下面是一个典型的嵌套列表节点示例:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<user_list>
<user>
<id>1</id>
<name>张三</name>
<hobby_list>
<hobby>篮球</hobby>
<hobby>阅读</hobby>
</hobby_list>
</user>
<user>
<id>2</id>
<name>李四</name>
<hobby_list>
<hobby>跑步</hobby>
<hobby>绘画</hobby>
</hobby_list>
</user>
</user_list>
</root>
上面的示例中,user_list是列表父节点,下面包含两个user子节点,每个user节点下的hobby_list又是嵌套的列表节点,包含多个hobby子节点。
使用XPath提取嵌套列表节点
XPath是XML路径语言,能够快速定位XML中的节点,是提取嵌套列表节点最高效的方式之一。常用的XPath表达式规则如下:
//节点名:匹配所有同名节点,不管位置父节点/子节点:匹配父节点下的直接子节点节点[@属性名=值]:匹配带指定属性的节点
Python中使用XPath提取示例
Python可以借助lxml库实现XPath解析,首先需要安装依赖:
# 安装lxml库 pip install lxml
提取上面示例XML中所有的user节点和嵌套的hobby节点的代码如下:
from lxml import etree
# 加载XML数据
xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
<root>
<user_list>
<user>
<id>1</id>
<name>张三</name>
<hobby_list>
<hobby>篮球</hobby>
<hobby>阅读</hobby>
</hobby_list>
</user>
<user>
<id>2</id>
<name>李四</name>
<hobby_list>
<hobby>跑步</hobby>
<hobby>绘画</hobby>
</hobby_list>
</user>
</user_list>
</root>'''
# 解析XML
tree = etree.XML(xml_data.encode('utf-8'))
# 提取所有user节点
user_nodes = tree.xpath('//user')
print(f'共找到{len(user_nodes)}个用户节点')
for user in user_nodes:
# 提取用户名称
name = user.xpath('name/text()')[0]
# 提取当前用户下的所有hobby节点
hobby_nodes = user.xpath('hobby_list/hobby/text()')
print(f'用户{name}的爱好:{hobby_nodes}')
Java中使用XPath提取示例
Java内置了XPath解析支持,不需要额外引入第三方库,实现代码如下:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class XmlParseDemo {
public static void main(String[] args) throws Exception {
String xmlData = "<?xml version="1.0" encoding="UTF-8"?>" +
"<root>" +
"<user_list>" +
"<user>" +
"<id>1</id>" +
"<name>张三</name>" +
"<hobby_list>" +
"<hobby>篮球</hobby>" +
"<hobby>阅读</hobby>" +
"</hobby_list>" +
"</user>" +
"<user>" +
"<id>2</id>" +
"<name>李四</name>" +
"<hobby_list>" +
"<hobby>跑步</hobby>" +
"<hobby>绘画</hobby>" +
"</hobby_list>" +
"</user>" +
"</user_list>" +
"</root>";
// 解析XML文档
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new java.io.ByteArrayInputStream(xmlData.getBytes("UTF-8")));
// 创建XPath对象
XPath xPath = XPathFactory.newInstance().newXPath();
// 提取所有user节点
NodeList userNodes = (NodeList) xPath.evaluate("//user", document, XPathConstants.NODESET);
System.out.println("共找到" + userNodes.getLength() + "个用户节点");
for (int i = 0; i < userNodes.getLength(); i++) {
org.w3c.dom.Node userNode = userNodes.item(i);
// 提取用户名称
String name = (String) xPath.evaluate("name/text()", userNode, XPathConstants.STRING);
// 提取当前用户下的所有hobby节点
NodeList hobbyNodes = (NodeList) xPath.evaluate("hobby_list/hobby/text()", userNode, XPathConstants.NODESET);
StringBuilder hobbies = new StringBuilder();
for (int j = 0; j < hobbyNodes.getLength(); j++) {
hobbies.append(hobbyNodes.item(j).getNodeValue()).append(" ");
}
System.out.println("用户" + name + "的爱好:" + hobbies.toString().trim());
}
}
}
使用DOM解析提取嵌套列表节点
如果不想使用XPath,也可以通过DOM解析的方式逐层遍历节点提取嵌套列表。DOM解析会把整个XML加载为树形结构,通过节点关系逐层获取子节点。
Python DOM解析示例
Python内置的xml.dom.minidom模块可以实现DOM解析,代码如下:
from xml.dom import minidom
# 加载XML数据
xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
<root>
<user_list>
<user>
<id>1</id>
<name>张三</name>
<hobby_list>
<hobby>篮球</hobby>
<hobby>阅读</hobby>
</hobby_list>
</user>
<user>
<id>2</id>
<name>李四</name>
<hobby_list>
<hobby>跑步</hobby>
<hobby>绘画</hobby>
</hobby_list>
</user>
</user_list>
</root>'''
# 解析XML
doc = minidom.parseString(xml_data)
# 获取所有user节点
user_nodes = doc.getElementsByTagName('user')
print(f'共找到{len(user_nodes)}个用户节点')
for user in user_nodes:
# 提取name节点内容
name_node = user.getElementsByTagName('name')[0]
name = name_node.firstChild.data
# 提取hobby_list下的所有hobby节点
hobby_list_node = user.getElementsByTagName('hobby_list')[0]
hobby_nodes = hobby_list_node.getElementsByTagName('hobby')
hobbies = [hobby.firstChild.data for hobby in hobby_nodes]
print(f'用户{name}的爱好:{hobbies}')
提取注意事项
在提取XML嵌套列表节点时,需要注意以下几点:
- 确认XML的编码格式,解析时指定正确的编码,避免出现乱码问题
- 提取节点内容前先判断节点是否存在,避免空指针异常
- 如果嵌套层级较深,XPath的匹配路径要写准确,避免匹配到无关节点
- 处理大体积XML时,DOM解析会占用较多内存,优先使用XPath或者流式解析方式