Java DOM 教程展示了如何使用 Java DOM API 读写 XML 文档。
DOM
文档对象模型(DOM)是标准树结构,其中每个节点都包含来自 XML 结构的组件之一。 元素节点和文本节点是两种最常见的节点类型。 使用 DOM 函数,我们可以创建节点,删除节点,更改其内容以及遍历节点层次结构。
Java DOM
DOM 是用于 XML 处理(JAXP)的 Java API 的一部分。 Java DOM 解析器遍历 XML 文件并创建相应的 DOM 对象。 这些 DOM 对象以树结构链接在一起。 解析器将整个 XML 结构读入内存。
SAX 是 DOM 的替代 JAXP API。 SAX 解析器基于事件; 它们速度更快,所需的内存更少。 另一方面,DOM 更易于使用,并且有些任务(例如,排序元素,重新排列元素或查找元素)使用 DOM 更快。 DOM 解析器是 JDK 附带的,因此无需下载依赖项。
DocumentBuilderFactory
使应用可以获得一个解析器,该解析器从 XML 文档生成 DOM 对象树。 DocumentBuilder
定义用于从 XML 文档获取 DOM 文档实例或创建新 DOM 文档的 API。 DocumentTraversal
包含创建迭代器以遍历节点及其子节点的方法。 NodeFilter
用于过滤掉节点。 NodeIterator
用于遍历一组节点。 TreeWalker
用于使用由其whatToShow
标志和文档过滤器定义的文档视图浏览文档树或子树。
节点类型
以下是一些重要的节点类型的列表:
类型 | 描述 |
---|---|
Attr |
表示 Element 对象中的属性 |
CDATASection |
转义包含可能被视为标记的字符的文本块 |
Comment |
代表评论的内容 |
Document |
代表整个 HTML 或 XML 文档 |
DocumentFragment |
一个轻量级或最小的 Document 对象,用于表示 XML 文档中大于单个节点的部分 |
Element |
元素节点是 DOM 树的基本分支; 除文本外,大多数项目都是元素 |
Node |
整个 DOM 及其每个元素的主要数据类型 |
NodeList |
有序的节点集合 |
Text |
表示元素或属性的文本内容(在 XML 中称为字符数据) |
XML 示例文件
我们使用以下 XML 文件:
users.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
文件。
continents.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 主类。
Java DOM 读取示例
在下面的示例中,我们使用 DOM 解析器读取 XML 文件。
JavaXmlDomReadEx.java
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 文档实例的 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()
获得 element 属性。
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
以首先深度遍历节点及其子节点(预订购文档顺序)的方法。 此顺序等效于开始标记在文档的文本表示中出现的顺序。
JavaXmlDomReadElements.java
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
包含这 18 个元素。
Java DOM 使用NodeIterator
读取文本
在下面的示例中,我们使用NodeIterator
读取文本数据。
JavaXmlDomReadText.java
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
This is the output.
Java DOM 自定义NodeFilter
以下示例使用自定义 DOM 过滤器。 自定义 DOM 过滤器必须实现NodeFilter
接口。
JavaXmlCustomFilter.java
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 文件中的斯洛伐克和波兰节点。
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
This is the output.
Java DOM 使用TreeWalker
读取 XML
TreeWalker
比NodeIterator
具有更多的遍历方法。
JavaXmlDomTreeWalkerEx.java
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
This is the output.
Java DOM 编写示例
在下面的示例中,我们创建一个 XML 文件。
JavaXmlDomWrite.java
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()
创建文本节点。