用Digester簡(jiǎn)化XML文檔處理 (2)
作者:仙人掌工作室 來(lái)源:賽迪網(wǎng) 發(fā)布時(shí)間:2002.11.25
【Java專(zhuān)區】 【網(wǎng)絡(luò )安全】 【網(wǎng)管專(zhuān)區】 【linux專(zhuān)區】 【進(jìn)入論壇】 【IT博客】
指定模式和規則 Digester框架以模式(Pattern)和規則(Rule)為基礎處理輸入的XML。模式必須與XML元素匹配,包括其名字和在文檔樹(shù)內的位置。描述匹配模式的語(yǔ)法類(lèi)似于XPath匹配模式,例如:catalog模式匹配頂層的<catalog>元素,catalog/book模式匹配直接嵌套在<catalog>元素內的<book>元素(但不匹配文檔內其他位置的<book>元素)。 所有的模式都必須指定其完整名稱(chēng)——從根元素開(kāi)始的完整路徑。唯一的例外是包含通配符(“*”)的模式,例如*/name模式匹配XML文檔內任何位置的<name>元素。但是根元素不必特別指出,因為所有的路徑都是從根元素開(kāi)始的絕對路徑。 當Digester發(fā)現一個(gè)指定的模式,它就執行關(guān)聯(lián)的任務(wù)。由此可見(jiàn),Digester框架顯然與SAX解析器有著(zhù)密切的關(guān)系(實(shí)際上,Digester類(lèi)實(shí)現了org.xml.sax.ContentHandler,并維護著(zhù)解析棧)。所有在Digester中使用的規則必須擴展org.apache.commons.digester.Rule,后者本身提供了一些類(lèi)似于SAX的ContentHandler回調函數的方法。例如,當遇到匹配元素的開(kāi)始標記和結束標記時(shí),begin()方法和end()方法將分別被調用。 一旦遇到匹配元素的內容,body()方法被調用;最后被調用的方法是finish(),這個(gè)方法在匹配元素的結束標記處理完畢之后被調用,用來(lái)執行可能需要的事后清理任務(wù)。然而,大多數時(shí)候我們不必關(guān)注這些方法,因為框架提供的標準規則很可能已經(jīng)提供了所有必需的功能。 要反配制一個(gè)文檔,首先創(chuàng )建一個(gè)org.apache.commons.digester.Digester類(lèi)的實(shí)例,如果必要的話(huà),進(jìn)行一些配置操作,指定必需的模式和規則,最后向parse()方法傳遞一個(gè)XML文件的引用。下面的DigesterDriver示范了這一處理過(guò)程(必須在命令行上指定輸入XML文檔的名稱(chēng))。 import org.apache.commons.digester.*; import java.io.*; import java.util.*; public class DigesterDriver { public static void main( String[] args ) { try { Digester digester = new Digester(); digester.setValidating( false ); digester.addObjectCreate( "catalog", Catalog.class ); digester.addObjectCreate( "catalog/book", Book.class ); digester.addBeanPropertySetter( "catalog/book/author", "author" ); digester.addBeanPropertySetter( "catalog/book/title", "title" ); digester.addSetNext( "catalog/book", "addBook" ); digester.addObjectCreate( "catalog/magazine", Magazine.class ); digester.addBeanPropertySetter( "catalog/magazine/name", "name" ); digester.addObjectCreate( "catalog/magazine/article", Article.class ); digester.addSetProperties( "catalog/magazine/article", "page", "page" ); digester.addBeanPropertySetter( "catalog/magazine/article/headline" ); digester.addSetNext( "catalog/magazine/article", "addArticle" ); digester.addSetNext( "catalog/magazine", "addMagazine" ); File input = new File( args[0] ); Catalog c = (Catalog)digester.parse( input ); System.out.println( c.toString() ); } catch( Exception exc ) { exc.printStackTrace(); } } }
在上面的代碼中,我們首先創(chuàng )建了Digester類(lèi)的一個(gè)實(shí)例digester,然后指定它不要用DTD驗證XML文檔的合法性——這是因為我們沒(méi)有為XML文檔定義DTD。接下來(lái),我們指定了模式和關(guān)聯(lián)的規則:ObjectCreateRule創(chuàng )建指定類(lèi)的一個(gè)實(shí)例,并將它壓入解析棧。SetPropertiesRule把Bean屬性設置成當前XML元素的屬性值——規則的第一個(gè)參數是XML屬性的名稱(chēng),第二個(gè)參數是Bean屬性的名稱(chēng)。 SetPropertiesRule獲取的是XML屬性的值,而B(niǎo)eanPropertySetterRule獲取的是位于當前元素內的原始字符數據值。使用BeanPropertySetterRule時(shí)不必指定要設置的Bean屬性名字,默認是當前XML元素的名稱(chēng)。在上面的例子中,在匹配catalog/magazine/article/headline模式的規則定義中使用的就是默認值。最后,SetNextRule彈出解析棧頂部的對象,并把該對象傳遞給它下面對象的指定名稱(chēng)的方法——通常用來(lái)把一個(gè)配置完畢的Bean插入父對象。 注意,我們可以為同一個(gè)模式注冊多個(gè)規則。如果注冊了多個(gè)規則,則這些規則按照它們被加入到Digester的次序執行,例如,如果要處理catalog/magazine/article的元素,我們首先創(chuàng )建合適的article Bean,然后設置page屬性,最后彈出完成后的article Bean,并把它插入magazine。 調用任意方法 我們不僅可以設置Bean的屬性,而且還可以調用堆棧內對象的任意方法。這通過(guò)CallMethodRule完成,我們只需指定方法名字,如有必要,再說(shuō)明調用的參數類(lèi)型和數量。CallParamRule用來(lái)定義傳遞給被調用函數的參數值,參數值可以從當前XML元素的命名的屬性獲取,也可以從當前元素包含的原始字符數據獲取。例如,在前面實(shí)現DigesterDriver的例子中,我們可以不用BeanPropertySetterRule,而是通過(guò)顯式調用屬性的set方法達到同樣的目的: digester.addCallMethod( "catalog/book/author", "setAuthor", 1 ); digester.addCallParam( "catalog/book/author", 0 );
上面的第一行代碼給出了要調用的方法(即setAuthor()),以及該調用需要的參數數量(即1)。第二行代碼的意思是從元素包含的字符數據獲取函數參數的值,把它作為參數數組的第一個(gè)傳入(即索引是0的數組元素)。如果我們指定了XML元素屬性的名稱(chēng)(例如digester.addCallParam( "catalog/book/author", 0, "author" );),則參數值將從當前元素的相應屬性值獲取。 這里必須注意的是,“digester.addCallMethod( "pattern", "methodName", 0 );”這個(gè)語(yǔ)句不是指定了一個(gè)不帶參數的方法調用,而是指定了帶有一個(gè)參數的方法調用,它的值就是當前XML元素的字符數據!這樣,我們又有了另一種替代BeanPropertySetterRule的辦法: digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );
如果要調用一個(gè)確實(shí)沒(méi)有參數的方法,必須采用如下形式:digester.addCallMethod( "pattern", "methodName" );。 標準規則概要 下面簡(jiǎn)要說(shuō)明所有標準規則。 創(chuàng )建 ObjectCreateRule:利用指定類(lèi)的默認構造函數,創(chuàng )建該類(lèi)的一個(gè)對象,并把對象壓入棧。當元素處理結束時(shí),對象被彈出。被實(shí)例化的類(lèi)可通過(guò)class對象或類(lèi)的全稱(chēng)給出。 FactoryCreateRule:利用指定的工廠(chǎng)類(lèi)創(chuàng )建一個(gè)對象,把對象壓入棧。對于沒(méi)有提供默認構造函數的類(lèi),這一規則很有用。用于該規則的工廠(chǎng)類(lèi)必須實(shí)現org.apache.commons.digester.ObjectCreationFactory接口。 設置屬性 SetPropertiesRule:利用指定名稱(chēng)的XML元素屬性值,設置頂層Bean的一個(gè)或者多個(gè)指定名稱(chēng)的屬性。XML元素的屬性名稱(chēng)和Bean的屬性名稱(chēng)以String[]數組形式傳入該規則(通常用來(lái)處理之類(lèi)的結構)。 BeanPropertySetterRule:把頂層Bean的指定名稱(chēng)的屬性設置成當前XML元素包含的字符數據。(通常用來(lái)處理<page>10</page>之類(lèi)的結構)。 SetPropertyRule:設置頂層Bean的一個(gè)屬性。無(wú)論是Bean屬性的名稱(chēng),還是賦予該屬性的值,都在當前XML元素中以屬性的形式指定,例如:<article key="page" value="10" />。 管理父/子關(guān)系 SetNextRule:彈出棧頂的對象,把它傳遞給緊接其下的另一個(gè)對象的指定名稱(chēng)的方法。通常用來(lái)把一個(gè)已經(jīng)初始化的Bean插入到父對象。 SetTopRule:把棧里面上數第二的對象傳遞給頂層的對象。當子對象提供了一個(gè)setParenet方法時(shí),這一規則很有用。 SetRootRule:調用棧底對象的一個(gè)方法,并把棧頂的對象作為參數傳入。 調用任意方法 CallMethodRule:調用頂層Bean的指定名稱(chēng)的方法。被調用的方法可以有任意多個(gè)參數,參數的值通過(guò)后繼的CallParamRule給出。 CallParamRule:表示方法調用的參數。參數的值或者取自指定名稱(chēng)的XML元素的屬性,或者是當前元素包含的原始字符數據。這個(gè)規則要求用一個(gè)整數指定它在參數列表中的位置。 通過(guò)XML指定規則 在前面的內容中,我們用程序代碼的方式指定模式和規則,這些模式和規則都是在編譯的時(shí)候就已經(jīng)確定,雖然從概念上來(lái)講比較簡(jiǎn)單,但卻不能說(shuō)盡善盡美:Digester框架的總體目標是在運行時(shí)識別和處理各種數據結構,但如果我們用編程的方法指定模式和規則,則所有行為在編譯時(shí)已經(jīng)固定!如果Java源程序中包含了大量固定的字符串,通常意味著(zhù)程序在執行某些配置操作,這部分操作可以被(或許是應該被)延遲到運行時(shí)進(jìn)行。 org.apache.commons.digester.xmlrules包解決了這個(gè)問(wèn)題。這個(gè)包提供了一個(gè)DigesterLoader類(lèi),它能夠從XML文檔讀取模式/規則對,返回配置好的Digester對象。用來(lái)配置Digester對象的XML文檔必須遵從digester-rules.dtd,這個(gè)DTD是xmlrules包的一部分。 下面就是本文例子的配置文件rules.xml。有幾點(diǎn)必須說(shuō)明。 首先,模式可以用兩種方式指定:或者使用<pattern>元素,或者通過(guò)代表規則的XML元素的屬性。這兩種辦法可以混合使用,且<pattern>元素是可以嵌套的。其次,<alias>元素和<set-properties-rule>一起使用,用來(lái)把XML屬性映射到Bean屬性。最后,就當前發(fā)行的Digester軟件包而言,我們不能在配置文件中指定BeanPropertySetterRule,正如前面所介紹的,我們用CallMethodRule來(lái)達到同樣的目標。 <?xml version="1.0"?> <digester-rules> <object-create-rule pattern="catalog" classname="Catalog" /> <set-properties-rule pattern="catalog" > <alias attr-name="library" prop-name="library" /> </set-properties-rule> <pattern value="catalog/book"> <object-create-rule classname="Book" /> <call-method-rule pattern="author" methodname="setAuthor" paramcount="0" /> <call-method-rule pattern="title" methodname="setTitle" paramcount="0" /> <set-next-rule methodname="addBook" /> </pattern> <pattern value="catalog/magazine"> <object-create-rule classname="Magazine" /> <call-method-rule pattern="name" methodname="setName" paramcount="0" /> <pattern value="article"> <object-create-rule classname="Article" /> <set-properties-rule> <alias attr-name="page" prop-name="page" /> </set-properties-rule> <call-method-rule pattern="headline" methodname="setHeadline" paramcount="0" /> <set-next-rule methodname="addArticle" /> </pattern> <set-next-rule methodname="addMagazine" /> </pattern> </digester-rules>
現在,所有實(shí)際的操作都轉移到了Digester和DigesterLoader類(lèi),XmlRulesDriver類(lèi)就變得相當簡(jiǎn)單。運行下面的XmlRulesDriver時(shí),在第一個(gè)命令行參數中指定目錄文檔的名字,在第二個(gè)參數中指定rules.xml(注意,DigesterLoader不是從File或者org.xml.sax.InputSource讀取rules.xml文件,而是要求指定一個(gè)URL,因此,下面代碼中File引用被轉換成了等價(jià)的URL)。 import org.apache.commons.digester.*; import org.apache.commons.digester.xmlrules.*; import java.io.*; import java.util.*; public class XmlRulesDriver { public static void main( String[] args ) { try { File input = new File( args[0] ); File rules = new File( args[1] ); Digester digester = DigesterLoader.createDigester( rules.toURL() ); Catalog catalog = (Catalog)digester.parse( input ); System.out.println( catalog.toString() ); } catch( Exception exc ) { exc.printStackTrace(); } } }
結束語(yǔ):本文對Jakarta Commons Digester的介紹就到這里結束。當然,還有許多內容這里尚未涉及。其中一個(gè)在這里忽略的主題是XML名稱(chēng)空間:Digester允許把規則定義成只能對某一個(gè)名稱(chēng)空間內定義的元素起作用。 另外,我們簡(jiǎn)單地提及了通過(guò)擴展Rule類(lèi)開(kāi)發(fā)定制規則的問(wèn)題。按照習慣,Digester類(lèi)提供了push()、peek()和pop()方法,使得開(kāi)發(fā)者能夠自由地直接操作解析棧。 參考: Jakarta Commons Digester Homepage Jakarta Struts Homepage