Java DOM
最后修改于 2024 年 1 月 27 日
Java DOM 教程展示了如何使用 Java DOM API 读取和写入 XML 文档。
DOM
文档对象模型 (DOM) 是一个标准的树结构,其中每个节点包含 XML 结构中的一个组件。元素节点和文本节点是两种最常见的节点类型。通过 DOM 函数,我们可以创建节点、删除节点、更改节点内容以及遍历节点层次结构。
Java DOM
DOM 是 Java API for XML Processing (JAXP) 的一部分。Java DOM 解析器会遍历 XML 文件并创建相应的 DOM 对象。这些 DOM 对象以树形结构链接在一起。解析器将整个 XML 结构读取到内存中。
SAX 是 DOM 的另一种 JAXP API。SAX 解析器是基于事件的;它们速度更快且需要的内存更少。另一方面,DOM 更易于使用,并且对于某些任务(例如对元素进行排序、重新排列元素或查找元素)而言,DOM 速度更快。DOM 解析器随 JDK 一起提供,因此无需下载依赖项。
DocumentBuilderFactory 使应用程序能够获取一个解析器,该解析器从 XML 文档生成 DOM 对象树。DocumentBuilder 定义了从 XML 文档获取 DOM Document 实例或创建新 DOM Document 的 API。DocumentTraversal 包含创建迭代器以遍历节点及其子节点的方法。NodeFilter 用于过滤掉节点。NodeIterator 用于遍历一组节点。TreeWalker 用于使用由其 whatToShow 标志和文档过滤器定义的文档视图来导航文档树或子树。
节点类型
以下是一些重要的节点类型的列表
| 类型 | 描述 |
|---|---|
| Attr | 表示 Element 对象中的一个属性 |
| CDATASection | 转义包含字符的文本块,这些字符在其他情况下会被视为标记 |
| Comment | 表示注释的内容 |
| Document | 表示整个 HTML 或 XML 文档 |
| DocumentFragment | 一个轻量级的或最小的 Document 对象,用于表示大于单个节点的 XML 文档的某些部分 |
| Element | 元素节点是 DOM 树的基本分支;除了文本之外的大多数项目都是元素 |
| Node | 整个 DOM 及其每个元素的主要数据类型 |
| NodeList | 节点的有序集合 |
| 文本 | 表示 Element 或 Attr 的文本内容(在 XML 中称为字符数据) |
XML 示例文件
我们使用以下 XML 文件
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="1">
<firstname>Peter</firstname>
<lastname>Brown</lastname>
<occupation>programmer</occupation>
</user>
<user id="2">
<firstname>Martin</firstname>
<lastname>Smith</lastname>
<occupation>accountant</occupation>
</user>
<user id="3">
<firstname>Lucy</firstname>
<lastname>Gordon</lastname>
<occupation>teacher</occupation>
</user>
</users>
这是 users.xml 文件。
<?xml version="1.0" encoding="UTF-8"?>
<continents>
<europe>
<slovakia>
<capital>
Bratislava
</capital>
<population>
421000
</population>
</slovakia>
<hungary>
<capital>
Budapest
</capital>
<population>
1759000
</population>
</hungary>
<poland>
<capital>
Warsaw
</capital>
<population>
1735000
</population>
</poland>
</europe>
<asia>
<china>
<capital>
Beijing
</capital>
<population>
21700000
</population>
</china>
<vietnam>
<capital>
Hanoi
</capital>
<population>
7500000
</population>
</vietnam>
</asia>
</continents>
这是 continents.xml 文件。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>com.zetcode.JavaReadXmlDomEx</mainClass>
</configuration>
</plugin>
</plugins>
</build>
这些示例使用 exec-maven-plugin 从 Maven 执行 Java main 类。
Java DOM 读取示例
在以下示例中,我们使用 DOM 解析器读取 XML 文件。
package com.zetcode;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
public class JavaXmlDomReadEx {
public static void main(String argv[]) throws SAXException,
IOException, ParserConfigurationException {
File xmlFile = new File("src/main/resources/users.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = factory.newDocumentBuilder();
Document doc = dBuilder.parse(xmlFile);
doc.getDocumentElement().normalize();
System.out.println("Root element: " + doc.getDocumentElement().getNodeName());
NodeList nList = doc.getElementsByTagName("user");
for (int i = 0; i < nList.getLength(); i++) {
Node nNode = nList.item(i);
System.out.println("\nCurrent Element: " + nNode.getNodeName());
if (nNode.getNodeType() == Node.ELEMENT_NODE) {
Element elem = (Element) nNode;
String uid = elem.getAttribute("id");
Node node1 = elem.getElementsByTagName("firstname").item(0);
String fname = node1.getTextContent();
Node node2 = elem.getElementsByTagName("lastname").item(0);
String lname = node2.getTextContent();
Node node3 = elem.getElementsByTagName("occupation").item(0);
String occup = node3.getTextContent();
System.out.printf("User id: %s%n", uid);
System.out.printf("First name: %s%n", fname);
System.out.printf("Last name: %s%n", lname);
System.out.printf("Occupation: %s%n", occup);
}
}
}
}
该示例解析 users.xml 文件。它利用了代码中的标签名称;例如:elem.getElementsByTagName("lastname")。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = factory.newDocumentBuilder();
从 DocumentBuilderFactory 中,我们得到 DocumentBuilder。DocumentBuilder 包含从 XML 文档获取 DOM Document 实例的 API。
Document doc = dBuilder.parse(xmlFile);
parse 方法将 XML 文件解析为 Document。
doc.getDocumentElement().normalize();
规范化文档有助于生成正确的结果。
System.out.println("Root element:" + doc.getDocumentElement().getNodeName());
我们获取文档的根元素。
NodeList nList = doc.getElementsByTagName("user");
我们使用 getElementsByTagName 获取文档中用户元素的 NodeList。
for (int i = 0; i < nList.getLength(); i++) {
我们使用 for 循环遍历列表。
String uid = elem.getAttribute("id");
我们使用 getAttribute 获取元素属性。
Node node1 = elem.getElementsByTagName("firstname").item(0);
String fname = node1.getTextContent();
Node node2 = elem.getElementsByTagName("lastname").item(0);
String lname = node2.getTextContent();
Node node3 = elem.getElementsByTagName("occupation").item(0);
String occup = node3.getTextContent();
我们获取用户元素的三个子元素的文本内容。
System.out.printf("User id: %s%n", uid);
System.out.printf("First name: %s%n", fname);
System.out.printf("Last name: %s%n", lname);
System.out.printf("Occupation: %s%n", occup);
我们将当前用户的文本打印到控制台。
$ mvn -q exec:java Root element: users Current Element: user User id: 1 First name: Peter Last name: Brown Occupation: programmer Current Element: user User id: 2 First name: Martin Last name: Smith Occupation: accountant Current Element: user User id: 3 First name: Lucy Last name: Gordon Occupation: teacher
Java DOM 使用 NodeIterator 读取元素
DocumentTraversal 包含创建 NodeIterators 和 TreeWalkers 的方法,以深度优先、预先排序的文档顺序遍历节点及其子节点。此顺序等效于开始标记在文档的文本表示形式中出现的顺序。
package com.zetcode;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;
public class JavaXmlDomReadElements {
public static void main(String[] args) throws ParserConfigurationException,
SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder loader = factory.newDocumentBuilder();
Document document = loader.parse("src/main/resources/continents.xml");
DocumentTraversal trav = (DocumentTraversal) document;
NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT, null, true);
int c = 1;
for (Node node = it.nextNode(); node != null;
node = it.nextNode()) {
String name = node.getNodeName();
System.out.printf("%d %s%n", c, name);
c++;
}
}
}
该示例打印 continents.xml 文件的所有节点元素。
DocumentTraversal trav = (DocumentTraversal) document;
从文档中,我们获取 DocumentTraversal 对象。
NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT, null, true);
我们创建一个 NodeIterator。设置了 NodeFilter.SHOW_ELEMENT 后,它仅显示节点元素。
for (Node node = it.nextNode(); node != null;
node = it.nextNode()) {
String name = node.getNodeName();
System.out.printf("%d %s%n", c, name);
c++;
}
在一个 for 循环中,我们遍历节点并打印它们的名称。
$ mvn -q exec:java 1 continents 2 europe 3 slovakia 4 capital 5 population 6 hungary 7 capital 8 population 9 poland 10 capital 11 population 12 asia 13 china 14 capital 15 population 16 vietnam 17 capital 18 population
continents.xml 包含这十八个元素。
Java DOM 使用 NodeIterator 读取文本
在以下示例中,我们使用 NodeIterator 读取文本数据。
package com.zetcode;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;
public class JavaXmlDomReadText {
public static void main(String[] args) throws ParserConfigurationException,
SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder loader = factory.newDocumentBuilder();
Document document = loader.parse("src/main/resources/continents.xml");
DocumentTraversal traversal = (DocumentTraversal) document;
NodeIterator iterator = traversal.createNodeIterator(
document.getDocumentElement(), NodeFilter.SHOW_TEXT, null, true);
for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
String text = n.getTextContent().trim();
if (!text.isEmpty()) {
System.out.println(text);
}
}
}
}
该示例从 continents.xml 文件读取字符数据。
NodeIterator iterator = traversal.createNodeIterator(
document.getDocumentElement(), NodeFilter.SHOW_TEXT, null, true);
节点过滤器设置为 NodeFilter.SHOW_TEXT。
String text = n.getTextContent().trim();
if (!text.isEmpty()) {
System.out.println(text);
}
我们修剪空格,如果文本不为空,则打印文本。
$ mvn -q exec:java Bratislava 421000 Budapest 1759000 Warsaw 1735000 Beijing 21700000 Hanoi 7500000
Java DOM 自定义 NodeFilter
以下示例使用自定义 DOM 过滤器。自定义 DOM 过滤器必须实现 NodeFilter 接口。
package com.zetcode;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;
public class JavaXmlCustomFilter {
public static void main(String[] args) throws ParserConfigurationException,
SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder loader = factory.newDocumentBuilder();
Document document = loader.parse("src/main/resources/continents.xml");
DocumentTraversal trav = (DocumentTraversal) document;
MyFilter filter = new MyFilter();
NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT, filter, true);
for (Node node = it.nextNode(); node != null;
node = it.nextNode()) {
String name = node.getNodeName();
String text = node.getTextContent().trim().replaceAll("\\s+", " ");
System.out.printf("%s: %s%n", name, text);
}
}
static class MyFilter implements NodeFilter {
@Override
public short acceptNode(Node thisNode) {
if (thisNode.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) thisNode;
String nodeName = e.getNodeName();
if ("slovakia".equals(nodeName) || "poland".equals(nodeName)) {
return NodeFilter.FILTER_ACCEPT;
}
}
return NodeFilter.FILTER_REJECT;
}
}
}
该示例仅显示 XML 文件中的 slovakia 和 poland 节点。
MyFilter filter = new MyFilter();
NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT, filter, true);
我们创建 MyFilter 并将其设置为 createNodeIterator 方法。
String text = node.getTextContent().trim().replaceAll("\\s+", " ");
文本内容包含空格和换行符;因此,我们使用正则表达式删除不必要的空格。
static class MyFilter implements NodeFilter {
@Override
public short acceptNode(Node thisNode) {
if (thisNode.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) thisNode;
String nodeName = e.getNodeName();
if ("slovakia".equals(nodeName) || "poland".equals(nodeName)) {
return NodeFilter.FILTER_ACCEPT;
}
}
return NodeFilter.FILTER_REJECT;
}
}
在 acceptNode 方法中,我们通过返回 NodeFilter.FILTER_ACCEPT 和 NodeFilter.FILTER_REJECT 来控制要使用的节点。
$ mvn -q exec:java slovakia: Bratislava 421000 poland: Warsaw 1735000
Java DOM 使用 TreeWalker 读取 XML
TreeWalker 具有比 NodeIterator 更多的遍历方法。
package com.zetcode;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;
import org.xml.sax.SAXException;
public class JavaXmlDomTreeWalkerEx {
public static void main(String[] args) throws SAXException, IOException,
ParserConfigurationException {
DocumentBuilderFactory factory
= DocumentBuilderFactory.newInstance();
DocumentBuilder loader = factory.newDocumentBuilder();
Document document = loader.parse("src/main/resources/continents.xml");
DocumentTraversal traversal = (DocumentTraversal) document;
TreeWalker walker = traversal.createTreeWalker(
document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, true);
traverseLevel(walker, "");
}
private static void traverseLevel(TreeWalker walker,
String indent) {
Node node = walker.getCurrentNode();
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(indent + node.getNodeName());
}
if (node.getNodeType() == Node.TEXT_NODE) {
String content_trimmed = node.getTextContent().trim();
if (content_trimmed.length() > 0) {
System.out.print(indent);
System.out.printf("%s%n", content_trimmed);
}
}
for (Node n = walker.firstChild(); n != null;
n = walker.nextSibling()) {
traverseLevel(walker, indent + " ");
}
walker.setCurrentNode(node);
}
}
该示例使用 TreeWalker 读取 continents.xml 文件的元素和文本。
TreeWalker walker = traversal.createTreeWalker(
document.getDocumentElement(),
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, true);
从 DocumentTraversal 使用 createTreeWalker 创建一个 TreeWalker。我们将处理元素和文本节点。请注意,空文本(如缩进)也被认为是文本。
traverseLevel(walker, "");
处理被委托给 traverseLevel 方法,该方法被递归调用。
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(indent + node.getNodeName());
}
我们打印元素的名称并带有一些缩进。
if (node.getNodeType() == Node.TEXT_NODE) {
String content_trimmed = node.getTextContent().trim();
if (content_trimmed.length() > 0) {
System.out.print(indent);
System.out.printf("%s%n", content_trimmed);
}
}
我们打印文本数据。由于我们只对首都和人口数据感兴趣,因此我们跳过所有空字符串。
for (Node n = walker.firstChild(); n != null;
n = walker.nextSibling()) {
traverseLevel(walker, indent + " ");
}
在这个 for 循环中,我们递归地深入到树的一个分支。
walker.setCurrentNode(node);
完成一个分支的处理后,我们使用 setCurrentNode 转到同一级别,以便我们可以继续处理另一个树分支。
$ mvn -q exec:java
continents
europe
slovakia
capital
Bratislava
population
421000
hungary
capital
Budapest
population
1759000
poland
capital
Warsaw
population
1735000
asia
china
capital
Beijing
population
21700000
vietnam
capital
Hanoi
population
7500000
Java DOM 写入示例
在以下示例中,我们创建一个 XML 文件。
package com.zetcode;
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class JavaXmlDomWrite {
public static void main(String[] args) throws ParserConfigurationException,
TransformerException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root = doc.createElementNS("zetcode.com", "users");
doc.appendChild(root);
root.appendChild(createUser(doc, "1", "Robert", "Brown", "programmer"));
root.appendChild(createUser(doc, "2", "Pamela", "Kyle", "writer"));
root.appendChild(createUser(doc, "3", "Peter", "Smith", "teacher"));
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transf = transformerFactory.newTransformer();
transf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transf.setOutputProperty(OutputKeys.INDENT, "yes");
transf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
DOMSource source = new DOMSource(doc);
File myFile = new File("src/main/resources/users.xml");
StreamResult console = new StreamResult(System.out);
StreamResult file = new StreamResult(myFile);
transf.transform(source, console);
transf.transform(source, file);
}
private static Node createUser(Document doc, String id, String firstName,
String lastName, String occupation) {
Element user = doc.createElement("user");
user.setAttribute("id", id);
user.appendChild(createUserElement(doc, "firstname", firstName));
user.appendChild(createUserElement(doc, "lastname", lastName));
user.appendChild(createUserElement(doc, "occupation", occupation));
return user;
}
private static Node createUserElement(Document doc, String name,
String value) {
Element node = doc.createElement(name);
node.appendChild(doc.createTextNode(value));
return node;
}
}
该示例在 src/main/resources 目录中创建一个新的 users.xml 文件。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder();
从文档生成器工厂创建一个新的文档生成器。
Document doc = builder.newDocument();
从文档生成器中,我们使用 newDocument 创建一个新文档。
Element root = doc.createElementNS("zetcode.com", "users");
doc.appendChild(root);
我们创建一个根元素并使用 appendChild 将其添加到文档中。
root.appendChild(createUser(doc, "1", "Robert", "Brown", "programmer")); root.appendChild(createUser(doc, "2", "Pamela", "Kyle", "writer")); root.appendChild(createUser(doc, "3", "Peter", "Smith", "teacher"));
我们将三个子元素附加到根元素。
TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transf = transformerFactory.newTransformer();
Java DOM 使用 Transformer 生成 XML 文件。它被称为转换器,因为它也可以使用 XSLT 语言转换文档。在我们的例子中,我们只写入 XML 文件。
transf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transf.setOutputProperty(OutputKeys.INDENT, "yes");
transf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
我们设置文档的编码和缩进。
DOMSource source = new DOMSource(doc);
DOMSource 保存 DOM 树。
StreamResult console = new StreamResult(System.out); StreamResult file = new StreamResult(myFile);
我们将写入控制台和一个文件。StreamResult 是转换结果的持有者。
transf.transform(source, console); transf.transform(source, file);
我们将 XML 源写入到流结果。
private static Node createUser(Document doc, String id, String firstName,
String lastName, String occupation) {
Element user = doc.createElement("user");
user.setAttribute("id", id);
user.appendChild(createUserElement(doc, "firstname", firstName));
user.appendChild(createUserElement(doc, "lastname", lastName));
user.appendChild(createUserElement(doc, "occupation", occupation));
return user;
}
使用 createElement 在 createUser 方法中创建一个新的用户元素。使用 setAttribute 设置元素的属性。
private static Node createUserElement(Document doc, String name,
String value) {
Element node = doc.createElement(name);
node.appendChild(doc.createTextNode(value));
return node;
}
使用 appendChild 将元素添加到其父元素,并使用 createTextNode 创建一个文本节点。
来源
在本文中,我们使用 Java DOM API 读取和写入了 XML 文件。
作者
列出所有Java教程。