Java XML教程
来源:http://d23xapp2.cn.ibm.com/developerWorks/education/xml/xmljava/tutorial/xmljava-1-1.html
第一章 入门介绍
关于本教程
在本教程中,我们将讨论如何使用一个 XML 解析器来:
处理一个 XML 文档
创建一个 XML 文档
操作一个 XML 文档
我们也将讨论一些有用而不为众人所知的 XML 解析器特性。 最重要的,我们所讨论的每个工具都可从 IBM 的 alphaWorks 站点 (www.alphaworks.ibm.com) 和其它网站免费获得。
未讨论的:
有些重要的编程概念并未在此介绍:
1 使用可视工具来构建 XML 应用
2 将一个 XML 文档从一种形式转换到另一种
3 为最终用户或其他进程创建接口,及对后端存储数据的接口
当您构建一个 XML 应用时,所有这些概念都是重要的。我们正在编制新的教程来讨论它们,因此请常光顾我们的网址!
XML 应用架构
一个 XML 应用通常是基于一个 XML 解析器而构建的。它为其用户提供了一个接口,以及对后端存储数据的一个接口。
本教程关注于编写使用 XML 解析器来操作 XML 文档的 Java
代码。如下边图片所示,本教程关注于中间那块。
第二章 解析器基础
基础
一个 XML 解析器是一段可以读入一个文档并分析其结构的代码。在本章节,我们将讨论如何使用一个 XML 解析器来读入一个 XML 文档。我们也将讨论不同类型的解析器以及您在何时使用它们。
本教程后面的章节将讨论您从解析器可以获得什么以及如何使用这些结果。
如何使用一个解析器
我们将在稍后的章节详细讨论它,但通常而言,您如下使用它:
1 创建一个解析器对象
2 将您的 XML 文档传递给解析器
3 处理结果
构建一个 XML 应用显然远远超出这些,但通常一个 XML 的应用将包含这些流程。
解析器种类
有不同的方法来划分解析器种类:
验证或非验证解析器
支持 Document Object Model (DOM) 的解析器
支持 Simple API for XML (SAX) 的解析器
用特定语言编写的解析器 (Java, C++, Perl 等)
验证或非验证解析器
如我们在第一个教程中所提及的,XML 文档如果使用一个 DTD 并符合 DTD 中的规则将被称为有效文档(valid document)。符合基本标记规则的 XML 文档被称为格式正确文档(well-formed document)。
XML 规范要求所有的解析器当其发现一个文档不是格式正确时要报错。验证(Validation)则是另一个问题了。验证解析器(Validating parser)在解析 XML 文档同时进行验证。非验证解析器(Non-validating parser) 忽略所有的验证错误。换而言之,如果一个 XML 文档是格式正确的时,一个非验证解析器并不关注文档是否符合其对应 DTD 所指定的规则(如果有的话)。
为何使用非验证解析器?
速度和效率。要一个 XML 解析器处理 DTD 并确保每个 XML 的元素符合在 DTD 中的规则需要相当大的开销。如果您确定一个 XML 文档是有效的(可能来自一个数据源),那就没有必要在次验证它了。
同样,有时您所需要的只是从一个文档中找出 XML 的标记。一旦您有了这些标记,您可以将数据从中提取出然后加以处理。如果这就是您所需要的,一个非验证解析器就是正确的选择。
Document Object Model (DOM)
文档对象模型(Document Object Model)是 World Wide Web Consortium(W3C) 的正式推荐。它定义了一个接口使得程序可以存取和更新 XML 文档的风格、结构和内容。支持 DOM 的 XML 解析器实现该接口。
该规范的第一版,DOM Level 1,可从 http://www.w3.org/TR/REC-DOM-Level-1 获得, 如果您愿意阅读规范的话。
DOM 解析器可提供什么
当您用一个 DOM 解析器来解析一个 XML 文档时,您将获得一个包含文档中所有元素的树结构。DOM 提供了不同的功能来检查文档的内容和结构。
关于标准
现在我们即将讨论开发 XML 应用了,我们也要关注 XML 的标准。正式而言,XML 是 MIT(麻省理工) 的商标和 World Wide Web Consortium (W3C)组织的产品。
XML 规范,W3C 的正式推荐,可从 www.w3.org/TR/REC-xml 下载。W3C 站点包含了 XML、DOM 以及一大堆 XML 相关标准的规范。
Simple API for XML (SAX)
SAX API 是另一种处理 XML 文档内容的方法。一个既成事实的标准,它由 David Megginson 和 XML-Dev 邮件列表其它成员所开发。
要查看完整的 SAX 标准,参见 www.megginson.com/SAX/。要参加 XML-Dev 邮件列表,发送邮件到 majordomo@ic.ac.uk 其中包含: subscribe xml-dev。
SAX 解析器可提供什么
当您使用 SAX 解析器来解析 XML 文档时,解析器在文档的不同处将产生事件。由您来决定对每个事件如何处理。
SAX 解析器会在以下情况下产生事件:在文档开始和结束时,在一个元素开始和结束时,或者它在一个元素中找到字符时,以及其它若干点。您可编写 Java 代码来处理每个事件,以及如何处理从解析器获得的信息。
何时使用 SAX?何时使用 DOM?
我们将在稍后的章节详细讨论这个问题,但通常而言,您在下列时候应该使用一个 DOM 解析器:
您需要十分了解文档的结构
您需要操作文档中的某些部分(例如,您可能想对某些元素排序)
您需要不止一次使用文档中的信息
当您只需要从一个 XML 文档中提取若干元素时,可使用 SAX 解析器。SAX 解析器在您没有大多数内存时、或者如果您只需要使用文档中的信息一次(而不是解析文档一次,而后要反复使用它)。
不同语言的 XML 解析器
在 Web 上使用的大多数语言都有其对应的 XML 解析器和库,包括 Java、C++、Perl 和 Python。下一页介绍了 IBM 或其它公司提供的解析器的链接。
本教程中绝大多数的示例是使用 IBM 的 XML4J 解析器。我们所讨论的所有代码使用标准的接口。在本教程的最后章节,我们将向您展现编写可使用不同解析器的代码是如何简单。
Java
IBM 的解析器,XML4J,可从 www.alphaWorks.ibm.com/tech/xml4j 获得。
James Clark 的解析器,XP,可从 www.jclark.com/xml/xp 获得。
Sun 的 XML 解析器可从 developer.java.sun.com/developer/products/xml/ (您必需成为 Java Developer Connection 的会员)下载。
DataChannel 的 XJParser 可从 xdev.datachannel.com/downloads/xjparser/ 获得。
C++
IBM 的 XML4C 解析器可从 www.alphaWorks.ibm.com/tech/xml4c 获得。
James Clark 的 C++ 解析器,expat,可从 www.jclark.com/xml/expat.html 获得。
Perl
有多种 Perl 语言的 XML 解析器。要获得更多信息,参见 www.perlxml.com/faq/perl-xml-faq.html。
Python
要获得更多 Python 语言的 XML 解析器,参见 www.python.org/topics/xml/。
总结
任何 XML 应用的核心是一个 XML 解析器。要处理一个 XML 文档,您的应用将创建一个 parser 对象,将一个 XML document 传递给它,然后处理从 parser 对象返回的结果。
我们讨论了不同类型的 XML 解析器,以及您为何选取其一。我们用不同方式来对解析器分类:
验证或非验证解析器
支持 Document Object Model (DOM) 的解析器
支持 Simple API for XML (SAX) 的解析器
用特定语言编写的解析器 (Java, C++, Perl 等)
在我们的下一章节,我们将探讨 DOM 解析器和如何使用它们。
第三章 DOM (Document Object Model)
Dom, dom, dom, dom, dom,
Doobie, doobie,
Dom, dom, dom, dom, dom...
DOM 是一个操作文档结构的通用接口。它设计的一个目标是为一个 DOM 兼容解析器所编写的 Java 代码应该可以使用其它任意 DOM 兼容的解析器而不需要修改代码。(我们稍后将展示这个。)
正如我们前面所提的,一个 DOM 解析器将以树形式返回您整个文档的结构。
示例代码
在我们继续以前,请您下载我们的示例 XML 应用程序。解开此文件 xmljava.zip,就可以了!(blueski:***或者查看本教程附录)
DOM 接口
DOM 定义了多个 Java 接口。下列是常用的:
Node: DOM 基本的数据类型。
Element: 您将最主要处理的对象是 Element。
Attr: 代表一个元素的属性。
Text: 一个 Element 或 Attr 的实际内容。
Document: 代表整个 XML 文档。一个 Document 对象通常也被称为一棵 DOM 树。
常用的 DOM 方法
当您使用 DOM 时,下列是您将常会使用的方法:
Document.getDocumentElement()
返回文档的根(root)元素。
Node.getFirstChild() and Node.getLastChild()
返回给定 Node 的第一个子女。
Node.getNextSibling() and Node.getPreviousSibling()
它将删除 DOM 树中一切内容,格式化您的硬盘,然后给您地址簿中每个人发送一个谩骂的邮件。(不是真的啦。这些方法返回下一个或前一个给定 Node 的同胞。)
Node.getAttribute(attrName)
对给定的 Node,返回给定名称的属性。例如,如果您要获得名为 id 属性 的对象,可调用 getAttribute("id")。
我们的第一个 DOM 应用!
介绍了很多概念,让我们继续吧。我们的第一个应用简单地读入一个 XML 文档并将其内容输出到标准输出。
在一个命令行窗口,运行下面的命令:
java domOne sonnet.xml
这个命令将载入我们的应用然后让它解析 sonnet.xml 文件。如果一切运行正常,您将看到 XML 文档的内容被输出到标准输出。
<?xml version="1.0"?>
<sonnet type="Shakespearean">
<author>
<last-name>Shakespeare</last-name>
<first-name>William</first-name>
<nationality>British</nationality>
<year-of-birth>1564</year-of-birth>
<year-of-death>1616</year-of-death>
</author>
<title>Sonnet 130</title>
<lines>
<line>My mistress?eyes are ...
domOne 剖析
domOne 的源码是非常直了的。我们创建一个新的类 domOne;它有两个方法,parseAndPrint 以及 printDOMTree。
在 main 方法中,我们处理命令行,创建一个 domOne 对象,然后将文件名传递给 domOne 对象。domOne 对象创建一个 parser 对象,解析文档,然后通过 printDOMTree 方法处理 DOM 树 (即 Document 对象)。
我们将详细研究每个步骤。
public class domOne
{
public void parseAndPrint(String uri)
...
public void printDOMTree(Node node)
...
public static void main(String argv[])
...
处理命令行
处理命令行的代码在左面显示。我们将检查用户是否在命令行上输入参数。如果没有,我们打印使用方法并推出;否则,我们假定命令行上第一个参数( Java 语言中的 argv[0] ) 是文档名。我们忽略用户可能输入的其它参数。
我们使用命令行参数来简化我们的示例。在大多数情况下,一个 XML 应用可能使用 servlet、Java Bean 和其它类型的组件一起使用;而用命令行参数并不是一个问题。
public static void main(String argv[])
{
if (argv.length == 0)
{
System.out.println("Usage: ... ");
...
System.exit(1);
}
domOne d1 = new domOne();
d1.parseAndPrint(argv[0]);
}
创建一个 domOne 对象
在我们的示例代码中,我们创建一个单独的类 domOne。要解析文件和打印结果,我们创建一个 domOne 类的实例,然后让我们刚创建的 domOne 对象来解析和打印 XML 文档。
我们为何这样处理?由于我们想要使用一个递归方法来遍历 DOM 树并打印出结果。我们无法用一个如 main 的静态方法来处理,因此我们创建一个单独的类来处理它。
public static void main(String argv[])
{
if (argv.length == 0)
{
System.out.println("Usage: ... ");
...
System.exit(1);
}
domOne d1 = new domOne();
d1.parseAndPrint(argv[0]);
}
创建一个 Parser 对象
现在我们已经让 domOne 的实例来解析和处理我们的 XML 文档,它的第一个处理是创建一个新的 Parser 对象。在此例中,我们将使用一个 DOMParser 对象,一个实现 DOM 接口的 Java 类。在 XML4J 包中还有其它 parser 对象,例如 SAXParser、ValidatingSAXParser 和 NonValidatingDOMParser。
注意我们将这段代码放在一个 try 模块中。parser 在某些情况下将抛出异常(exception),包括一个无效的 URI、找不到一个 DTD 或者一个 XML 文档不是有效的或格式错误。要很好地处理它,我们要捕获异常(exception)。
try
{
DOMParser parser = new DOMParser();
parser.parse(uri);
doc = parser.getDocument();
}
解析 XML 文档
解析文档只是简单的一行代码。当解析结束时,我们获得解析器生成的 Document 对象。
如果 Document 对象不是 null (如果解析过程出错它将是 null),我们将其传递给 printDOMTree 方法。
try
{
DOMParser parser = new DOMParser();
parser.parse(uri);
doc = parser.getDocument();
...
if (doc != null)
printDOMTree(doc);
}
处理 DOM 树
现在解析已经完成,我们将遍历 DOM 树。注意这段代码是递归的。对每个节点,我们处理其本身,然后我们对每个节点的子女递归地调用 printDOMTree 方法。递归调用如左所示。
要记住当有些 XML 文档非常大时,它们反而不会有太多层标记。以一个上海市的电话簿为例,可能有几百万条记录,但其标记可能不会超过几层。考虑到这个原因,递归算法的栈溢出不是一个问题。
public void printDOMTree(Node node)
{
int nodeType = Node.getNodeType();
switch (nodeType)
{
case DOCUMENT_NODE:
printDOMTree(((Document)node).
GetDocumentElement());
...
case ELEMENT_NODE:
...
NodeList children =
node.getChildNodes();
if (children != null)
{
for (int i = 0;
i < children.getLength();
i++)
printDOMTree(children.item(i));
}
很多 Node
如果您查看 sonnet.xml,有二十四个节点。您可能认为这意味着二十四个节点。然而,这不正确。在 sonnet.xml 中一共有 69 个节点;一个文档节点(document node), 23 个元素节点(element node)以及 45 个文本节点(text node)。我们运行 java domCounter sonnet.xml
就获得了下边所示的结果。
domCounter.java
这段代码解析一个 XML 文档,然后遍历 DOM 树来采集有关该文档的数据。当数据采集后将其输出到标准输出。
统计 sonnet.xml 的数据:
====================================
Document Nodes: 1
Element Nodes: 23
Entity Reference Nodes: 0
CDATA Sections: 0
Text Nodes: 45
Processing Instructions: 0
----------
Total: 69 Nodes
节点列表示例
对于下边的片断,
<sonnet type="Shakespearean">
<author>
<last-name>Shakespeare</last-name>
下列是从解析器返回的节点:
Document 节点
Element 节点对应于 <sonnet> 标记
一个 Text 节点对应于 <sonnet> 节点后的回车符以及 <author> 标记前的两个空格符
Element 节点对应于 <author> 标记
一个 Text 节点对应于 <author> 节点后的回车符以及 <last-name> 标记前的四个空格符
Element 节点对应于 <last-name> 标记
所有那些文本节点
如果您查看由解析器返回的所有节点列表,您将发现它们大多数是没用的。在每行开始的空格符组成其中包含可忽略的 Text 节点。
注意如果您将所有的节点放在一行上我们就不会得到这些无用的节点了。我们通过添加分行符和空格符来提高文档的可读性。
当您构建一个 XML 文档时不需要考虑可读性,就可省略分行符和空格符。这可使得您的文档更小,处理您的文档时也不需要构建那些无用的节点。
所有那些文本节点
如果您查看由解析器返回的所有节点列表,您将发现它们大多数是没用的。在每行开始的空格符组成其中包含可忽略的 Text 节点。
注意如果您将所有的节点放在一行上我们就不会得到这些无用的节点了。我们通过添加分行符和空格符来提高文档的可读性。
当您构建一个 XML 文档时不需要考虑可读性,就可省略分行符和空格符。这可使得您的文档更小,处理您的文档时也不需要构建那些无用的节点。
<sonnet type="Shakespearean">
<author>
<last-name>Shakespeare</last-name>
<first-name>William</first-name>
<nationality>British</nationality>
<year-of-birth>1564</year-of-birth>
<year-of-death>1616</year-of-death>
</author>
<title>Sonnet 130</title>
<lines>
<line>My mistress' eyes are nothing like the sun,</line>
一个 Text 节点对应于 "Shakespeare" 字符
如果您看到标记间所有的空格符,您可发现为何我们有那么多超出您想像的节点。
了解您的 Node
我们最后对处理在 DOM 树的 Node 要指出的是,我们在处理其之前要检查每个 Node 的类型。一些方法,例如 getAttributes,对一些特定的节点类型返回 null 值。如果您不检查节点类型,您将得到不正确的结果(最佳情况)和异常(最差情况)。
在此所介绍的 switch 语句常出现在使用 DOM 解析器的代码。
switch (nodeType)
{
case Node.DOCUMENT_NODE:
...
case Node.ELEMENT_NODE:
...
case Node.TEXT_NODE:
...
}
总结
不管您信不信,这就是我们使用 DOM 对象所要了解的所有内容。我们的 domOne 代码完成了下列工作:
创建一个 Parser 对象
将一个 XML 文档传递给 Parser 来解析
获得来自于 Parser 的 Document 对象然后加以检查。
在本教程最后一章,我们将讨论如何不需要 XML 原文件来构建一棵 DOM 树,并展示如何对一个 XML 文档中的元素排序。而那些都是基于我们这里所讨论的概念之上。
在我们继续那些更高级的应用前,我们将详细探讨 SAX API。我们同样将使用类似的示例,展现 SAX 和 DOM 的不同处。
|