企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# Java DOM 教程 原文:http://zetcode.com/java/dom/ 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` ```java <?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` ```java <?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`文件。 ```java <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` ```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")`。 ```java DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = factory.newDocumentBuilder(); ``` 从`DocumentBuilderFactory`获得`DocumentBuilder`。 `DocumentBuilder`包含用于从 XML 文档中获取 DOM 文档实例的 API。 ```java Document doc = dBuilder.parse(xmlFile); ``` `parse()`方法将 XML 文件解析为`Document`。 ```java doc.getDocumentElement().normalize(); ``` 规范化文档有助于生成正确的结果。 ```java System.out.println("Root element:" + doc.getDocumentElement().getNodeName()); ``` 我们得到了文档的根元素。 ```java NodeList nList = doc.getElementsByTagName("user"); ``` 我们使用`getElementsByTagName()`在文档中获得了用户元素的`NodeList`。 ```java for (int i = 0; i < nList.getLength(); i++) { ``` 我们使用`for`循环遍历列表。 ```java String uid = elem.getAttribute("id"); ``` 我们通过`getAttribute()`获得`element`属性。 ```java 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(); ``` 我们获得用户元素的三个子元素的文本内容。 ```java 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); ``` 我们将当前用户的文本打印到控制台。 ```java $ 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` ```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`文件的所有节点元素。 ```java DocumentTraversal trav = (DocumentTraversal) document; ``` 从文档中,我们得到一个`DocumentTraversal`对象。 ```java NodeIterator it = trav.createNodeIterator(document.getDocumentElement(), NodeFilter.SHOW_ELEMENT, null, true); ``` 我们创建一个`NodeIterator`。 设置`NodeFilter.SHOW_ELEMENT`时,仅显示节点元素。 ```java for (Node node = it.nextNode(); node != null; node = it.nextNode()) { String name = node.getNodeName(); System.out.printf("%d %s%n", c, name); c++; } ``` 在`for`循环中,我们遍历节点并打印其名称。 ```java $ 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` ```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`文件读取字符数据。 ```java NodeIterator iterator = traversal.createNodeIterator( document.getDocumentElement(), NodeFilter.SHOW_TEXT, null, true); ``` 节点过滤器设置为`NodeFilter.SHOW_TEXT`。 ```java String text = n.getTextContent().trim(); if (!text.isEmpty()) { System.out.println(text); } ``` 我们修剪空白并打印文本(如果不为空)。 ```java $ mvn -q exec:java Bratislava 421000 Budapest 1759000 Warsaw 1735000 Beijing 21700000 Hanoi 7500000 ``` 这是输出。 ## Java DOM 自定义`NodeFilter` 以下示例使用自定义 DOM 过滤器。 自定义 DOM 过滤器必须实现`NodeFilter`接口。 `JavaXmlCustomFilter.java` ```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 文件中的斯洛伐克和波兰节点。 ```java MyFilter filter = new MyFilter(); NodeIterator it = trav.createNodeIterator(document.getDocumentElement(), NodeFilter.SHOW_ELEMENT, filter, true); ``` 我们创建`MyFilter`并将其设置为`createNodeIterator()`方法。 ```java String text = node.getTextContent().trim().replaceAll("\\s+", " "); ``` 文本内容包含空格和换行符; 因此,我们使用正则表达式删除了不必要的空格。 ```java 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`来控制要使用的节点。 ```java $ mvn -q exec:java slovakia: Bratislava 421000 poland: Warsaw 1735000 ``` 这是输出。 ## Java DOM 使用`TreeWalker`读取 XML `TreeWalker`比`NodeIterator`具有更多的遍历方法。 `JavaXmlDomTreeWalkerEx.java` ```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`文件的元素和文本。 ```java TreeWalker walker = traversal.createTreeWalker( document.getDocumentElement(), NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, true); ``` 使用`DocumentTraversal`中的`createTreeWalker()`创建了`TreeWalker`。 我们将处理元素和文本节点。 请注意,空文本(例如缩进)也被视为文本。 ```java traverseLevel(walker, ""); ``` 该处理委托给`traverseLevel()`方法,该方法被递归调用。 ```java if (node.getNodeType() == Node.ELEMENT_NODE) { System.out.println(indent + node.getNodeName()); } ``` 我们使用缩进来打印元素的名称。 ```java 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); } } ``` 我们打印文本数据。 由于我们仅对资本和人口数据感兴趣,因此我们跳过所有空字符串。 ```java for (Node n = walker.firstChild(); n != null; n = walker.nextSibling()) { traverseLevel(walker, indent + " "); } ``` 在此`for`循环中,我们递归地深入到树的分支中。 ```java walker.setCurrentNode(node); ``` 完成分支处理后,我们将与`setCurrentNode()`进入同一级别,以便我们可以继续进行另一个树分支。 ```java $ 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 文件。 `JavaXmlDomWrite.java` ```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`文件。 ```java DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); ``` 从文档构建器工厂创建一个新的文档构建器。 ```java Document doc = builder.newDocument(); ``` 在文档构建器中,我们使用`newDocument()`创建一个新文档。 ```java Element root = doc.createElementNS("zetcode.com", "users"); doc.appendChild(root); ``` 我们创建一个根元素,并使用`appendChild()`将其添加到文档中。 ```java root.appendChild(createUser(doc, "1", "Robert", "Brown", "programmer")); root.appendChild(createUser(doc, "2", "Pamela", "Kyle", "writer")); root.appendChild(createUser(doc, "3", "Peter", "Smith", "teacher")); ``` 我们将三个子元素附加到根元素。 ```java TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transf = transformerFactory.newTransformer(); ``` Java DOM 使用`Transformer`生成 XML 文件。 之所以称为转换器,是因为它也可以使用 XSLT 语言转换文档。 在我们的情况下,我们仅写入 XML 文件。 ```java transf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transf.setOutputProperty(OutputKeys.INDENT, "yes"); transf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); ``` 我们设置文档的编码和缩进。 ```java DOMSource source = new DOMSource(doc); ``` `DOMSource`保存 DOM 树。 ```java StreamResult console = new StreamResult(System.out); StreamResult file = new StreamResult(myFile); ``` 我们将要写入控制台和文件。 `StreamResult`是转换结果的持有者。 ```java transf.transform(source, console); transf.transform(source, file); ``` 我们将 XML 源代码写入流结果。 ```java 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()`设置。 ```java 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 SAX 教程](/java/sax/), [Java JAXB 教程](/java/jaxb/), [Java JSON 处理教程](/java/jsonp)和 [Java 教程](/lang/java/) 。