二、XML
2.1 XML 简介
XML(Extensible Markup Language),可扩展标记语言
2.1.1 特点
- XML 与操作系统、编程语言的开发平台无关
- 规范统一,实现不同系统之间的数据交换
2.1.2 作用
- 数据存储
- 数据交换
- 数据配置
2.1.3 XML 文档结构
<?xml version="1.0" encoding="UTF-8"?>
<!-- 声明 -->
<books>
<!-- 根元素 -->
<!-- 图书信息 -->
<book id="bk101">
<!-- id="bk101" 属性 -->
<title>.NET高级编程</title>
<author>王姗</author>
<description>包含C#框架和网络编程等</description>
</book>
<book id="bk102">
<title>XML基础编程</title>
<author>李明明</author>
<description>包含XML基础概念和基本用法</description>
</book>
</books>
使用标签描述的文档元素信息,成对出现。
节点不包含子节点可自闭合
<title text="自闭合"/>
2.2 XML 标签
XML 文档的内容元素由一系列标签组成
<元素名 属性名="属性值">元素内容(子元素、字符等)</元素名>
<!-- 开始标签 --> <!-- 元素允许是空元素,语法:如<title></title>、<title/>--> <!-- 结束标签 -->
2.2.1 元素命名规则
- 名称中可以包含字母、数字或者其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字符
xml
(或者XML、Xml)开始 - 名称中不能含空格
2.2.2 属性语法规则
- 属性可以加在任何一个元素的起始标签上,但不能加在结束标签上
- 属性值用双引号包裹
- 一个元素可以有多个属性,多个属性之间用空格隔开
- 属性值中不能直接包含
<、"、&
2.2.3 XML 中的特殊字符的处理
XML 中的特殊字符包括 < 、> 、' 、" 、&
使用预定义实体对特殊字符进行转义
特殊字符 | 实体名称 |
---|---|
< | < |
> | > |
" | " |
’ | ' |
& | & |
在元素的文本中使用 CDATA 节处理
<description>
<![CDATA[讲解了元素<title>以及</title>的使用]]>
</description>
2.2.4 编写格式良好的 XML 文档
格式良好的 XML 文档需要遵循如下规则
- 有 XML 声明语句
- 有且仅有一个根元素
- 标签大小写敏感
- 属性值用双引号
- 标签成对/空标签关闭
- 元素正确嵌套
2.3 XML 解析技术
2.3.1 DOM
- 基于 XML 文档树结构的解析
- 适用于多次访问的 XML 文档
- 比较消耗资源
2.3.2 SAX
- 基于事件的解析
- 适用于大数据量的 XML 文档
- 占用资源少,内存消耗小
DOM 和 SAX 不针对特定语言,只是定义了一些接口,以及部分缺省实现(使用空方法实现接口)
2.3.3 JDOM
- 针对 Java 的特定文档模型
- 使用具体类而不使用接口
- API 大量使用了 Java 集合类型
2.3.4 DOM4J
- 非常优秀的开源 Java XML API
- 面向接口编程,比 JDOM 更加灵活
- 使用 Java 集合框架处理 XML
2.4 DOM 解析 XML 文档
2.4.1 DM 解析 XML 原理
文档对象模型(Document Object Model)DOM 把 XML 文档映射成一个倒挂的树
<book >
<title>三国演义</title>
<author>罗贯中</author>
<price>30元</price>
</book>
使用 Oracle 提供的 JAXP(Java API for XML Processing)
- org.w3c.dom:W3C 推荐的用于使用 DOM 解析 XML 文档的接
- org.xml.sax:用于使用 SAX 解析 XML 文档的接口
- javax.xml.parsers:解析器工厂工具,获得并配置特殊的分析器
2.4.2 解析 XML 文档实现步骤
- 创建解析器工厂 DocumentBuilderFactory 对象
- 由解析器工厂对象创建解析器 DocumentBuilder 对象
- 由解析器对象对 XML 文件进行解析、构建 DOM 树,创建相应的 Document 对象
- 以Document 对象为起点对 DOM 树的节点进行增加、删除、修改、查询等操作
2.4.3 解析 XML 部分常用 API
常用接口 | 说明 | 常用方法 |
---|---|---|
Node | 代表了文档树中的一个抽象节点,有 Document、Element、Text 等子对象 | NodeList getChildNodes() Node getFirstChild() Node getLastChild() Node getNextSibling() Node getPreviousSibling() Node appendChild(Node child) String getNodeName() String getNodeValue() short getNodeType() |
NodeList | 包含了一个或多个节点(Node)的列表 | int getLength() Node item(int index) |
Document | 表示整个 XML 文档 | NodeList getElementsByTagName(String tagname) Element getElementById(String id) Element getDocumentElement() Element createElement(String tagname) |
Element | XML 文档中的一个元素,是 Node 最主要的子对象 | String getAttribute(String name) NodeList getElementsByTagName(String tagname) String getTagName() |
2.4.4 访问 DOM 树节点属性
显示 XML 文件中收藏的手机品牌和型号
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PhoneInfo>
<Brand name="华为">
<Type name="P90"/>
<Item>
<title>标题信息</title>
<link>链接</link>
<description>描述</description>
<pubDate>2023-02-01</pubDate>
</Item>
</Brand>
<Brand name="苹果">
<Type name="iPhone Z"/>
<Type name="iPhone ZL"/>
</Brand>
<Brand name="三星">
<Type name="NoteX"/>
</Brand>
</PhoneInfo>
- 演示案例一
public class Book {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList bookList = doc.getElementsByTagName("Brand");
for (int i = 0; i < bookList.getLength(); i++) {
Node brand = bookList.item(i);
Element element = (Element) brand;
String attrVal = element.getAttribute("name");
NodeList types = element.getChildNodes();
for (int j = 0; j < types.getLength(); j++) {
Node typeNode = types.item(j);
if (typeNode.getNodeType() == Node.ELEMENT_NODE) {
Element typeElement = (Element) typeNode;
String type = typeElement.getAttribute("name");
System.out.println("手机:" + attrVal + type);
}
}
}
}
}
- 演示案例二
public class Xz01 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
NodeList brandList = DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse("src/main/resources/PhoneInfo.xml")
.getElementsByTagName("Brand");
for (int i = 0; i < brandList.getLength(); i++) {
Element brandElement = (Element) brandList.item(i);
System.out.println(brandElement.getTagName() + ":" + brandElement.getAttribute("name"));
NodeList typeList = brandElement.getElementsByTagName("Type");
for (int j = 0; j < typeList.getLength(); j++) {
Element typeElement = (Element) typeList.item(j);
System.out.println("\t" + typeElement.getTagName() + ":" + typeElement.getAttribute("name"));
NodeList itemList = brandElement.getElementsByTagName("Item");
for (int k = 0; k < itemList.getLength(); k++) {
Element itemElement = (Element) itemList.item(k);
NodeList pubDateList = itemElement.getElementsByTagName("pubDate");
System.out.print("\t" + itemElement.getTagName() + ":");
for (int l = 0; l < pubDateList.getLength(); l++) {
String textContent = pubDateList.item(l).getTextContent();
System.out.println(textContent);
}
}
}
}
}
}
// Brand:华为
// Type:P90
// Item:2023-02-01
// Brand:苹果
// Type:iPhone Z
// Type:iPhone ZL
// Brand:三星
// Type:NoteX
调用 Node 对象的 getChildNodes() 方法有时会包含 XML 文档中的空白符,导致调用 Node 对象方法出错,解决办法包括:
- 取得 Node 对象后判断
node.getNodeType() == Node.ELEMENT_NODE
,即判断是否是元素节- 点将 Node 对象转换为 Element 对象,使用 Element 的 getElementsByTagName(String name)
2.4.5 访问 DOM 树节点内容
显示XML文件中手机新闻的发布日期
- 演示案例
public class Book2 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList bookList = doc.getElementsByTagName("pubDate");
Element pubDateElement = (Element) bookList.item(0);
String nodeValue = pubDateElement.getFirstChild().getNodeValue();
System.out.println(nodeValue);
}
}
// 2023-02-01
2.4.6 添加 DOM 节点并保存
- 演示案例
public class Book3 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
Element brandElement = doc.createElement("Brand");
brandElement.setAttribute("name", "三星");
Element typeElement = doc.createElement("Type");
typeElement.setAttribute("name", "NoteX");
brandElement.appendChild(typeElement);
Element phoneInfoElement = (Element) doc.getElementsByTagName("PhoneInfo").item(0);
phoneInfoElement.appendChild(brandElement);
TransformerFactory tff = TransformerFactory.newInstance();
Transformer tf = tff.newTransformer();
DOMSource domSource = new DOMSource(doc);
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StreamResult streamResult = new StreamResult(Files.newOutputStream(Paths.get("src/main/java/CH15_XML/PhoneInfo.xml")));
tf.transform(domSource, streamResult);
}
}
<Brand name="三星">
<Type name="NoteX"/>
</Brand>
2.4.7 修改或删除 DOM 节点
- 演示案例
public class Book4 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException, TransformerException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("src/main/java/CH15_XML/PhoneInfo.xml");
NodeList list = doc.getElementsByTagName("Brand");
for (int i = 0; i < list.getLength(); i++) {
Element brandItem = ((Element) list.item(i));
String brandName = brandItem.getAttribute("name");
if ("三星".equals(brandName)) {
// ElementBrand.setAttribute("name", "OPPO");
brandItem.getParentNode().removeChild(brandItem);
// 先获取父节点,再删除子节点
}
}
TransformerFactory tff = TransformerFactory.newInstance();
Transformer tf = tff.newTransformer();
DOMSource domSource = new DOMSource(doc);
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StreamResult streamResult = new StreamResult(Files.newOutputStream(Paths.get("src/CH15_XML/PhoneInfo.xml")));
tf.transform(domSource, streamResult);
}
}
2.5 DOM4J 解析 XML 文档
2.5.1 DOM4J 常用 API
DOM4J 的主要接口都定义在 org.dom4j 包里
接口说明 |
---|
Node:为所有的 DOM4J 中的 XML 节点定义了多态行为 |
Branch:为能够包含子节点的节点定义了公共行为 |
Element:定义 XML 元素 |
Document:定义 XML 文档 |
Entity:定义 XML 实体 |
Attribute:定义了 XML 的属性 |
DocumentType:定义 XML DOCTYPE 声明 |
ProcessingInstruction:定义 XML 处理指令 |
CharacterData:标识接口,标识基于字符的节点 |
Text:定义 XML 文本节点 |
CDATA:定义了 XML CDATA 区域 |
Comment:定义了 XML 注释的行为 |
常用接口 | 常用方法 |
---|---|
Branch | Element elementById(String id) Element addElement(String name) boolean remove(Node node) |
Document | Element getRootElement() |
Element | Element element(String name) List elements() List elements(String tagname) Iterator elementIterator() Iterator elementIterator(String name) Attribute attribute(String name) Element addAttribute(String name, String value) String getText() |
2.5.2 DOM4J 读取收藏信息
- 演示案例
package CH15_XML;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.Iterator;
public class Book5 {
public static void main(String[] args) throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File("src/main/java/CH15_XML/PhoneInfo.xml"));
Element rootElement = document.getRootElement();
for (Iterator<Element> itBrand = rootElement.elementIterator(); itBrand.hasNext(); ) {
Element brand = itBrand.next();
System.out.println(brand.attributeValue("name"));
for (Iterator<Element> itType = brand.elementIterator(); itType.hasNext(); ) {
Element type = itType.next();
System.out.println("\t" + type.attributeValue("name"));
}
}
}
}
// 华为
// P90
// null
// 苹果
// iPhone Z
// iPhone ZL
// 三星
// NoteX