【Java I/O 流】4 - 12 XML 入门
§4-12 XML 入门
4-12.1 XML 文件概述
可扩展标记语言(XML, EXtensible Markup Language),是由万维网联盟(W3C)发布的一个标准通用标记语言的子集。
XML 使用标签描述数据,标签有时也称元素。标签的名字支持自定义,一个 XML 文件中由许多标签组成,这体现了 XML 的可扩展性。
XML 文件用于存储数据并进行传输,也可用于作为应用程序的配置文件。以 XML 作为配置文件,它的可读性好,易于维护。
配置文件的类型有许多种,有 .txt
, .properties
和 .xml
等形式。这里以这三种形式为例,比较三者的优劣。
- 文本文件:无优点,不利于阅读。
- 属性文件:易于阅读,但无法存储成组出现的多组数据。
- XML 文件:易于阅读,可配置成组出现的数据,但解析复杂。
在实际生产中,一般根据实际情况选择后两种的其中一种形式存储数据。
4-12.2 XML 语法
一个 XML 文件必须遵循以下语法规则:
-
XML 文件的后缀名必须是
.xml
,文件名称可自定义。 -
XML 文件必须要在第一行写上文档声明:
<?xml version = "1.0" encoding = "UTF-8" standalone="yes" ?>
xml version
:XML 默认的版本号,必须字段,只能为1.0
。encoding
:该文件的编码。standalone
:可选字段,表示当前 XML 文件是否依赖其他 XML 文件,取值为 yes/no。 -
标签由一对尖括号和合法标识符组成(如
<name></name>
),文件中必须存在一个根标签,根标签有且只有一个。 -
其他标签必须写在根标签内。如:
<?xml version = "1.0" encoding = "UTF-8" ?> <students> <student></student> <student></student> </students>
上述文件中的
students
为根标签,有且只有一个。 -
标签必须成对出现,有开始标签与结束标签。如
<name></name>
。 -
特殊标签可不成对出现,但必须有结束标记。如
<br/>
。 -
标签中可定义属性,属性与标签名用空格(
<student id = "1"></student>
。 -
标签需要正确地嵌套。
<student id = "1"> <name>张三</name> <age>18</age> <scores> <chinese>100</chinese> <math>100</math> <english>100</english> <sports>100</sports> </scores> </student>
上述标签具有正确的嵌套关系。下列标签嵌套关系错误:
<student id = "1"> <name> </student> </name>
-
XML 文件允许注释,单行注释的格式为
<!-- COMMENTS -->
。 -
XML 文件中可以存在以下特殊字符:
语法 表示的字符 说明 <
<
小于 >
>
大于 &
&
与符号 '
'
单引号 "
"
双引号 存在这样特殊字符的原因是在 XML 文件中这些字符已具有特殊语法作用,为防止歧义,使用这些特殊标记表示这些字符。
-
XML 文件可存在 CDATA 区:
<![CDATA[ ... CONTENTS ...]]>
。CDATA 区内的内容被视作普通的文本,不具备任何特殊的语法含义。因此可有:
<name> <![CDATA[ <<<<>>>>>]]> </name>
在使用大量具有特殊含义的字符时,这样做更为方便。
4-12.3 约束 XML 文件
文档约束可用来限定 XML 文件中的标签和属性的书写规范。文档约束可以有效地保证文件中数据的规范性和完整性,强制要求书写人员按照文档约束的规定编写 XML 文件。
文档约束的方法有两种:DTD 约束和 Schema 约束。
-
DTD 约束:使用
.dtd
文件约束 XML 文档。语法格式:
<!ELEMENT elementName (subElement+)> <!ELEMENT elementName (subElement1, subElement2, subElement3, ...)> <!ELEMENT element1 (#PCDATA)> <!ELEMENT element2 (#PCDATA)> <!ELEMENT element3 (#PCDATA)> <!ATTLIST elementName attributeName attributeType attributeValue>
-
声明元素使用
<!ELEMENT elementName elementType>
,其中,elementType
允许以下内容:EMPTY
:表示标签体为空;ANY
:表示标签体可为空也可不为空;PCDATA
:表示该元素的内容为字符串;
-
标签内的子标签使用
()
定义,多个子标签用,
分隔。 -
至少出现一次的子标签使用
+
注明,出现零次或多次用*
注明,出现零次或一次用?
注明。 -
声明属性使用
<!ATTLIST elementName attributeName attributeType attributeRestriction>
。其中属性类型
attributeType
支持:CDATA
:属性值为字符数据;(en1|en2|...)
:属性值为枚举列表中的一个值;ID
:值为唯一的 ID;IDREF
:值为另外一元素的 ID;IDREFS
:值为其他 ID 的列表;NMTOKEN
:值为合法的 XML 名称;NMTOKENS
:值为合法的 XML 名称的列表;ENTITY
:值为一个实体;ENTITIES
:值是一个实体列表;NOTATION
:此值为富豪的名称;xml:
:值为一个预定义的 XML 值;
属性约束
attributeRestriction
支持:value
:属性的某个具体的默认值value
;#REQUIRED
:属性值是必需的;#IMPLIED
:属性不是必需的;#FIXED value
:属性值固定为value
;
注意,DTD 约束不能限制数据类型。
编写 XML 文件时,有以下方式导入 DTD 约束:
- 引入本地 DTD:
<!DOCTYPE rootElementName SYSTEM 'DTDFilePath'>
; - 在 XML 文件内部导入:
<!DOCTYPE rootElementName [DTDFileContent]>
; - 引入网络 DTD:
<!DOCTYPE rootElementName PUBLIC "DTDFileName" "DTDFileURL"
;
-
-
Schema 约束:Schema 可以约束具体的数据类型,约束能力更强。Schema 本身也是一个 XML 文件,本身也受到其他约束文件约束,编写更严谨。
Schema 约束文件示例:Schema 文件后缀名为
.xsd
。<?xml version="1.0" encoding="UTF-8"?> <!-- XML 文件声明,Schema 本身也是一个 XML 文件,这个字段是必须的 --> <!-- xs:schema 是 Schema 文件的根标签,标签具有三个属性字段 --> <!-- xmlns:xs Schema 文件的顶级约束,具有固定值,该文件约束了 Schema 文件 --> <!-- targetNamespace 是该 Schema 文件被引用时所需的命名空间,通常为域名,该字段值可自定义 --> <!-- elementFormDefault 表示文件是否结构良好,固定为 qualified --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.zebt.com/" elementFormDefault="qualified"> <!-- xs:element 表示元素声明,后接属性 name 声明元素的名称 --> <!-- 此处声明的标签为所约束的 XML 文件的根标签 --> <xs:element name='persons'> <!-- xs:complexType 表示复合类型标签,该标签下有其他的标签 --> <xs:complexType> <!-- xs:sequence 表示序列标签,标签中的子标签必须按照指定顺序排列 --> <!-- 属性 maxOccurs 指定标签最大出现次数,可为整型或 unbounded(无界) ,未声明默认为 1 --> <xs:sequence maxOccurs='unbounded'> <xs:element name='person'> <xs:complexType> <xs:sequence> <!-- xs:element 后可有属性字段 type 定义标签内容的数据类型 --> <xs:element name='name' type='xs:string'/> <xs:element name='age' type='xs:int'/> <xs:element name='gender' type='xs:string'/> </xs:sequence> <!-- 定义属性,必须定义在复合类型中 --> <!-- name 指定属性名称,type 指定属性值数据类型,use 指定属性使用 ---> <!-- use 支持 required(必须)、 optional(可选) --> <xs:attribute name="id" type="xs:int" use="required"></xs:attribute> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
向 XML 文件中引入 Schema 约束:
<?xml version="1.0" encoding="UTF-8" ?> <!-- xmlns:xsi 为防止重名,第一个加上 :xsi 表示顶级约束下的一个实例文件 --> <!-- xmlns 表示被那个 Schema 文件约束,填入对应约束文件的 targetNamespace 字段 --> <!-- xsi:schemaLocation 指定指定名称空间下的约束文件路径 --> <person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="targetNamespace" xsi:schemaLocation="namespace fileLocation"> ... </person>
有一种更简单的引入方法,只需填写第二行的
xmlns
并填入指定目标名称空间即可。<?xml version="1.0" encoding="UTF-8" ?> <person xmlns="targetNamespace"> ... </person>
4-12.4 XML 文件解析技术
解析 XML 文件的本质实际上就是将 XML 文件中所存储的静态数据读取到内存中,并获取文件中存储的数据。
解析 XML 文件的方式主要有 SAX 解析和 DOM 解析。
- SAX 解析(Simple API for XML Parsing):SAX 解析方式不会将整个 XML 文件加载到内存中,而是逐行扫描。这种方式只能读取 XML 文件,而不能修改 XML 文件。适用于解析较大的 XML 文件。
- DOM 解析(Document Object Model):DOM 解析读取整个 XML 文件到内存中并形成一个树形结构。通过树形结构解析 XML 文件,因此不仅可以读取,还可以修改。但不适合较大的 XML 文件。
实际开发中,XML 的体积不会过大,且现代计算机的性能足够强大,现如今都采用 DOM 解析方式解析 XML 文件。
常见的 DOM 解析工具有:
工具 | 说明 |
---|---|
JAXP | 由 Sun 提供的一套解析 XML 的 API。Java 原生支持,但使用不便。 |
JDOM | 开源项目。基于树形结构,使用纯 Java 技术实现针对 XML 文件的解析、生成、序列化等多种操作。 |
dom4j | JDOM 的升级品,用于读写 XML 文件。性能优异、功能强大、易于使用。 |
jsoup | 功能强大的 XML 解析开发包,但更常用于解析 HTML 文件。 |
XmlPullParser | 适用于 Android 的 XML 解析工具。 |
本章使用 Dom4J 解析 XML 文件。Dom4J 结合 SAX 和 DOM 技术,读取整个 XML 文档并返回包含整个文档树形结构的 Document
对象。Document
对象中的元素(标签)由 Element
记录,每个 Element
对象中可能还含有属性对象 Attribute
以及文本内容对象 Text
。Element
, Attribute
和 Text
都有一个共同的父接口 Node
。
<?xml version="1.0" encoding="UTF-8" ?>
<students> <!-- 这是一个标签 Element -->
<student id="1"> <!-- 标签中的 id 是一个属性 Attribute -->
<name>张三</name> <!-- 标签中的文本”张三“是元素的文本 -->
<age>23</age>
<gender>男性</gender>
</student>
</students>
4-12.5 使用 Dom4J 解析 XML 文件
要解析 XML 文件,我们需要获取一个 SAX 阅读器对象。
构造方法 | 描述 |
---|---|
SAXReader() |
返回一个 SAX 阅读器对象 |
通过 SAX 阅读器对象,读取全文,获得对应的文档对象。
方法 | 描述 |
---|---|
Document read(File file) Document read(URL url) |
从给定路径读取 XML 文档并返回文档对象 |
有了文档对象后,就有了整个 XML 文档的树形数据结构。
方法所属类 | 方法 | 描述 |
---|---|---|
Document |
Element getRootElement() |
获取文档的根元素 |
Element |
Iterator<Attribute> attributeIterator() |
返回该元素的属性迭代器 |
Element |
List<Attribute> attributes() |
返回该元素的属性列表 |
Element |
String attributeValue(String name) |
返回元素指定名称属性的值 |
Element |
Element element(String name) |
返回该元素中首次出现的带有指定名称的子元素 |
Element |
Iterator<Element> elementIterator() |
返回该元素下所有子元素的迭代器 |
Element |
List<Element> elements() |
返回该元素下含有的所有子元素列表 |
Element |
List<Element> elements(String name) |
返回元素下所有含指定名字的子元素列表 |
Element |
String elementText(String name) |
返回指定子元素的文本值 |
Node |
String getName() |
返回结点的名称 |
Node |
Element getParent() |
获取元素的父元素 |
Node |
String getText() |
返回元素的文本值 |
示例:逐层解析。
public class ParseXMLDemo {
public static void main(String[] args) throws DocumentException {
// 获取解析器并得到文档对象
SAXReader reader = new SAXReader();
Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml"));
// 获取根元素
Element root = document.getRootElement();
// 迭代元素
for (Iterator<Element> persons = root.elementIterator(); persons.hasNext();) {
Element person = persons.next();
System.out.println("id: " + person.attribute("id").getText());
for (Iterator<Element> it = person.elementIterator(); it.hasNext();) {
Element element = it.next();
System.out.println(element.getName() + ": " + element.getText());
}
System.out.println();
}
}
}
4-12.6 Xpath 和路径检索
Xpath 在解析 XML 方面提供了一种新的路径思想,这样做更优雅,更高效。XPath 使用路径表达式来定位 XML 文档中的元素结点或属性结点。
XPath 依赖于 dom4j 的技术,通过路径检索结点。要使用 XPath 路径解析,需要同时导入 dom4j 以及 jaxen(XPath)的 jar
包。
使用:通过 dom4j 的 SAXReader
获取 Document
对象,利用 XPath 提供的 API,结合 XML 语法完成选取 XML 文档元素结点解析。
获取 SAXReader
对象:SAXReader
的构造器
构造器 | 描述 |
---|---|
SAXReader() |
获取读取器对象 |
SAXReader
的解析相关 API:
方法 | 描述 |
---|---|
Document read(File file) |
从指定文件读取 XML 文件 |
Document read(InputStream in) |
从指定字节流读取 XML 文件 |
Document read(Reader reader) |
从指定字符流读取 XML 文件 |
Document read(URL url) |
从指定 URL 读取 XML 文件 |
Document
对象中与标签(元素)有关的 API:
方法 | 描述 |
---|---|
Element getRootElement() |
返回文档的根元素 |
元素相关 API:
方法 | 描述 |
---|---|
String getText() |
返回元素的文本 |
String getTextTrim() |
返回元素的文本 |
Document
对象中与 XPath
有关的 API:
方法 | 描述 |
---|---|
Node selectSingleNode(String xpathExpression) |
获取符合表达式的唯一元素 |
List<Node> selectNodes(String xpathExpression) |
获取符合表达式的元素集合 |
其中,方法中的形参 exp
指的是元素的路径。XPath 支持以下方式的路径检索:
-
绝对路径。从根结点开始,逐级深入,直到目标结点为止。格式:
/rootNode/subNode/.../targetNode
;示例:
SAXReader reader = new SAXReader(); Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml")); List<Node> nodes = document.selectNodes("/persons/person/name"); for (Node node : nodes) { System.out.println(node.getName()); System.out.println(node.getText()); }
-
相对路径。基于当前结点位置向下逐级查找。格式:
./subNode/.../targetNode
;示例:
SAXReader reader = new SAXReader(); Document document = reader.read(new File("JavaSE\\files\\xmls\\personnel.xml")); // 获取根结点 Element element = document.getRootElement(); // 从根结点向下查找 List<Node> nodes = element.selectNodes("./person/name"); for (Node node : nodes) { System.out.println(node.getText()); }
-
全文检索。全文或基于某个给定的范围检索元素。格式
//node
(可多次使用)。//
的意思是,在给定的元素范围内找到所有指定的元素。如//name
,这会在全文范围内找到名为name
的元素,无论其位置。//persons//name
的意思是在persons
标签内查找名为name
的元素,无论其位置(只要满足name
是persons
的父级标签)。而
/
表示的是明确的逐级关系。如persons/person/name
,它表示的是persons
标签下的子标签person
,继续逐级向下查找,还有person
标签下的子标签name
。//
和/
可以混用。如//students/student//math
表示的是全文检索students
标签下的student
标签,再在student
标签下检索名为math
的标签,math
标签可以不是student
的直接子标签。混用时,标签之间也具有明确的层级关系。 -
属性查找。在全文中查找属性,或带有指定属性值的元素。
//@AttributeName
:查找属性对象,无论哪个元素,只要带有指定属性的元素都满足条件;//element[@AttributeName]
:查找带有指定属性的元素,全文搜索指定的元素和属性;//element[@Attribute='value']
:查找带有指定属性和值的元素,全文搜索指定元素名和属性名,并且属性值相等。