杰瑞科技汇

Java如何用XPath解析XML?

XPath 是一门在 XML 文档中查找信息的语言,它通过路径表达式来选取 XML 文档中的节点或节点集,Java 标准库 javax.xml.xpath 提供了完整的 XPath 支持。

Java如何用XPath解析XML?-图1
(图片来源网络,侵删)

下面我将从基础到高级,通过完整的示例代码,为你演示如何使用 XPath。

准备工作:XML 示例文件

我们创建一个示例 XML 文件 students.xml,后续的所有操作都将基于这个文件。

students.xml

<?xml version="1.0" encoding="UTF-8"?>
<class>
    <student id="001">
        <name>Alice</name>
        <age>20</age>
        <gender>Female</gender>
        <courses>
            <course name="Math" score="95"/>
            <course name="Physics" score="88"/>
        </courses>
    </student>
    <student id="002">
        <name>Bob</name>
        <age>22</age>
        <gender>Male</gender>
        <courses>
            <course name="History" score="92"/>
            <course name="Literature" score="85"/>
        </courses>
    </student>
    <student id="003">
        <name>Charlie</name>
        <age>21</age>
        <gender>Male</gender>
        <courses>
            <course name="Math" score="90"/>
            <course name="Chemistry" score="78"/>
        </courses>
    </student>
</class>

基本步骤

在 Java 中使用 XPath 通常遵循以下三个步骤:

Java如何用XPath解析XML?-图2
(图片来源网络,侵删)
  1. 获取 XPathFactory 实例:这是创建 XPath 处理器的工厂。
  2. 创建 XPath 对象:通过工厂对象创建 XPath 实例,它将用于编译和评估 XPath 表达式。
  3. 编译 XPath 表达式并评估:使用 XPath 对象的 compile() 方法编译表达式,然后用 evaluate() 方法在 XML 文档上执行该表达式。

完整示例代码

下面是一个完整的 Java 类,演示了如何读取 students.xml 并执行各种 XPath 查询。

XPathParserDemo.java

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.File;
public class XPathParserDemo {
    public static void main(String[] args) {
        try {
            // 1. 创建 XPathFactory 和 XPath 对象
            XPathFactory xPathFactory = XPathFactory.newInstance();
            XPath xpath = xPathFactory.newXPath();
            // 2. 加载 XML 文档到 DOM (Document Object Model)
            //    XPath 通常需要一个 DOM Document 或 InputSource 作为输入
            File xmlFile = new File("students.xml");
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.parse(xmlFile);
            // --- 示例 1: 获取单个节点的文本内容 ---
            // 获取第一个学生的名字
            String studentName = (String) xpath.evaluate("/class/student[1]/name/text()", document, XPathConstants.STRING);
            System.out.println("1. 第一个学生的名字: " + studentName); // 输出: Alice
            // --- 示例 2: 获取属性值 ---
            // 获取第二个学生的 id 属性
            String studentId = (String) xpath.evaluate("/class/student[2]/@id", document, XPathConstants.STRING);
            System.out.println("2. 第二个学生的ID: " + studentId); // 输出: 002
            // --- 示例 3: 获取节点列表 ---
            // 获取所有学生的名字
            NodeList nameNodes = (NodeList) xpath.evaluate("/class/student/name", document, XPathConstants.NODESET);
            System.out.println("3. 所有学生的名字:");
            for (int i = 0; i < nameNodes.getLength(); i++) {
                System.out.println("   - " + nameNodes.item(i).getTextContent());
            }
            /* 输出:
               3. 所有学生的名字:
                  - Alice
                  - Bob
                  - Charlie
            */
            // --- 示例 4: 使用谓词 (Predicates) 进行条件查询 ---
            // 获取所有年龄大于21岁的学生
            NodeList olderStudents = (NodeList) xpath.evaluate("/class/student[age > 21]", document, XPathConstants.NODESET);
            System.out.println("\n4. 年龄大于21岁的学生数量: " + olderStudents.getLength()); // 输出: 1
            for (int i = 0; i < olderStudents.getLength(); i++) {
                Node studentNode = olderStudents.item(i);
                // 需要在子节点中查找 name
                String name = (String) xpath.evaluate("name/text()", studentNode, XPathConstants.STRING);
                String age = (String) xpath.evaluate("age/text()", studentNode, XPathConstants.STRING);
                System.out.println("   - 姓名: " + name + ", 年龄: " + age);
            }
            /* 输出:
               4. 年龄大于21岁的学生数量: 1
                  - 姓名: Bob, 年龄: 22
            */
            // --- 示例 5: 使用通配符 ---
            // 获取第一个学生的所有子元素 (name, age, gender, courses)
            NodeList firstStudentChildren = (NodeList) xpath.evaluate("/class/student[1]/*", document, XPathConstants.NODESET);
            System.out.println("\n5. 第一个学生的所有子元素:");
            for (int i = 0; i < firstStudentChildren.getLength(); i++) {
                System.out.println("   - " + firstStudentChildren.item(i).getNodeName());
            }
            // --- 示例 6: 使用轴 (Axes) ---
            // 获取 "Bob" 节点的所有祖先节点 (class, ...)
            // ancestor:: 会从当前节点向上查找
            System.out.println("\n6. 'Bob' 节点的所有祖先节点:");
            NodeList ancestors = (NodeList) xpath.evaluate("student[name='Bob']/ancestor::*", document, XPathConstants.NODESET);
            for (int i = 0; i < ancestors.getLength(); i++) {
                System.out.println("   - " + ancestors.item(i).getNodeName());
            }
            /* 输出:
               6. 'Bob' 节点的所有祖先节点:
                  - class
            */
            // --- 示例 7: 使用函数 ---
            // 获取学生总数 (count 函数)
            // 注意:XPath 1.0 没有内置的 sum 函数对数字求和,但 count 可以
            Double studentCount = (Double) xpath.evaluate("count(/class/student)", document, XPathConstants.NUMBER);
            System.out.println("\n7. 学生总数: " + studentCount.intValue()); // 输出: 3
            // --- 示例 8: 复杂查询 - 获取所有选修了 Math 课的学生 ---
            // 使用 contains 或 =
            System.out.println("\n8. 选修了 Math 课的学生:");
            NodeList mathStudents = (NodeList) xpath.evaluate("/class/student[course[@name='Math']]", document, XPathConstants.NODESET);
            for (int i = 0; i < mathStudents.getLength(); i++) {
                Node studentNode = mathStudents.item(i);
                String name = (String) xpath.evaluate("name/text()", studentNode, XPathConstants.STRING);
                System.out.println("   - " + name);
            }
            /* 输出:
               8. 选修了 Math 课的学生:
                  - Alice
                  - Charlie
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码详解

1 核心类和接口

  • javax.xml.xpath.XPathFactory: 工厂类,用于创建 XPath 实例。
    • newInstance(): 静态工厂方法,获取默认的 XPathFactory 实现。
  • javax.xml.xpath.XPath: 主要接口,用于编译 XPath 表达式和评估表达式。
    • newXPath(): 从 XPathFactory 获取 XPath 实例。
    • compile(String expression): 编译一个 XPath 表达式,返回 XPathExpression 对象,编译可以缓存,提高性能。
    • evaluate(String expression, InputSource source, QName returnType): 执行 XPath 表达式。
      • expression: XPath 字符串。
      • source: XML 源,可以是 Document, InputSource 等。
      • returnType: 返回值的类型,使用 XPathConstants 枚举定义。
  • javax.xml.xpath.XPathConstants: 包含预定义的返回类型常量。
    • XPathConstants.NODE: 返回一个 Node
    • XPathConstants.NODESET: 返回一个 NodeList
    • XPathConstants.STRING: 返回 String
    • XPathConstants.NUMBER: 返回 Double
    • XPathConstants.BOOLEAN: 返回 Boolean
  • org.w3c.dom.Document: XML 文档在内存中的表示(DOM)。
  • org.w3c.dom.Node: DOM 中任何类型节点的基接口。
  • org.w3c.dom.NodeList: 节点集合,通常用于存储查询结果。

2 关键步骤解析

  1. 加载 XML:

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document document = db.parse(xmlFile);

    这部分代码使用标准的 DOM 解析器将 students.xml 文件读入内存,生成一个 Document 对象。xpath.evaluate() 方法可以直接操作这个 Document 对象。

    Java如何用XPath解析XML?-图3
    (图片来源网络,侵删)
  2. 评估表达式:

    // 方式一:直接 evaluate 字符串
    String name = (String) xpath.evaluate("/class/student/name", document, XPathConstants.STRING);
    // 方式二:先编译,再评估(推荐,尤其在循环中)
    XPathExpression expr = xpath.compile("/class/student/name");
    NodeList nodes = (NodeList) expr.evaluate(document, XPathConstants.NODESET);

    第二种方式(编译后评估)更高效,特别是当同一个 XPath 表达式需要被多次执行时,编译过程会预先解析表达式,后续评估时速度更快。

3 常用 XPath 表达式速查

表达式 描述 示例
nodename 选取此节点的所有子节点 /class/student
从根节点选取 /class
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 //student 选取所有 student 元素
选取当前节点 ./name 选取当前节点的 name 子节点
选取当前节点的父节点 ../age
选取属性 student/@id 选取 student 元素的 id 属性
匹配任何元素节点 /class/*/name 选取 class 下任何层级元素的 name 子元素
[] 谓词,用于过滤节点 /class/student[1] (第一个), /class/student[@id='001'] (id属性为001的)
text() 选取文本节点 /class/student/name/text()
and, or 逻辑操作 /class/student[age > 20 and gender='Female']
contains(), starts-with() 字符串函数 //student[contains(name, 'li')] (名字包含'li'的学生)

高级主题:命名空间

XML 文件使用了命名空间,XPath 查询会变得更复杂,你需要先注册命名空间前缀。

示例 XML (ns_students.xml)

<school:students xmlns:school="http://www.example.com/school">
    <school:student id="004">
        <school:name>David</school:name>
    </school:student>
</school:students>

查询带命名空间的 XML

// ...
Document document = db.parse(new File("ns_students.xml"));
// 1. 创建 XPath 对象
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
// 2. 注册命名空间前缀
//    第一个参数是你在 XPath 表达式中使用的前缀
//    第二个参数是 XML 中定义的命名空间 URI
xpath.setNamespaceContext(new SimpleNamespaceContext("sch", "http://www.example.com/school"));
// 3. 使用带前缀的 XPath 表达式
String name = (String) xpath.evaluate("/sch:students/sch:student/sch:name/text()", document, XPathConstants.STRING);
System.out.println("命名空间查询结果: " + name); // 输出: David

SimpleNamespaceContext 是一个简单的 NamespaceContext 实现,你可以自己实现 NamespaceContext 接口来处理更复杂的命名空间映射。

使用 XPath 解析 XML 在 Java 中是一个非常强大且灵活的技术,它比 DOM 遍历更简洁,比 SAX 更容易定位特定数据,掌握 XPathFactory, XPath, Document 和常用 XPath 表达式是关键,对于大型 XML 文件,可以考虑使用 SAXSourceStAXSource 作为 evaluate 方法的输入,以避免将整个文档加载到内存中。

分享:
扫描分享到社交APP
上一篇
下一篇