摘 要:SAX是用來處理XML文檔的一種重要的模式。他采用一種基于事件驅(qū)動的處理模式,該模式將XML文檔看成一系列事件,對每個事件都有不同的事件處理器處理。XML文檔以數(shù)據(jù)流的形式讀入,讀入時就會觸發(fā)相應的事件,同時也會調(diào)用相應的事件處理器。文章針對SAX在處理XML文檔中存在的不足,通過分析XML文檔的語法特性,然后將這些特性和棧數(shù)據(jù)結(jié)構(gòu)應用到SAX處理XML文檔中,該方法在一定程度上彌補了SAX所存在的不足。
關(guān)鍵詞:XML語法特性;SAX;基于事件驅(qū)動模式;事件處理器
中圖分類號:TP31111文獻標識碼:A
文章編號:1004-373X(2008)08-064-04
Study on Application of XML Syntax Speciality in SAX
CAI Qihua,WANG Mingqiang
(Institute of Command Automation,PLA University of Science and Technology,Nanjing,210007,China)
Abstract:SAX is an important model to process a XML document.It uses an eventdriven based processing model,and this model looks on a XML document as serial events which have different event handlers.When a XML document is read by way of flow of data,relevant events are caught,and handled immediately by relevant event handlers.In this paper,XML syntax speciality and stack are used in SAX to process the XML document,such as the limitation of SAX is eliminated partially.
Keywords:XML syntax speciality;SAX;eventdriven based model;event processor
1 引 言
XML[1](Extensible Markup Language,可擴展性標記語言)由于其強大的對復雜數(shù)據(jù)的描述能力和廣泛的適應性,已經(jīng)逐漸地被廣泛應用于各個領(lǐng)域。當前,用于解析XML文檔的2種基本方式是DOM和SAX。其中DOM(Document Object Model)方式是首先將整個XML文檔讀入到內(nèi)存形成一棵樹狀結(jié)構(gòu),應用程序可以用各種方法對該樹進行遍歷、插入、刪除以及修改等操作[2]。該方式有著信息量豐富,并且對其中的信息可以隨機訪問的優(yōu)點。但是由于要將整個XML文檔讀入內(nèi)存并生成一棵樹,對于非常大的XML文檔,DOM解析過程就十分緩慢,同時也會耗費大量的內(nèi)存資源。
SAX(Simply API for XML)采用的是一種基于事件驅(qū)動的處理模式,他的處理方式把XML文檔中元素、屬性、內(nèi)容等都當成是事件,并且為每個事件都定義一個回調(diào)方法,這個回調(diào)方法由應用程序提供[2]。解析器以數(shù)據(jù)流的方式讀入XML,當遇到某個事件時就調(diào)用相應的回調(diào)方法。該方式并不會事先將XML文檔讀入內(nèi)存,而是在讀XML文檔的過程中就開始對文檔進行解析。其優(yōu)點就是處理效率高,特別是適合大型的XML文檔處理。但是對一些類似于查詢元素在XML中的位置以及元素的嵌套情況等與XML結(jié)構(gòu)相關(guān)的操作,SAX 顯得有些困難。
本文分析XML文檔結(jié)構(gòu)和語法規(guī)則特性,并且將這些特性應用到SAX解析XML文檔中,利用棧數(shù)據(jù)結(jié)構(gòu)來保存與XML結(jié)構(gòu)相關(guān)的信息,通過對棧的分析解決類似查詢元素位置和嵌套情況等問題,從而從一定程度上消除SAX在處理XML文檔時所存在的不足。
2 XML文檔結(jié)構(gòu)和語法特點
XML是由W3C(World Wide Web Consortium,萬維網(wǎng)協(xié)會)設(shè)計的一種可擴展的標記語言。相對HTML(Hyper Text Markup Language)語言,XML更側(cè)重于如何結(jié)構(gòu)化地描述所要表達的信息,并且有著嚴格的語法要求,因此結(jié)構(gòu)更加的清晰,信息更便于閱讀和維護。
2.1 XML文檔的結(jié)構(gòu)
XML文檔采用樹狀的層次性結(jié)構(gòu),類似于層次性數(shù)據(jù)庫系統(tǒng)[1,3]。在層次性模型結(jié)構(gòu)中,有且僅有一個節(jié)點沒有任何父親,這個節(jié)點稱為根節(jié)點,除根節(jié)點外所有節(jié)點都有且僅有一個父節(jié)點,也就是說XML文檔結(jié)構(gòu)就像一個倒置的樹。如圖1所示是Student.xml文檔的結(jié)構(gòu)圖。
一般的XML文檔由以下幾個部分組成:XML聲明、根元素、元素、屬性以及注釋等[1]。其中屬性和注釋有時也可以沒有。例1是名為Student.xml的XML文檔:
[HTH]例1 [HTSS]一個XML文檔代碼為student.xml
<?xml version=″1.0″ encoding=″GB2312″?>
<Student>
<Id>2005012</Id>
<Name>Tom</Name>
<Age>20</Age>
<School[CD#*2]Report[CD#*2]Card>
<Subject Name =″Math″>
<Grade>90</Grade>
</Subject>
<Subject Name =″English″>
<Grade>95</Grade>
</Subject>
<Subject Name =″Physics″>
<Grade>94</Grade>
</Subject>
</School[CD#*2]Report[CD#*2]Card>
</Student>
其中<?xml version=″1.0″ encoding=″GB2312″>是XML聲明,其作用是告訴瀏覽器或者其他處理程序該文檔是XML文檔;其中version是XML文檔的版本信息;encoding是內(nèi)部信息的編碼方式。而根元素部分是:
<Student>
…
</Student>
根元素是文檔的主體部分,他包含文檔的數(shù)據(jù)以及描述數(shù)據(jù)結(jié)構(gòu)的信息。在根元素中,信息主要是以元素和屬性的形式存儲。
圖1 Student.xml文檔的層次性結(jié)構(gòu)模型
2.2 XML文檔語法特性
XML文檔有著十分嚴格的語法規(guī)定,他必須遵循W3C所推薦規(guī)則規(guī)定的XML語法,這些語法的主要特性[1]是:
(1) XML文檔中所有元素的標簽(tag)都必須有相應的結(jié)束標簽(ending tag)。也就是說開始標簽和結(jié)束標簽是成套出現(xiàn)的。例如:
<Student>
<Name>Tom</Name>
</Student>
其中所有標簽都是封閉的,例如數(shù)據(jù)“Tom” 包含在<Name>和</Name>標簽對之間,其中<Name>是開始標簽,</Name>是結(jié)束標簽。
(2) XML文檔中所有元素都必須合理地嵌套,不容許交叉混亂的嵌套。例如:
<Student>
<Name>Tom</Name>
<School[CD#*2]Report[CD#*2]Card>
<Subject>Math</Subject>
<Grade>90</Grade>
</School[CD#*2]Report[CD#*2]Card>
</Student>
該XML文檔中元素的嵌套是層次分明合理的。又例如:
<Student>
<Name>Tom</Name>
<School[CD#*2]Report[CD#*2]Card>
<Subject>Math</Subject>
<Grade>90</Grade>
</Student>
</School[CD#*2]Report[CD#*2]Card>
該XML文檔中元素的嵌套就是不合理的,因為根元素Student和其子元素School[CD#*2]Report[CD#*2]Card 中標簽出現(xiàn)了交錯嵌套。
(3) XML文檔必須包含一個容納其他所有元素的根元素。例如在前面的Student.xml文檔中,根元素是Student,他有惟一的一對標簽<Student>和</Student>來定義,所有子元素都必須合理的嵌套在內(nèi)部。
由于XML有著比較嚴格的語法要求,因此看起來整個文檔更加的清晰、層次分明,一些基于XML文檔的應用程序?qū)λ奶幚硐鄬碚f也會更加的方便。
3 SAX處理XML方式及其改進
SAX(Simply API for XML)實際上是一個訪問XML文檔的Java接口,他是由XMLDEV郵件列表的成員開發(fā)維護的一個公共領(lǐng)域(Public Domain)軟件,Java版本由DavidMegginson來維護。
3.1 SAX處理XML的方式
SAX采用的是一種基于事件驅(qū)動的XML處理方式,他將XML文檔看成一系列的事件,對不同的事件采用不同的事件處理器來處理[2]。這種基于事件的處理模式主要是圍繞著事件源以及事件處理器(或者叫監(jiān)聽器)工作。事件源是一個能產(chǎn)生事件的對象,同時能夠注冊事件處理器并且向他們發(fā)送事件對象,而事件處理器是可以針對事件產(chǎn)生響應的對象。事件源和事件處理器之間是通過在事件源中的事件處理器注冊方法來連接的,這樣當事件源產(chǎn)生事件后,調(diào)用事件處理器相應的處理方法,一個事件就得到了處理[2]。
在SAX中,事件源是org.xml.sax包中的XMLReader,當XML文檔輸入到XMLReader中后,他通過parse()方法來開始解析XML文檔并根據(jù)文檔內(nèi)容產(chǎn)生事件。而事件處理器則主要是org.xml.sax包中的ContentHandler這個接口,在這個接口中有處理不同事件的方法(幾個主要方法見表1)。而事件源XMLReader和這4個事件處理器的連接是通過在XMLReader中的相應的事件處理器注冊方法setContentHandler( )來完成[24]。這樣,事件源XMLReader通過這個注冊方法就跟事件處理器ContentHandler聯(lián)系起來,如圖2所示。
表1 ContentHandler接口中的幾個主要方法
XML文檔中有關(guān)事件ContentHandler中相應的方法
文檔解析開始startDocument()
文檔解析結(jié)束endDocument()
元素處理開始(元素開始標簽)startElement(String namespaceURI,String localName,String qName,Attributes atts)
元素處理結(jié)束(元素結(jié)束標簽)endElement(String namespaceURI,String localName,String qName)
元素字符數(shù)據(jù)處理characters(char[] ch,int start,int length)
圖2 SAX的處理XML模式
以上可以看出,SAX處理XML文檔過程其實是讀入與解析幾乎同步的過程,在讀入XML文檔的時候,SAX將XML看成一個數(shù)據(jù)流。當遇到某一特定的XML標簽時相應地觸發(fā)某一事件,然后調(diào)用相應的事件處理器,從而完成XML的處理。對于基于XML文檔的應用程序,這種類似于流媒體的處理方式顯得更加的靈活,應用程序不必解析整個XML文檔,他可以在某個條件得到滿足時候停止解析。同時由于是邊讀取邊處理的方式,沒必要將整個數(shù)據(jù)讀入內(nèi)存,這對于大型的XML文檔的處理是一個大的優(yōu)點。
3.2 SAX存在的不足
在SAX處理XML文檔的方式中,事件處理器中每個事件處理方法都沒有提供有關(guān)XML文檔結(jié)構(gòu)的信息,比如當前元素所在XML文檔中的位置以及XML文檔的嵌套情況等。但是,在許多的應用程序中,經(jīng)常需要查詢元素在XML文檔中的位置以及元素的嵌套情況等與XML文檔結(jié)構(gòu)相關(guān)的信息,這時,應用SAX來處理顯得十分吃力。
3.3 在SAX處理XML文檔中應用XML文檔語法特性
在2.2中知道XML文檔的語法特性:完備的標簽嵌套結(jié)構(gòu),每一個開始標簽總會對應一個結(jié)束標簽以及有且僅有一個根元素,也就是說XML文檔有著良好的結(jié)構(gòu)性。這樣,可以考慮利用XML文檔的這些特性,同時應用棧(stack)實現(xiàn)在DOM中才能實現(xiàn)的一些功能。
棧[5](stack)是一種只容許在同一端進行插入/刪除操作的表結(jié)構(gòu),在容許插入/刪除的一端稱為棧頂(top),另一段稱為棧底(bottom),他的特點是后進先出(last in first out)。對棧的操作主要有棧插入push(也稱壓棧)和棧刪除pop(也稱彈棧),而所有這些操作都在棧頂一端進行。利用棧來處理XML文檔的算法思想是:
在解析XML文檔時,當遇到元素處理開始事件后,在相應的事件處理器中采取push操作將該元素標簽壓入到棧中,而當遇到元素處理結(jié)束事件后采取pop操作將該元素標簽彈出棧。對于一個結(jié)構(gòu)良好的XML文檔來說,很容易知道所有push和pop操作都是成對出現(xiàn)的。這樣只需要分析棧的結(jié)構(gòu)就可以確定當前標簽在XML文檔中的具體位置,而棧中最大條目數(shù)就是該XML文檔的最大嵌套層數(shù),棧底就是整個XML的根節(jié)點?,F(xiàn)在以表1中ContentHandler事件處理器為例實現(xiàn)該算法思想:
ContentHandler事件處理器實際上是一個接口,現(xiàn)在來創(chuàng)建一個實現(xiàn)該接口的類,為了方便起見,在此只寫表1了中所涉及到的幾個主要的方法的實現(xiàn)。則用例2所示的Java來實現(xiàn)。要解析的是例1的Studenet.xml文檔,假如現(xiàn)在想知道學生的年齡(age)以及該元素在文檔中處理樹結(jié)構(gòu)中的位置,則用例2所示的Java程序?qū)崿F(xiàn)。
[HTH]例2[HTSS] 算法思想的實現(xiàn),代碼為Mystudent XML java
public class MyStudentXML implements ContentHandler {
// 聲明棧tags
Stack tags = new Stack();
//文檔解析開始的處理方式
public void startDocument()
{
System.out.println(\"…Parse start…\");
};
// 文檔解析結(jié)束的處理方式
public void endDocument()
{
System.out.println(\"…Parse End…\");
};
// 當遇到元素開始標簽時,將開始標簽名bq壓入棧tags中
public void startElement(String ns,String bq,String q,Attributes a)
{
tags.push(bq);
};
// 當遇到元素結(jié)束標簽的時候,對棧tags做彈棧操作
public void endElement(String ns,String bq,String q,Attributes a)
{
tags.pop();
};
/****/
這個方法是用來處理在XML文件中讀到具體的數(shù)據(jù)字符串,他的參數(shù)是一個字符數(shù)組,以及讀到的這個數(shù)據(jù)字符串在這個數(shù)組中的起始位置start和長度length。
public void characters(char datachar,int start,int length) {
// 獲得當前棧的長度,也就當前節(jié)點元素在XML文檔中的嵌套層數(shù)
int stackSize = tags.size();
// 獲得當前棧頂信息,也就是當前元素的標簽
String tag = (String)tags.peek();
//獲得標簽內(nèi)所包括的數(shù)據(jù)data
String data = new String(datachar,start,length);
//打印學生的年齡以及該元素在文檔中的位置
If(tag.equals(\"Age\")){
System.out.println(\"元素Age的數(shù)據(jù)是:\" + data + \",并且該元素處于整個XML文檔的第\" + stackSize);
}
};
//解析Student.xml文檔
static public void main(String args) {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser=1;
try{
saxParser = spf.newSAXParser();
}
catch(Exception ex) {
System.err.println(ex);
System.exit(1);
}
try{
saxParser.parse(new File(\"Student.xml\"),new SAXReader());
}
catch(SAXException se) {
System.err.println(se.getMessage());
System.exit(1);
}
catch(IOException ioe) {
System.err.println(ioe);
System.exit(1);
}
}
}
該程序運行的結(jié)果是:
元素Age的數(shù)據(jù)是:20,處于整個XML文檔的第2層
[LL]
從上面程序中可以看出:棧中保存的是在解析文檔過程中的標簽名稱,他是一個動態(tài)的數(shù)據(jù)結(jié)構(gòu),隨著解析的進展而變化,通過分析棧的結(jié)構(gòu)可以得到一些關(guān)于XML文檔結(jié)構(gòu)和元素在文檔中位置的信息。其中代碼:
int stackSize = tags.size();
是用來獲得當前棧內(nèi)元素個數(shù),也就是當前數(shù)據(jù)data所在XML文檔樹狀層次結(jié)構(gòu)中的層次。此外,如果需要得到從根元素將當前元素的一條路徑,則只要將當前棧內(nèi)元素從底往上按順序取出即可。增加代碼如下:
for (int top = 1,top <= tagsSize,top++ ){
String datapath = tags.get(top);
}
總的來說,改進后的方法是通過分析棧的結(jié)構(gòu)獲得元素所在XML文檔的位置以及當前元素的嵌套情況。代碼中字符串數(shù)組變量datapath就是棧中元素從底向上的逐一排列,其實就是當前解析的元素在XML結(jié)構(gòu)樹中的一條從根元素到當前元素的惟一路徑,同時也是當前元素在XML文檔中的嵌套情況。
4 結(jié) 語
本文將XML文檔的語法特性應用于SAX對XML的處理中,同時利用棧這個常見的數(shù)據(jù)結(jié)構(gòu),很好地克服了SAX在處理XML文檔時所存在的一些不足。盡管去維護棧也要花費一些工夫,但是對于那些處理大型的XML文檔并且用戶只對文檔中部分信息有興趣的應用程序來說,這些維護工夫相對于使用DOM方式來說是很值得的。
參 考 文 獻
[1]丁躍潮,葉文來,陳杰.XML實用教程[M].北京:機械工業(yè)出版社,2006.
[2]\\[英\\] Mark Birbeck.XML高級編程\\[M\\].2版.裴劍鋒,譯.北京:機械工業(yè)出版社,2002.
[3]Jeff Suttor,Norman Walsh,Kohsuke Kawaguchi.JSR 206 Java API for XML Processing (JAXP) 1.3[EB/OL].http://www.java.sun.com/webservices/jaxp 2007.6.3/2007.
[4]\\[美\\] Cay S.Horstmann,Gary Cornell.最新Java 2核心技術(shù) 卷Ⅱ:高級特性(v1.3)5E[M].王建華,董志敏,譯.北京:機械工業(yè)出版社,2005.
[5]王慶瑞.數(shù)據(jù)結(jié)構(gòu)教程(C語言版)[M].北京:希望電子出版社,2002.
作者簡介
蔡七華 男,1977年出生,碩士研究生。研究方向為軟件理論。
王明強 男,1979年出生,碩士研究生。研究方向為軍事運籌分析應用。
注:本文中所涉及到的圖表、注解、公式等內(nèi)容請以PDF格式閱讀原文