J2EE/XML 开发者通常都是使用文档对象模型 (DOM)API 或简单的 API for XML(SAX) API 来分析 XML 文档。然而,这些 API 都有其缺点。其中, DOM API 的缺点之一是消耗大量的内存,因为在该 XML 文档可以被导航之前,必须创建一个完整的 XML 文档的内存结构。而 SAX API 的缺点在于,它实例了一种推分析模型 API ,其中分析事件是由分析器生成的。比较之下, StAX 则是基于一种拉分析模型。在本文中,你将首先创建你自己的 XML 文档,然后学习使用各种不同方法来对之进行分析;最后,我们使用事件生成的 StAX 拉方法。
一、 Push 推分析之于拉分析 Pull
比较于推分析,拉分析具有如下一些优点:
1. 在拉分析中,事件是由分析应用程序生成的,因此把分析规则提供到客户端而不是分析器。
2. 拉分析的代码更简单并且它比推分析有更少的库。
3. 拉分析客户端能同时读多个 XML 文档。
4. 拉分析允许你过滤 XML 文档并且跳过分析事件。
二、了解 StAX
针对于 XML 的流式 API(StAX) ,是在 2004 年 3 月的 JSR 173 规范中引入,这是一种针对 XML 的流式拉分析 API 。 StAX 是 JDK 6.0 提供的一种新特征,你可以从此处下载它的测试版本试用。
一个推模型分析器不断地生成事件,直到 XML 文档被完全分析结束。但是,拉分析由应用程序进行调整;因此,分析事件是由应用程序生成的。这意味着,使用 StaX ,你可以推迟分析 - 在分析时跳过元素并且分析多个文档。在使用 DOM API 的时候,你必须把整个的 XML 文档分析成一棵 DOM 结构,这样也就降低了分析效率。而借助于 StAX ,在分析 XML 文档时生成分析事件。有关于 StAX 分析器与其它分析器的比较在此不多介绍。
StAX API 的实现是使用了 Java Web 服务开发( JWSDP ) 1.6 ,并结合了 Sun Java 流式 XML 分析器 (SJSXP)- 它位于 javax.xml.stream 包中。 XMLStreamReader 接口用于分析一个 XML 文档,而 XMLStreamWriter 接口用于生成一个 XML 文档。 XMLEventReader 负责使用一个对象事件迭代子分析 XML 事件 - 这与 XMLStreamReader 所使用的光标机制形成对照。本教程将基于 JDK 6.0 中的 StAX 实现来完成对一个 XML 文档的分析。
其实, StaX 仅仅是 JDK 6.0 所提供的 XML 新特征之一。新的 JDK 6.0 还提供了对针对于 XML-Web 服务的 Java 架构( JAX-WS ) 2.0 ,针对于 XML 绑定的 Java API(JAXB) 2.0 , XML 数字签名 API 的支持,甚至还支持 SQL:2003 'XML' 数据类型。
三、安装
如果你正在使用 JDK 6.0 ,那么默认情况下, StAX API 位于 Classpath 中。如果你在使用 JWSDP 1.6 ,请把 JWSDP 1.6 StAX API 添加到 classpath 中。
jsr173_api.jar 和 sjsxp.jar 添加到 CLASSPATH 变量中。在 <jwsdp-1.6> 目录下安装 JWSDP 1.6 。 Jsr173_api.jar 相应于 JSR-173 API JAR , Sjsxp.jar 相应于 SJXSP 实现 JAR 。
四、使用 XMLStreamWriter 进行写操作
首先,你要创建将待分析的 XML 文档。由 StAX 的 XMLStreamWriter 生成 XML 。然而, XMLStreamWriter 的一个限制是,它不一定会生成良构的文档 - 而且生成的文档也不一定是有效的。你需要确保生成的 XML 文档是良构的。列表 1 是一个由 XMLStreamWriter 生成的原始 XML 文档的示例。
在此,你试图使用 XMLStreamWriter API 生成列表 1 中的 catalog.xml 。在本节中的代码片断节选自 XMLWriter.java 应用程序,显示于列表 2 中。首先,你将导入 StAX 包类,请参考下列编码:
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLOutputFactory;
// 首先你必须创建一个新的 XMLOutputFactory
XMLOutputFactory outputFactory=XMLOutputFactory.newInstance();
// 接下来,创建一个 FileWriter 以输出 XML 文档 - 它将被生成到一个 XML 文件中:
FileWriter output=new FileWriter(new File("C:/STAX/catalog.xml"));
// 接下来,创建一个 XMLStreamWriter :
XMLStreamWriter XMLStreamWriterr=outputFactory.createXMLStreamWriter(output);
// 添加要在 XML 声明中指定的编码和版本(记住,指定的编码并不是生成的 XML 文档的编码)。如果你需要指定 XML 文档的编码,该怎么办呢?当从一个 XMLOutputFactory 对象创建一个 XMLStreamWriter 对象时,你会这样做:
XMLStreamWriter.writeStartDocument("UTF-8" , "1.0");
// 使用 writeComment() 方法以输出一个注释:
XMLStreamWriter.writeComment("A OReilly Journal Catalog");
// 使用 writeProcessingInstruction() 方法以输出一条处理指令:
XMLStreamWriter.writeProcessingInstruction("catalog" , "journal='OReilly'");
// 使用 writeStartElement() 方法以输出 'catalog' 元素的开始(元素前缀和命名空间 URI 也可以在这个方法中指定的):
XMLStreamWriter.writeStartElement("journal" , "catalog" , "http://OnJava.com/Journal");
// 使用 writeNamespace() 方法以添加 'journal' 命名空间声明(命名空间前缀和命名空间 URI 也是在这个方法中指定的):
XMLStreamWriter.writeNamespace("journal" , "http://OnJava.com/Journal");
// 再次使用 writeNamespace() 方法添加 xsi 命名空间:
XMLStreamWriter.writeNamespace("xsi" , "http://www.w3.org/2001/XMLSchema-instance");
// 使用 writeAttribute() 方法添加 xsi:namespaceSchemaLocation 属性:
XMLStreamWriter.writeAttribute("xsi:noNamespaceSchemaLocation" , "file://c:/Schemas/catalog.xsd");
// 使用 writeAttribute() 方法添加 'publisher' 属性:
XMLStreamWriter.writeAttribute("publisher" , "OReilly");
// 输出 'journal' 元素的开始。当增加一个新元素时,前一个元素的 '>' 括号也被添加上:
XMLStreamWriter.writeStartElement("journal" , "journal" , "http:
//OnJava.com/Journal");
// 使用 writeAttribute() 方法以添加 'date' 和 'title' 属性。然后,使用 writeElement() 方法以添加 'article' 和 'title' 元素。然后,使用 writeCharacters() 方法输出 'title' 元素的文本:
XMLStreamWriter.writeCharacters("Data Binding with XMLBeans");
// 任何包含文本或子元素的元素都要有一个结束标签。使用 writeEndElement() 元素来添加 'title' 元素的结束标签:
XMLStreamWriter.writeEndElement();
// 添加 'author' 元素和 'journal' 元素的结束标签。在 writeEndElement() 方法中,不必要指定元素前缀和命名空间 URI 。以类似方式添加另一个 'journal' 元素。然后,添加 'catalog' 元素的结束标签。最后,输出缓冲的数据:
XMLStreamWriter.flush();
// 最后一步,关闭 XMLStreamWriter 。
XMLStreamWriter.close();
// 这就是生成 catalog.xml 的过程。
源码中的列表 2 展示了完整的 Java 应用程序 -XMLWriter.java 。这个应用程序可以作为一个命令行应用程序运行或在一种例如 Eclipse 这样的 IDE 中运行。
五、使用 XMLStreamReader 进行分析
通过使用 XMLStreamReader API 分析列表 1 中的文档,我们来详细分析一下其工作原理。 XMLStreamReader 使用一种光标分析 XML 文档。它的接口包含一个 next() 方法 - 由它分析下一个分析事件。 getEventType() 方法返回事件类型。后面的代码片断来自于 XMLParser.java 应用程序,详见列表 3 。
在这个 XMLParser.java 应用程序中,首先,你要导入 StAX 类:
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLInputFactory;
// 创建一个 XMLInputFactory ,由此你会得到一个 XMLStreamReader :
XMLInputFactory inputFactory=XMLInputFactory.newInstance();
// 创建一个 InputStream ,作为一个输入流,它描述了将被分析的文件。另外,还要从前面创建的 XMLInputFactory 对象中创建一个 XMLStreamReader 。
InputStream input=new FileInputStream(new File("C:/STAX/catalog.xml"));
XMLStreamReader xmlStreamReader =inputFactory.createXMLStreamReader(input);
// 如果更多分析事件可用, hasNext() 方法返回 true 。然后,使用 next() 方法获得下一个分析事件:
int event=xmlStreamReader.next();
比较于 SAX 分析, StAX 分析的优点是,一个分析事件可以被跳过 - 通过调用 next() 方法 ,详见下面的代码。例如,如果分析事件类型为 ENTITY_DECLARATION ,那么开发者可以决定是要从当前事件中获得事件信息,还是检索下一个事件:
If(event.getEventType()==XMLStreamConstants.ENTITY_DECLARATION){
int event=xmlStreamReader.next();
}
通过不调用 next() 方法,分析也可以被推迟。 next() 方法返回 int ,它代表了一个分析事件 - 通过使用一个 XMLStreamConstants 常量指定。
XMLStreamReader 所返回的不同的事件类型列举于表格1中。
事件类型 |
描述 |
START_DOCUMENT |
一个文档的开始 |
START_ELEMENT |
一个元素的开始 |
ATTRIBUTE |
一个元素属性 |
NAMESPACE |
一个命名空间声明 |
CHARACTERS |
字符可以是文本,或是一个空格 |
COMMENT |
一个注释 |
SPACE |
可忽略的空格 |
PROCESSING_INSTRUCTION |
处理指令 |
DTD |
一个 DTD |
ENTITY_REFERENCE |
一个实体参考 |
CDATA |
Cdata 节 |
END_ELEMENT |
结束元素 |
END_DOCUMENT |
结束文档 |
ENTITY_DECLARATION |
一个实体声明 |
NOTATION_DECLARATION |
一个标志声明 |
表格 1.XMLStreamReader 事件
这些不同的分析事件能够使你获得 XML 文档中的数据和元数据。如果分析事件类型是 START_DOCUMENT ,那么你将使用 getEncoding() 方法获得 XML 文档中的指定编码,而你将使用 getVersion() 方法返回 XML 文档的 XML 版本。
同样,如果你在使用一个 START_ELEMENT 事件类型工作,那么你将使用 getPrefix() 方法来返回元素前缀并且使用 getNamespaceURI 来返回元素前缀命名空间或默认命名空间。为了获得元素的本地命名,你将使用 getLocalName() 方法并且使用 getAttributesCount() 方法获得属性数目。你将使用 getAttributePrefix(i) 方法得到一个指定的属性索引 i 的属性前缀,而使用 getAttributeNamespace(i) 方法取得属性命名空间。使用 getAttributeLocalName(i) 方法获得属性本地命名,使用 getAttributeValue(i) 方法获得属性值。如果事件类型是 CHARACTERS 或 COMMENT ,则使用 getText() 方法获得相应的文本。
列表 4 显示了示例 XML 文档, catalog.xml 的分析输出结果。
列表 3 显示了用于分析 XML 文档的 Java 应用程序。你可以从命令行上或在一种例如 Eclipse 这样的 IDE 中来运行该应用程序。记住:如果你没有首先运行 XMLWriter.java 应用程序而运行 XMLParser.java( 见源码中的列表 2) ,那么你需要把 catalog.xml( 见源码中的列表 1) 复制到 C:/StAX 目录下。
六、使用 XMLEventReader 进行分析
本节将向你展示如何使用 XMLEventReader 来分析 catalog.xml 。 XMLEventReader 接口使用一个事件对象迭代算子分析一个 XML 文档;通过这种方式,一个 XML 事件生成一个 XMLEvent 对象。 XMLEventReader 类似于 XMLStreamReader- 分析事件是由 StAX 分析器生成的。然而, XMLEventReader 比 XMLStreamReader 有一个优点:通过使用 XMLEventReader ,一个应用程序可以使用 peek() 方法来 " 偷看 " 下一个事件,而不必从流中读取事件。这样,一个应用程序客户端可以决定是否有必要分析下一个事件。 本节中的代码片断节选自 XMLEventParser.java 应用程序,请参见列表 5 。
首先,导入 StAX 类:
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLInputFactory;
接下来,创建一个 XMLInputFactory ,由它获得一个 XMLEventReader 对象:
XMLInputFactory inputFactory=XMLInputFactory.newInstance();
InputStream input=new FileInputStream(new File("C:/STAX/catalog.xml"));
XMLEventReader xmlEventReader =inputFactory.createXMLEventReader(input);
在 StAX 中, XML 文档事件是通过 XMLEvent 对象描述的。使用 nextEvent() 方法来遍历 XMLEventReader 对象以获得下一个事件:
XMLEvent event=xmlEventReader.nextEvent();
使用 getEventType() 方法来获得事件类型 ( 请参考表格1 ) 。 XMLEvent 接口还提供布尔方法来获得事件类型。例如, isStartDocum ent() 返回 true ,如果事件是开始文档类型。在下列代码中,事件是开始元素类型,因此一个 StartElement 对象可以从这个 XMLEvent 接口获得:
if(event.isStartElement()){
StartElement startElement=event.asStartElement();
}
使用 getAttributes() 方法获得元素属性:
Iterator attributes=startElement.getAttributes();
这个 Iterator 描述了一个 javax.xml.stream.events.Attribute 对象。使用 next() 方法遍历该 Iterator 。
Attribute attribute=(javax.xml.stream.events.Attribute)(attributes.next());
最后,使用 getName() 方法获得属性命名,使用 getValue() 方法获得属性值。
列表 5 显示出分析该 XML 文档的 Java 应用程序。应用程序 XMLEventReader 可以作为一个命令行应用程序运行,或在一种例如 Eclipse 这样的 IDE 中运行。记住:如果你运行 XMLWriter.java 或 XMLParser.java 应用程序而不首先运行 XMLEventParser.java 应用程序,那么你将需要把 catalog.xml 复制到 C:/StAX 目录下。
最终,基于拉的事件生成把事件规则提供到分析器应用程序而不是提供到分析器。