j2ee/xml开发者通常都是使用文档对象模型(dom)api或简单的api for xml(sax) api来分析xml文档。然而,这些api都有其缺点。其中,dom api的缺点之一是消耗大量的内存,因为在该xml文档可以被导航之前,必须创建一个完整的xml文档的内存结构。而sax api的缺点在于,它实例了一种推分析模型api,其中分析事件是由分析器生成的。比较之下,stax则是基于一种拉分析模型。在本文中,你将首先创建你自己的xml文档,然后学习使用各种不同方法来对之进行分析;最后,我们使用事件生成的stax拉方法。
一、 推分析之于拉分析
比较于推分析,拉分析具有如下一些优点:
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中。这需要把<jwsdp-1.6>/sjsxp/lib/ jsr173_api.jar和<jwsdp-1.6>/sjsxp/lib/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中得到你的xmlstreamwriter。因此,首先你必须创建一个新的xmloutputfactory:
xmloutputfactory outputfactory=xmloutputfactory.newinstance();
接下来,创建一个filewriter以输出xml文档-它将被生成到一个xml文件中:
filewriter output=new filewriter(new file("c:/stax/catalog.xml"));
接下来,创建一个xmlstreamwriter:
xmlstreamwriter xmlstreamwriterr=outputfactory.createxmlstreamwriter(output);
现在,使用writestartdocument()方法创建一个文档开头。添加要在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接口还提供布尔方法来获得事件类型。例如,isstartdocument()返回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目录下。
最终,基于拉的事件生成把事件规则提供到分析器应用程序而不是提供到分析器。