深入探究 XML:从基础到实践
1. XML 概述
XML(可扩展标记语言)是计算机领域的一项重要技术,被认为是自真空管发明以来计算领域的重大突破。它是一种以标准化方式存储和交换信息的方法,易于创建、检索,并且能够在不同类型的计算机系统或程序之间进行交换。当 XML 存储在文件中时,文件通常具有
.xml
扩展名。
1.1 标签
与 HTML 类似,XML 使用标签来标记数据。例如,下面是一段描述书籍的 XML 代码:
<Book>
<Title>Java All-In-One Desk Reference For Dummies</Title>
<Author>Lowe</Author>
</Book>
在这个例子中,
Book
是一个元素,它包含了关于一本书的信息。
Book
元素又包含了两个子元素:
Title
和
Author
。每个元素以开始标签开始,以结束标签结束,开始标签列出元素的名称,结束标签在元素名称前加上斜杠。元素的内容可以是文本数据,也可以是一个或多个额外的元素。如果一个元素包含其他元素,这些额外元素被称为子元素,包含它们的元素被称为父元素。XML 文档中的最高级元素称为根元素,一个格式正确的 XML 文档由一个根元素组成,根元素可以包含嵌套的元素。
例如,一个包含两部电影信息的 XML 文档可能如下所示:
<Movies>
<Movie>
<Title>It’s a Wonderful Life</Title>
<Year>1946</Year>
<Price>14.95</Price>
</Movie>
<Movie>
<Title>The Great Race</Title>
<Year>1965</Year>
<Price>12.95</Price>
</Movie>
</Movies>
这里,
Movies
是根元素,包含了两个
Movie
元素。
XML 和 HTML 有两个关键区别:
- HTML 中的标签用于指示数据的显示格式,而 XML 文档中的标签用于指示数据的含义。例如,HTML 中的
<B>
和
<I>
标签用于指示数据为粗体或斜体,而包含书籍信息的 XML 文档可能有
<Title>
和
<Author>
标签来提供书籍的标题和作者。
- HTML 文档中的标签是固定的,而在 XML 文档中,你可以自定义任何你需要的标签。例如,创建关于汽车的 XML 文档时,你可以使用
<Make>
、
<Model>
和
<Year>
标签;创建关于大学课程的 XML 文档时,你可以使用
<Course>
、
<Title>
、
<Instructor>
、
<Room>
和
<Schedule>
标签。
1.2 属性
除了使用子元素,你还可以使用属性为元素提供数据。属性是一个键值对,写在元素的开始标签内。例如,下面的
Movie
元素使用属性而不是子元素来记录年份:
<Movie year="1946">
<Title>It’s a Wonderful Life</Title>
<Price>14.95</Price>
</Movie>
是否使用属性或子元素很大程度上取决于个人偏好。许多 XML 纯粹主义者建议避免使用属性,或者仅将其用于标识数据,如识别号码或代码;而另一些人则建议自由使用属性。根据经验,适当使用一些属性不会有太大问题,但在大多数情况下,最好避免使用属性。
以下是一个
movies.xml
文件的示例,后续的程序将使用该文件:
<Movies>
<Movie year="1946">
<Title>It’s a Wonderful Life</Title>
<Price>14.95</Price>
</Movie>
<Movie year="1965">
<Title>The Great Race</Title>
<Price>12.95</Price>
</Movie>
<Movie year="1974">
<Title>Young Frankenstein</Title>
<Price>16.95</Price>
</Movie>
<Movie year="1975">
<Title>The Return of the Pink Panther</Title>
<Price>11.95</Price>
</Movie>
<Movie year="1977">
<Title>Star Wars</Title>
<Price>17.95</Price>
</Movie>
<Movie year="1987">
<Title>The Princess Bride</Title>
<Price>16.95</Price>
</Movie>
<Movie year="1989">
<Title>Glory</Title>
<Price>14.95</Price>
</Movie>
<Movie year="1995">
<Title>Apollo 13</Title>
<Price>19.95</Price>
</Movie>
<Movie year="1997">
<Title>The Game</Title>
<Price>14.95</Price>
</Movie>
<Movie year="2001">
<Title>The Fellowship of the Ring</Title>
<Price>19.95</Price>
</Movie>
</Movies>
2. 使用 DTD 定义 XML 结构
DTD(文档类型定义)用于明确指定 XML 文档中可以出现哪些元素以及这些元素的出现顺序。例如,一个关于电影的 XML 文档的 DTD 可能规定每个
Movie
元素必须有
Title
和
Price
子元素以及一个名为
year
的属性,同时规定根元素名为
Movies
,可以包含任意数量的
Movie
元素。
DTD 的主要目的是明确 XML 文档的结构,以便文档的使用者知道如何解释它。此外,DTD 还可以用于验证文档,确保文档没有任何结构错误。例如,如果创建一个
Movies
XML 文档,其中一部电影有两个标题,就可以使用 DTD 来检测这个错误。
可以将 XML 文档的 DTD 存储在与 XML 数据相同的文件中,但更常见的做法是将 DTD 存储在单独的文件中。这样,可以使用一个 DTD 来管理同一类型的多个 XML 文档的格式。为了指示包含 DTD 的文件的名称,需要在 XML 文档中添加
<!DOCTYPE>
标签。例如:
<!DOCTYPE Movies SYSTEM "movies.dtd">
这表明该 XML 文件是一个
Movies
文档,其 DTD 可以在
movies.dtd
文件中找到。应将此标签添加到
movies.xml
文件的开头,紧跟在
<?xml>
标签之后。
以下是
movies.xml
文件的 DTD 文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT Movies (Movie*)>
<!ELEMENT Movie (Title, Price)>
<!ATTLIST Movie year CDATA #REQUIRED>
<!ELEMENT Title (#PCDATA)>
<!ELEMENT Price (#PCDATA)>
DTD 中的每个
ELEMENT
标签定义了文档中可以出现的元素类型,并指示该元素类型的内容可以是什么。
ELEMENT
标签的一般形式为:
<!ELEMENT element (content)>
可以使用以下规则来表示内容:
| 内容 | 描述 |
| ---- | ---- |
|
element*
| 指定元素可以出现零次或多次 |
|
element+
| 指定元素可以出现一次或多次 |
|
element?
| 指定元素可以出现零次或一次 |
|
element1|element2
| 可以出现
element1
或
element2
|
|
element1, element2
|
element1
后面跟着
element2
|
|
#PCDATA
| 文本数据 |
|
ANY
| 允许任何子元素 |
|
EMPTY
| 不允许任何类型的子元素 |
ATTLIST
标签用于提供每个属性的名称,其一般形式为:
<!ATTLIST element attribute type default-value>
该标签的具体含义如下:
-
element
:指定属性可以出现在哪个元素的标签中。
-
attribute
:提供属性的名称。
-
type
:指定属性值可以是什么,类型可以是下表中的任何一项:
| 元素 | 属性值 |
| ---- | ---- |
|
CDATA
| 可以是任何字符串 |
|
(string1|string2...)
| 可以是列出的字符串之一 |
|
NMTOKEN
| 必须是一个名称标记,由字母和数字组成 |
|
NMTOKENS
| 必须是一个或多个由空格分隔的名称标记 |
|
ID
| 必须是唯一的名称标记,即文档中没有其他元素可以对该属性具有相同的值 |
|
IDREF
| 必须与文档中其他地方使用的
ID
值相同 |
|
IDREFS
| 是一个由空格分隔的
IDREF
值列表 |
-
default
:提供默认值,并指示属性是必需的还是可选的,默认值可以是下表中的任何一项:
| 默认值 | 是否可选 |
| ---- | ---- |
|
#REQUIRED
| 必需 |
|
#IMPLIED
| 可选 |
|
value
| 可选。如果省略属性,则使用此值 |
|
#FIXED value
| 可选。如果包含该属性,则必须是此值;如果省略,则默认使用此值 |
例如,
movies.dtd
中的
<!ATTLIST Movie year CDATA #REQUIRED>
声明表示
year
属性与
Movie
元素相关联,属性值可以是任何类型的数据,并且是必需的。
3. 两种处理 XML 的方式
在 Java 程序中,通常可以使用两种方法来处理 XML 文档:DOM(文档对象模型)和 SAX(简单 XML API)。
3.1 DOM
DOM 代表文档对象模型。其基本思想是将整个 XML 文档从文件读取到内存中,文档以树状结构的对象集合形式存储。然后可以根据需要处理树的元素(称为节点)。如果更改了任何节点,可以将文档写回文件。
3.2 SAX
SAX 代表简单 XML API。SAX 是一种只读的 XML 处理技术,允许从文件中读取 XML 文档的元素,并在元素出现时对其做出反应。由于 SAX 不需要一次性将整个 XML 文档存储在内存中,因此常用于处理非常大的 XML 文档。
在本文中,主要介绍使用 DOM 从 XML 文档中检索信息的基础知识。DOM 将 XML 文档在内存中表示为
Node
对象的树。例如,一个包含两个
Movie
元素的 XML 文档的简化 DOM 树如下:
graph TD
A[Document] --> B[Movies]
B --> C[Movie year=1946]
C --> D[Title]
D --> E[Text: It’s a Wonderful Life]
C --> F[Price]
F --> G[Text: 14.95]
B --> H[Movie year=1965]
H --> I[Title]
I --> J[Text: The Great Race]
H --> K[Price]
K --> L[Text: 12.95]
4. 读取 DOM 文档
在处理 DOM 文档之前,需要将 XML 文件中的文档读取到内存中。以下是一个完整的方法,该方法接受一个包含文件名的字符串作为参数,并返回一个文档对象:
private static Document getDocument(String name) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
factory.setValidating(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new InputSource(name));
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
4.1 创建文档构建器工厂
首先,调用
DocumentBuilderFactory
类的
newInstance
方法创建一个新的
DocumentBuilderFactory
对象。文档构建器工厂的作用是创建能够读取 XML 输入并在内存中创建 DOM 文档的文档构建器对象。由于
DocumentBuilderFactory
是一个抽象类,没有构造函数,因此使用
newInstance
静态方法根据系统配置确定要创建的实例类。
4.2 配置文档构建器工厂
可以对文档构建器工厂进行配置,使其按照所需的方式读取文档。以下三个语句配置了应用于该工厂对象创建的文档构建器的三个选项:
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
factory.setValidating(true);
-
setIgnoringComments(true):告诉文档构建器不要为 XML 文件中的注释创建节点。大多数 XML 文件不包含注释,如果包含,注释也不是文档所代表的数据的一部分,因此可以安全地忽略。设置此选项会自动忽略注释。 -
setIgnoringElementContentWhitespace(true):使文档构建器忽略任何不属于文本值的空白。如果不包含此选项,DOM 文档将包含表示空白的节点,这些空白节点只会使 DOM 文档更难处理,因此应始终设置此选项。 -
setValidating(true):告诉文档构建器如果 XML 指定了 DTD,则验证输入的 XML。验证输入可以大大简化程序,因为可以确定 DOM 文档符合 DTD 的要求。
4.3 创建文档构建器和文档
设置选项后,可以调用
newDocumentBuilder
方法创建一个文档构建器:
DocumentBuilder builder = factory.newDocumentBuilder();
最后,通过调用文档构建器的
parse
方法创建 DOM 文档。该方法接受一个
InputSource
对象作为参数。可以使用
InputSource
类的构造函数,该构造函数接受一个文件名参数并返回一个与该文件关联的输入源。因此,可以在一个语句中创建输入源、解析 XML 文件、创建 DOM 文档并将 DOM 文档返回给调用者:
return builder.parse(new InputSource(name));
需要注意的是,这些方法中的一些会抛出异常。为了简化示例,将所有异常捕获在一个
catch
子句中,并将异常消息打印到控制台。
使用
getDocument
方法,可以通过一条语句从文件创建 DOM 文档:
Document doc = getDocument("movies.xml");
同时,使用
getDocument
方法需要提供三个导入语句:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
DocumentBuilder
和
DocumentBuilderFactory
在
javax.xml.parsers
包中,
Document
在
org.w3c.dom
中,
InputSource
在
org.xml.sax
中。实际上,
DocumentBuilder
类的
parse
方法在构建 DOM 对象时使用了 SAX 来读取 XML 文件。
深入探究 XML:从基础到实践
5. 读取 DOM 节点
当将 DOM 文档加载到内存后,就可以轻松地从文档的节点中检索数据。DOM API 基于接口而非类,因此 DOM 文档的每个节点都由实现一个或多个 DOM 接口的对象表示。以下是需要了解的主要接口概述:
-
Document
:整个文档由实现
Document接口的对象表示。该接口中最常用的方法是getDocumentElement,它返回一个Element对象,表示文档的根节点。获取根节点后,就可以导航到文档中的其他节点以获取所需信息。 -
Node
:
Node接口表示 DOM 文档中的一个节点。该接口提供了所有节点共有的方法。表 1 列出了这些方法中最有用的部分,同时也列出了getNodeType方法可能返回的一些字段值。 -
Element
:
Element接口表示与 XML 文档中的元素对应的节点。Element扩展了Node,因此实现Element的任何对象也是一个Node。表 2 列出了该接口的一些更有用的方法。 -
Text
:元素的文本内容并不直接包含在元素本身中,而是存储在作为元素子节点的
Text节点中。Text接口有一些有趣的方法,但对于大多数应用程序,只需使用从Node接口继承的getNodeValue方法来检索文本节点存储的文本。 -
NodeList
:
NodeList是一个节点集合,由Node接口的getChildNodes方法或Element接口的getElementsByTagName方法等返回。NodeList只有两个方法:item(int i),返回指定索引处的节点;getLength(),返回列表中的项数。(与 Java 中的几乎所有其他索引一样,第一个节点的索引为 0,而不是 1)。
表 1:Node 接口
| 方法 | 描述 |
| ---- | ---- |
|
NodeList getChildNodes()
| 获取包含此节点所有子节点的
NodeList
对象 |
|
Node getFirstChild()
| 获取此节点的第一个子节点 |
|
Node getLastChild()
| 获取此节点的最后一个子节点 |
|
int getNodeType()
| 获取一个整数,指示节点的类型。值可以是表中列出的字段之一 |
|
String getNodeValue()
| 获取此节点的值(如果节点有值) |
|
Node getNextSibling()
| 获取下一个兄弟节点 |
|
Node getPrevSibling()
| 获取上一个兄弟节点 |
|
boolean hasChildNodes()
| 确定节点是否有任何子节点 |
| 字段 | 描述 |
|---|---|
ATTRIBUTE_NODE
| 节点是属性节点 |
CDATA_SECTION_NODE
| 节点包含内容数据 |
COMMENT_NODE
| 节点是注释 |
DOCUMENT_NODE
| 节点是文档节点 |
ELEMENT_NODE
| 节点是元素节点 |
TEXT_NODE
| 节点是文本节点 |
表 2:Element 接口
| 方法 | 描述 |
| ---- | ---- |
|
String getAttribute(String name)
| 获取指定属性的值 |
|
NodeList getElementsByTagName(String name)
| 获取包含此元素内所有具有指定名称的元素节点的
NodeList
对象 |
|
boolean hasAttribute(String name)
| 确定元素是否具有指定的属性 |
5.1 处理元素
假设在构建文档时使用 DTD 来验证 XML 文件,通常可以在文档中导航以获取所需信息,而无需使用
NodeList
对象。例如,以下是一个简单的例程,用于计算
movies.xml
文件(前面已给出示例)解析为名为
doc
的
Document
对象后,所有
Movie
元素的数量:
int count = 0;
Element root = doc.getDocumentElement();
Node movie = root.getFirstChild();
while (movie != null) {
count++;
movie = movie.getNextSibling();
}
System.out.println("There are " + count + " movies.");
此方法首先调用
getFirstChild
方法获取根元素的第一个子节点,然后使用每个子元素的
getNextSibling
方法获取也是根元素子节点的下一个元素。
如果运行包含这些代码行的程序,控制台将显示:
There are 10 movies.
这个程序只是对
Movie
元素进行计数,不做其他操作,但接下来将看到如何从
Movie
元素中提取数据。
另一种处理
movies.xml
文件中所有元素的方法是使用
getChildNodes
方法返回一个包含所有元素的
NodeList
对象,然后使用
for
循环逐个访问每个元素。例如,以下代码片段列出了每个元素的名称:
Element root = doc.getDocumentElement();
NodeList movies = root.getChildNodes();
for (int i = 0; i < movies.getLength(); i++) {
Node movie = movies.item(i);
System.out.println(movie.getNodeName());
}
这里,在
for
循环中使用
item
方法检索每个
Movie
元素。如果运行包含这些代码行的程序,控制台窗口将显示十行
Movie
。
5.2 获取属性值
要获取元素属性的值,可以调用
getAttribute
方法并将属性名称作为参数传递,该方法将返回属性的字符串值。如果需要,可以将此值转换为其他类型。需要注意的是,该值可能包含一些空白,因此应该使用
trim
方法去除多余的空白。
以下是一个示例,用于从
movies.xml
文件中的每部电影中获取
year
属性,并确定收藏中最古老电影的年份:
Element root = doc.getDocumentElement();
Element movie = (Element) root.getFirstChild();
int oldest = 9999;
while (movie != null) {
String s = movie.getAttribute("year");
int year = Integer.parseInt(s);
if (year < oldest) {
oldest = year;
}
movie = (Element) movie.getNextSibling();
}
System.out.println("The oldest movie in the file is from " + oldest + ".");
通过以下两行代码提取
year
属性:
String s = movie.getAttribute("year");
int year = Integer.parseInt(s);
第一行获取
year
属性的字符串值,第二行将其转换为
int
类型。
注意,此方法中进行了额外的类型转换。这是因为
movie
变量必须是
Element
类型,以便可以调用
getAttribute
方法。然而,
getNextSibling
方法返回的是一个
Node
,而不是
Element
。因此,除非先将节点强制转换为
Element
,否则编译器不允许将节点赋值给
movie
变量。
5.3 获取子元素值
可能会惊讶地发现,元素的文本内容并不直接存储在元素中,而是存储在类型为
Text
的子节点中。例如,考虑以下 XML:
<Title>The Princess Bride</Title>
这个元素在 XML 文档中会产生两个节点:一个名为
Title
的
Element
节点,以及一个包含文本
The Princess Bride
的
Text
节点。
因此,如果手头有一个
Title
元素,必须先获取
Text
元素,然后才能获取文本内容。例如:
Node textElement = titleElement.getFirstChild();
String title = textElement.getNodeValue();
如果希望代码更简洁,可以将其写成一条语句:
String title = titleElement.getFirstChild().getNodeValue();
如果觉得这种写法有点繁琐,并且在程序中经常需要这样做,可以编写一个辅助方法。例如:
private static String getTextValue(Node n) {
return n.getFirstChild().getNodeValue();
}
然后,可以通过调用
getTextValue
方法来获取元素的文本内容,如下所示:
String title = getTextValue(titleElement);
获取文本内容后,如果需要,可以将其解析为数字类型。
综上所述,XML 是一种强大的信息存储和交换技术,通过 DTD 可以定义其结构,使用 DOM 或 SAX 可以对其进行处理。通过本文介绍的方法,可以从 XML 文档中有效地读取和处理数据,实现各种应用需求。无论是小型 XML 文件还是大型数据集,都可以根据具体情况选择合适的处理方式,充分发挥 XML 的优势。
深入探究 XML:从基础到实践
超级会员免费看
472

被折叠的 条评论
为什么被折叠?



