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目錄下。
最終,基于拉的事件生成把事件規則提供到分析器應用程序而不是提供到分析器。