现在的位置: 首页 > 综合 > 正文

利用Java技术进行XML编程(续)

2013年05月05日 ⁄ 综合 ⁄ 共 12255字 ⁄ 字号 评论关闭
 

主菜单(下部分)

一、简介
二、解析器基础
三、文档对象模型(DOM)
四、Simple API for XML(SAX)
五、JDOM
六、结束语

 

-----------------------------------------------------------------------------------------------------------

Chapter 4 Simple API for XML(SAX)

 

概述

 

尽管 DOM 是由 W3C 定义的,Web 上的讨论组还是提出了一些问题。其中包括:

  • DOM 在内存中建立 XML 文档树。如果文档非常大,DOM 树可能需要很大的内存。
  • DOM 树包括许多对象表示 XML 源文档的内容。如果只需要文档中的少量信息,创建所有这些对象是一种浪费。
  • DOM 解析器必须在代码访问之前建立整个 DOM 树。如果解析非常大的 XML 文档,在等待解析器完成之前会有明显的延迟。

为了解决上述问题,在 David Megginson 的领导下定义了 SAX API。该标准是集体努力的结果,事实上被所有的 XML 解析器支持。

 

SAX 是如何工作的

SAX 是一种 推式 API:您创建一个 SAX 解析器,解析器在发现 XML 文档中的内容时告诉您(把事件推给您)。具体来讲,SAX 是通过这样解决上述问题的:

  • SAX 不建立 XML 文档的内存树。SAX 解析器在发现 XML 文档中的事物时发出事件。如何(或者是否)保存那些数据取决于您。
  • SAX 解析器不创建任何对象。如果需要您可以建立对象,但这是您的事情,与解析器无关。
  • SAX 立即发出事件,不需要等待解析器读完文档。

SAX 事件

 

SAX API 定义了许多事件。您的任务是编写 Java 代码对这些事件作出响应。您很可能要在应用程序中使用 SAX 帮助器类。如果使用帮助器类,您就只需为关心的少量事件编写事件处理程序,让帮助器类完成其他的工作。如果不处理某个事件,解析器将丢弃该事件,因此不必担心内存使用、不必要的对象以及其他使用 DOM 解析器所担心的问题。

要记住,SAX 事件是 无状态的。换句话说,观察单个的 SAX 事件不能说明其中的内容。如果您需要了解 <lastName> 元素内 <author> 元素中出现的一段文本,连续记录解析器所发现的元素是您的工作。所有的 SAX 事件只能告诉您,“这里是一些文本”。指出该文本属于哪个元素要由您自己完成。

 

一些常用的 SAX 事件

在基于 SAX 的应用程序中,多数时候您都要处理五种基本的事件。这里将讨论这五种事件,并在后面看一看错误处理事件。

startDocument()
告诉您解析器发现了文档的开始。该事件没有传递任何信息,只是告诉您解析器开始扫描文档。
endDocument()
告诉您解析器发现了文档尾。
startElement(...)
告诉您解析器发现了一个起始标签。该事件告诉您元素的名称、该元素所有属性的名称和值,还会告诉您一些名称空间的信息。
characters(...)
告诉您解析器发现了一些文本。您得到一个字符数组、该数组的偏移量和一个长度变量,有这三个变量您就可以访问解析器所发现的文本。
endElement(...)
告诉您解析器发现了一个结束标签。该事件告诉您元素的名称,以及相关的名称空间信息。

startElement()characters()endElement() 是最重要的事件,后面将进一步讨论这三个事件。(startDocumentendDocument 只是告诉您解析何时开始和结束。)所有这些事件都属于 ContentHandler 接口,定义的其他 SAX 接口处理错误、实体及其他很少使用的内容。

 

startElement() 事件

startElement() 事件告诉您 SAX 解析器发现了一个元素的起始标签。该事件有四个参数:

String uri
名称空间 URI。在这些例子中,没有一个 XML 文档使用名称空间,因此该参数是一个空字符串。
String localName
该元素的非限定名。
String qualifiedName
元素的限定名,即名称空间前缀和元素本地名称的组合。
org.xml.sax.Attributes attributes
包含该元素所有属性的一个对象。该对象提供了几种方法获取属性的名称和值,以及该元素的属性个数。

如果您的 XML 应用程序查找某个元素的内容,startElement() 事件可以告诉您该元素何时开始。

注意:处理名称空间需要能识别名称空间的解析器。默认情况下, SAXParserFactoryDocumentBuilderFactory 对象创建的解析器不能识别名称空间。我将在以后的高级 XML 编程教程中详细讨论名称空间。

 

characters() 事件

characters() 事件包含解析器在源文件中发现的字符。本着最小化内存占用的精神,这个事件包含一个字符数组,这与 Java String 对象相比要轻型得多。以下是 characters() 事件的参数:

char[] characters
解析器所发现的字符数组。startlength 参数表示数组的哪一部分生成了该事件。
int start
属于该事件的 characters 数组中的一个字符的索引号。
int length
该事件中字符的的个数。

如果您的 XML 应用程序需要存储特定元素的内容,可以把存储那些内容的代码放在 characters() 事件处理程序中。

注意:除了与该事件有关的字符之外,SAX 标准没有规定 characters 数组的其他内容。有可能通过观察字符数组了解以前或以后的事件的细节,但这样做不可取。即使给定的解析器在当前版本中提供这些额外的字符,将来的版本也完全可能改变处理当前事件之外的字符的方式。

要点:看一看 startlength 所规定的范围之外的内容,这种想法可能是诱人的,但是 可行。

 

endElement() 事件

 

endElement() 事件告诉您解析器发现了某个元素的结束标签。它有三个参数:

String uri
名称空间 URI。
String localName
元素的非限定名。
String qualifiedName
元素的限定名,即名称空间前缀与元素本地名的组合。

对该事件的典型响应是改变 XML 应用程序中的状态信息。

 

第一个 SAX 应用程序

现在您已经看到了最常用的 SAX 事件。我将给出一个简单的 SAX 应用程序。这个应用程序 SaxOne.java 和您的第一个 DOM 应用程序 DomOne.java 类似。这个 SAX 应用程序将把事件的内容写到控制台中,因此运行 java SaxOne sonnet.xml 的结果将和前面的 DomOne 运行结果相同。

代码需要完成三项工作。基本上与 DomOne 的四项任务相同:

  • 扫描命令行得到 XML 文件名称(或者 URI)。
  • 创建解析器对象。
  • 告诉解析器对象解析命令行中指定的文件,并要求它把生成的所有 SAX 事件发送给您的代码。

对于 DomOne,您有四项任务因为在解析完成后要处理 DOM 树;但这里使用 SAX,处理是在解析的过程中完成的。当解析完成时,您也就经历了所有的 SAX 事件。

接下来将详细讨论这三个任务。

 

第 1 步:扫描命令行

DomOne 一样,只需要扫描命令行查找一个参数。如果没有参数,则打印错误消息并退出。否则假定第一个参数是 XML 文件名或者 URI:

public static void main(String argv[]) 
{
  if (argv.length == 0 ||
      (argv.length == 1 && argv[0].equals("-help")))
  {
    // Print an error message and exit...
  }
  SaxOne s1 = new SaxOne();
  s1.parseURI(argv[0]);
}
第 2 步:创建一个解析器对象
 

下一项任务是创建一个解析器对象。我们将使用 JAXP 的 SAXParserFactory API 创建一个 SAXParser

public void parseURI(String uri)
{
  try
  { 
    SAXParserFactory spf = SAXParserFactory.newInstance();
    SAXParser sp = spf.newSAXParser();
    . . .

这就是您需要做的全部工作。您的最后一步是让 SAXParser 实际解析文件。

(如果您没有看过关于 JAXP 的简要讨论,请参阅本教程中 DOM 部分的 关于 JAXP 的简单说明。)

 

第 3 步:解析文件并处理所有的事件

现在,您已经创建了解析器对象,还需要让它解析文件。这是通过 parse() 方法实现的:

    . . .
    sp.parse(uri, this);
  }
  catch (Exception e)
  {
    System.err.println(e);
  }
}

注意,parse() 方法有两个参数。第一个是 XML 文档的 URI,第二个则是实现 SAX 事件处理程序的一个对象。对于 SaxOne 而言,您扩展了 SAX DefaultHandler 接口:

public class SaxOne
  extends DefaultHandler 
					

DefaultHandler 实现了几个事件处理程序。这些实现什么也不做,就是说您的代码必须实现所关心的事件处理程序。

注意:上面的异常处理是很随意的,作为一个练习,您可以处理任何特定的异常,如 SAXExceptionjava.io.IOException

 

实现事件处理程序

当您告诉 SAXParser 解析文件时,您声明 SaxOne 扩展了 DefaultHandler 接口。需要说明的最后一点是,SaxOne 如何实现了该接口中的一些事件处理程序。DefaultHandler 接口的一个主要优点是,您不必实现所有的事件处理程序。DefaultHandler 实现了所有事件处理程序,您只需要实现所关心的那些事件处理程序。

首先看一看 startDocument() 事件的处理:

public void startDocument()
{
  System.out.println("<?xml version=/"1.0/"?>");
} 

显然这是一个骗局:无论原始 XML 文档中是否有都输出一个基本 XML 声明。目前的基本 SAX API 不返回 XML 声明的细节,尽管一个扩展 API(SAX2 Version 1.1 中定义)已经提出要返回这个信息。

然后是 startElement() 的处理:

public void startElement(String namespaceURI, String localName, 
                         String rawName, Attributes attrs) 
{
  System.out.print("<");
  System.out.print(rawName);
  if (attrs != null)
  {
    int len = attrs.getLength();
    for (int i = 0; i < len; i++)
    {
      System.out.print(" ");
      System.out.print(attrs.getQName(i));
      System.out.print("=/"");
      System.out.print(attrs.getValue(i));
      System.out.print("/"");
    }
  }
  System.out.print(">");
} 

打印元素的名称,然后打印该元素的全部属性。完成后打印右尖括号(>)。如前所述,对于空元素(比如 XHTML 中的 <hr/> 元素),您得到一个 startElement() 事件,然后是一个 endElement() 事件。您无从知道源文档中的编码是 <hr/> 还是 <hr></hr>

更多的事件处理

下一个重要的处理程序是 characters() 处理程序:

public void characters(char ch[], int start, int length) 
{
  System.out.print(new String(ch, start, length));
}

因为只是将 XML 文档打印在控制台上,只需要将和本事件有关的字符数据部分打印出来。

下面要说明的两个处理程序是 endElement()endDocument()

public void endElement(String namespaceURI, String localName, 
                       String rawName) 
{
  System.out.print("</");
  System.out.print(rawName);
  System.out.print(">");
}

public void endDocument() 
{
  
}

遇到 endElement() 事件时只需要输出结束标签。对于 endDocument() 事件则什么也不做,为了完整起见这儿也列了出来。因为 DefaultHandler 接口默认忽略 endDocument() 事件,去掉这些代码对结果没有影响。

下面看一看 SAX 应用程序中的错误处理。

错误处理

SAX 定义了 ErrorHandler 接口,它是 DefaultHandler 实现的接口之一。ErrorHandler 包括三个方法:warningerrorfatalError

warning 对应着警告,XML 规范没有定义为错误或致命错误的一种情况。error 被称为与错误事件对应,比如 XML 规范把违反有效性规则定义成一种错误。我还没有讨论有效性检查,不过如果一个十四行诗不包含 14 个 <line> 元素应该是一种错误。最后 XML 规范也定义了 fatalError,没有用引号括起来的属性值就是一种致命错误。

ErrorHandler 定义的三个事件是:

warning()
响应 XML 规范定义的警告发出。
error()
响应 XML 规范认为是一种错误的情况发出。
fatalError()
响应 XML 规范(您猜得到)定义的致命错误发出。

警告和错误通常与实体和 DTD(这里没有讨论) 这样的事物有关,而致命错误往往是违反了基本的 XML 语法(比如没有正确嵌套的标签)。以下是这三个错误处理程序的语法:

public void warning(SAXParseException ex) 
{
  System.err.println("[Warning] "+
                     getLocationString(ex)+": "+
                     ex.getMessage());
}

public void error(SAXParseException ex) 
{
  System.err.println("[Error] "+
                     getLocationString(ex)+": "+
                     ex.getMessage());
}

public void fatalError(SAXParseException ex) 
  throws SAXException 
{
  System.err.println("[Fatal Error] "+
                     getLocationString(ex)+": "+
                     ex.getMessage());
  throw ex;
}

上述处理程序使用了一个名为 getLocationString 的私有方法提供错误的更多细节。SAXParseException 类定义了 getLineNumber()getColumnNumber() 这些方法提供出现错误的行号和列号。getLocationString 只是把这些信息格式化成有用的字符串,把这些代码放在一个单独的方法中意味着您不需要在每个错误处理程序中包括这些代码。详情参阅

运行 SaxOne

运行 SaxOne 非常简单。首先,要保证您已经按照 设置机器 所述正确地设置了 XML 解析器和示例代码。然后只需要输入 java SaxOne sonnet.xml 就可以了。您将看到如下的结果:

C:/xml-prog-java>java SaxOne sonnet.xml
<?xml version="1.0" ?>
<sonnet type="Shakespearean">
  <author>
    <lastName>Shakespeare</lastName>
    <firstName>William</firstName>
    <nationality>British</nationality>
    <yearOfBirth>1564</yearOfBirth>
    <yearOfDeath>1616</yearOfDeath>
  </author>
  <title>Sonnet 130</title>
  <lines>
    <line>My mistress' eyes are nothing like the sun,</line>
    <line>Coral is far more red than her lips red.</line>
    <line>If snow be white, why then her breasts are dun,</line>
    <line>If hairs be wires, black wires grow on her head.</line>
    . . .
  </lines>
</sonnet>

完整的代码清单请参阅

 

处理 SAX 应用程序中的空格

您可能注意到运行 SaxOne 可以得到格式非常好的 XML。这是因为您保留了原始 XML 文档中的所有空格节点。SaxOne 实现了 ignorableWhitespace() 方法:

public void ignorableWhitespace(char ch[], int start, int length) 
{
  characters(ch, start, length);
}

该方法所要做的就是用同样的参数(ignorableWhitespace()characters() 的签名相同)调用 characters() 事件。如果您希望保留空格,只需要从代码中去掉 ignorableWhitespace() 处理程序。以下是 SaxTwo 应用程序的运行结果:

C:/xml-prog-java>java SaxTwo sonnet.xml
<?xml version="1.0" ?>
<sonnet type="Shakespearean"><author><lastName>Shakespeare
</lastName><firstName>William</firstName><nationality>Brit
ish</nationality><yearOfBirth>1564</yearOfBirth><yearOfDea
th>1616</yearOfDeath></author><title>Sonnet 130</title><li
nes><line>My mistress' eyes are nothing like the sun,</lin
e><line>Coral is far more red than her lips red.</line><lin
e>If snow be white, why then her breasts are dun,</line><l
. . .

SaxTwo 的完整源代码请参阅

 

---------------------------------------------------------------------

Chapter 5 JDOM

 

概述

针对文档对象模型的复杂性,人们提出了另外一种解决方案,即 JDOM。它由 Brett McLaughlin 和 Jason 所创建,使用 80-20 法则为最常用的 80% 的 XML 处理功能提供一种简单的 API。JDOM 并没有尝试替代 DOM,目前还只能用于 Java 语言。

在这一章中您将看到,JDOM 是一些常见的任务变得极其简单。解析很简单、向解析后的 XML 文档中增加条目很简单,把解析后的 XML 文档保存成 XML 文件也很简单。

关于 80-20 法则 的注释:在软件行业中,它意味着软件 80% 的功能来自 20% 的代码。JDOM 的目标是编写那个 20% 并提供一种简单的 API。意大利经济学家 Vilfredo Pareto 1906 年首次表述了 80-20 法则,他发现意大利 80% 的土地被 20% 的人所有。20 世纪 30 年代质量大师 Dr. Joseph Juran 概念化了 Pareto 原理(即 80-20 法则),后来许多其他学科的科学家注意到事物的 20% 通常产生 80% 的结果。如果您希望通过更多的细节给您的朋友留下关于 80-20 法则的深刻印象,请访问 www.juran.com/search.cfm 并搜索“Pareto”。

 

JDOM 的目标

“没有任何理由使处理 XML 的 Java API 非常复杂、微妙、不够直观,或者说成为一个讨厌的家伙。”——引自 JDOM Web 站点(请参阅 参考资料)。使用 JDOM,XML 文档就像是 Java 对象,和使用 Java 集合一样。我将举出两个 JDOM 应用程序,和 DomOneDomTwo 类似,但是这两个程序要小得多。

要知道,JDOM 集成了 DOM 和 SAX。您可以使用 DOM 树和 SAX 事件构造 JDOM 文档。一旦建立了 JDOM 文档,您就可以非常容易地把它转化成 DOM 树或者 SAX 事件。

 

 

JdomOne

 

如您所料,JdomOne 的代码在结构上和 DomOneSaxOne 非常类似。幸运的是,需要编写的代码要少得多。首先检查命令行得到 XML 文件名,然后是程序逻辑:

try
{
  SAXBuilder sb = new SAXBuilder();
  Document doc = sb.build(new File(argv[0]));
  XMLOutputter xo = new XMLOutputter();
  xo.output(doc, System.out);
}
catch (Exception e)
{
  e.printStackTrace();
}

就是这样!注意,JDOM 定义了一个 XMLOutputter 类,它把 XML 文件写入命令行。作为 XML 源输出解析后的文档是一种常见的任务,于是 JDOM 提供了一个类帮助您完成这项工作。代码中的前两行,SAXBuilderDocument 都属于 JDOM 包(分别在 org.jdom.input.SAXBuilderorg.jdom.Document中)。

如果结合使用 JDOM 和 DOM,要知道有些类(如 Document)同时在两个包中都有定义。您必须完全限定这些类名,以便 Java 编译程序知道您所说的是哪一个 Document

 

运行 JdomOne

在运行 JDOM 应用程序之前,需要按照 设置机器 所述设置您的机器。运行 JdomOne 会得到和前面的 DOM 与 SAX 应用程序同样的结果:

C:/xml-prog-java>java JdomOne sonnet.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sonnet SYSTEM "sonnet.dtd">
<sonnet type="Shakespearean">
  <author>
    <lastName>Shakespeare</lastName>
    <firstName>William</firstName>
    <nationality>British</nationality>
    <yearOfBirth>1564</yearOfBirth>
    <yearOfDeath>1616</yearOfDeath>
  </author>
  <title>Sonnet 130</title>
  <lines>
    <line>My mistress' eyes are nothing like the sun,</line>
    <line>Coral is far more red than her lips red.</line>
    <line>If snow be white, why then her breasts are dun,</line>
    <line>If hairs be wires, black wires grow on her head.</line>
    . . .
  </lines>
</sonnet>

注意,JDOM 向 XML 声明增加了 encoding 属性,还添加了 DOCTYPE 声明。完整的源代码在 中。

 

去掉空格

为了提供 DomTwo 的 JDOM 版本,需要去掉所有可以忽略的空格。正如您所期望的那样,JDOM 使这项工作非常简单。代码如下:

SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(new File(argv[0]));
XMLOutputter xo = new XMLOutputter();
xo.setTrimAllWhite(true);
xo.output(doc, System.out);

您所要做的仅仅是在代码中增加 setTrimAllWhite() 方法。您告诉 XMLOutputter 去掉所有的空格得到期望的结果。

完整的例子请参阅

 

JDOM 的乐趣

到现在为止,您对 JDOM 的简单性有了很好的了解。毫无疑问您应该看一看这种 API,如果能满足需要,它可以极大地简化您的开发过程。更妙的是,JDOM 对所有常见的 XML 解析器都提供了适配器(来自 Apache XML Project 的 Xerces、Oracle 的 XML 解析器、Sun 的 Crimson、用于 JAXP API 的适配器、用于 IBM XML4J 解析器的适配器)。

尽管这不在本教程的讨论范围之内,您还可以创建自己的适配器,使其他的数据源像 XML 文档那样处理。比如,如果您可以使来自关系数据库或者 LDAP 目录的信息看起来像普通的 DOM 树。然后,您就可以使用 JDOM 方法按照需要进行处理。

 

------------------------------------------------------------------------

Chapter 6 结束语和参考资料

 

结束语

本教程介绍了 Java 技术中处理 XML 文档几种不同的解析器和接口。接口在不同的解析器实现之间是可以移植的,意味着改变 XML 应用程序背后的基础设施不必修改源代码。本教程还讨论了各种 API 的长处和缺点,考察了使用 XML 和不同的 API 解决实际问题的一些情形。

我希望,这会对您开始用 Java 语言编写 XML 应用程序有所帮助。

 

未涉及到的内容

为了避免这篇教程过于冗长,我避开了几个高级主题。我将在 developerWorks 的一篇高级 XML 编程教程中讨论这些问题。其中包括:

  • 名称空间的处理
  • 使用 DTD 或 XML 模式语言验证
  • API 之间的转换(比如从 SAX 事件生成 DOM 树)
  • 不使用 XML 源文档创建 DOM 和 JDOM 对象
  • 在读完 XML 源文档之前终止 SAX 解析器
  • 在 DOM 中移动节点
  • DOM 序列化功能
  • 高级 DOM 主题(范围、遍历以及 Level 2 和 Level 3 规范中的其他内容)

 

 参考资料

如果需要更多的信息,developerWorks 上的一些文章和教程会有所帮助。也有一些关于 XML 的书籍,下面列出了这几年中我认为最有用的一些。

  • 教程

    • Introduction to XML” 讨论了 XML 的基础知识。如果不熟悉标记语言,这是一个很好的起点(developerWorks,2002 年 8 月)。
    • Understanding DOM”从更深的层次上讨论了文档对象模型(developerWorks,2003 年 7 月)。
    • Understanding SAX”深入剖析了 Simple API for XML (developerWorks,2003 年 7 月)。
  • 书籍
    • Learning XML, 2nd Edition , Erik T. Ray 著,O'Reilly 出版,一本很好的(也非常普及的)XML 入门书 (http://www.oreilly.com/catalog/learnxml2/)。
    • XML in a Nutshell, 2nd Edition , Elliotte Rusty Harold 与 W. Scott Means 合著,O'Reilly 出版,非常棒的案头参考书,尤其是如果熟悉 XML 的基础知识的话 (http://www.oreilly.com/catalog/xmlnut2/)。
    • Processing XML with Java: A Guide to SAX, DOM, JDOM, JAXP, and TrAX , Elliotte Rusty Harold 著,Addison-Wesley 出版,深入考察本文所涉及的 API(http://www.awprofessional.com)。该书的作者是 XML 社区中一位知名作家。(Addison-Wesley 网站上没有该书的直接链接,请搜索“Processing XML”找到该书的网页。)
    • Java and XML, 2nd Edition , Brett McLaughlin 著,是另一本讨论这里所涉及到的 Java API 的好书(http://www.oreilly.com/catalog/javaxml2/)。
    • O'Reilly 出版的 SAX2 是我见过的最好的深入分析 SAX API 的书(http://www.oreilly.com/catalog/sax2/)。它由 David Brownell 所著,购买本书可以帮助 David 继续他的工作。
  • 规范
    • 可以在 W3C 网站 (http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/) 上找到 DOM Level 1 规范。DOM Level 1、2 和 3 的所有规范都可以在 W3C (http://www.w3.org/DOM/DOMTR) 的 DOM Technical Reports 页面上找到。
    • 目前正式的 JDOM 规范还没有出台(2004 年 1 月),JDOM 网站声称 JDOM Javadoc 文档 是目前的规范 (http://www.jdom.org/docs/apidocs/)。此外,JDOM 文档页面 也列出了一些有用的 JDOM 文献 (http://www.jdom.org/downloads/docs.html)。
    • 关于 SAX 的所有信息,包括快速入门、关于 API 的讨论、SAX Javadoc 文件可以在 www.saxproject.org 上找到。
    • “简介”部分我提到了 XML Infoset,您可以在 W3C 站点 (http://www.w3.org/TR/xml-infoset/) 上找到 Infoset 规范
  • 其他参考资料

 

 

【相信你看了上面的材料后也会发现JDOM的简易,我们会在以后重点介绍一下JDOM的使用】

抱歉!评论已关闭.