用Digester簡化XML文檔處理

          Posted on 2009-08-26 20:27 林光炎 閱讀(431) 評論(0)  編輯  收藏 所屬分類: JAVA
           

          Digester框架屬于Jakarta Commons,它以規則和模式為基礎處理XML文檔。與SAX和DOM之類的標準API相比,Digester不涉及太多的細節問題,非常適合于對XML文檔進行簡單的處理。

          在Java和XML開發中,一個常見的任務是把XML文檔轉換成對應的Java Bean對象的層次結構。人們經常用標準的SAX和DOM API來完成這個任務。雖然這兩種API都很強大和靈活,但對于某些簡單的任務來說,它們顯得操作層次太低,也就是說,涉及了太多的細節問題。Jakarta Digester框架能夠很好地滿足這類場合的需要。

          Digester框架簡介

          Jakarta的Digester框架從Struts框架發展而來,原先被用來處理struts-config.xml配置文件,但很快人們認識到它有著更廣泛的用途,把它轉入了Jakarta Commons項目。Jakarta Commons的目標是提供一個“可重用Java組件的倉庫”。Digester最新的版本是1.3,于2002年8月13日發布。

          Digester框架允許開發者指定一組動作,當解析器在XML文檔中發現某些特定的簡單模式時動作被執行。Digester框架帶有10個預定義的規則(Rule),涵蓋了unmarshalling XML(例如創建Bean或設置Bean屬性)的大多數需求( marshalling的原意是指“配制整齊,編組列車”,marshalling是在內存中為Java對象生成XML描述文檔的過程,而unmarshalling是指把XML形式的描述轉換到可用Java代碼操作的對象的過程,我們稱之為“反配制”),但必要時用戶可以定義和實現自己的規則。

          在本文的例子中,我們將反配制下面這個XML文檔:

          <?xml version="1.0"?>
                      <catalog library="somewhere">
                      <book>
                      <author>Author 1</author>
                      <title>Title 1</title>
                      </book>
                      <book>
                      <author>Author 2</author>
                      <title>His One Book</title>
                      </book>
                      <magazine>
                      <name>Mag Title 1</name>
                      <article page="5">
                      <headline>Some Headline</headline>
                      </article>
                      <article page="9">
                      <headline>Another Headline</headline>
                      </article>
                      </magazine>
                      <book>
                      <author>Author 2</author>
                      <title>His Other Book</title>
                      </book>
                      <magazine>
                      <name>Mag Title 2</name>
                      <article page="17">
                      <headline>Second Headline</headline>
                      </article>
                      </magazine>
                      </catalog>

          下面是Bean的代碼。注意使用Digester框架時,Bean類必須定義成public。

          import java.util.Vector;
                      public class Catalog {
                      private Vector books;
                      private Vector magazines;
                      public Catalog() {
                      books = new Vector();
                      magazines = new Vector();
                      }
                      public void addBook( Book rhs ) {
                      books.addElement( rhs );
                      }
                      public void addMagazine( Magazine rhs ) {
                      magazines.addElement( rhs );
                      }
                      public String toString() {
                      String newline = System.getProperty( "line.separator" );
                      StringBuffer buf = new StringBuffer();
                      buf.append( "--- Books ---" ).append( newline );
                      for( int i=0; i<books.size(); i++ ){
                      buf.append( books.elementAt(i) ).append( newline );
                      }
                      buf.append( "--- Magazines ---" ).append( newline );
                      for( int i=0; i<magazines.size(); i++ ){
                      buf.append( magazines.elementAt(i) ).append( newline );
                      }
                      return buf.toString();
                      }
                      }
                      //===================================================
                      public class Book {
                      private String author;
                      private String title;
                      public Book() {}
                      public void setAuthor( String rhs ) { author = rhs; }
                      public void setTitle(  String rhs ) { title  = rhs; }
                      public String toString() {
                      return "Book: Author='" + author + "' Title='" + title + "'";
                      }
                      }
                      //===================================================
                      import java.util.Vector;
                      public class Magazine {
                      private String name;
                      private Vector articles;
                      public Magazine() {
                      articles = new Vector();
                      }
                      public void setName( String rhs ) { name = rhs; }
                      public void addArticle( Article a ) {
                      articles.addElement( a );
                      }
                      public String toString() {
                      StringBuffer buf = new StringBuffer( "Magazine: Name='" + name + "' ");
                      for( int i=0; i<articles.size(); i++ ){
                      buf.append( articles.elementAt(i).toString() );
                      }
                      return buf.toString();
                      }
                      }
                      //===================================================
                      public class Article {
                      private String headline;
                      private String page;
                      public Article() {}
                      public void setHeadline( String rhs ) { headline = rhs; }
                      public void setPage(     String rhs ) { page     = rhs; }
                      public String toString() {
                      return "Article: Headline='" + headline + "' on page='" + page + "' ";
                      }
                      }

          1 2 下一頁>>

          Digester框架以模式(Pattern)和規則(Rule)為基礎處理輸入的XML。模式必須與XML元素匹配,包括其名字和在文檔樹內的位置。描述匹配模式的語法類似于XPath匹配模式,例如:catalog模式匹配頂層的<catalog>元素,catalog/book模式匹配直接嵌套在<catalog>元素內的<book>元素(但不匹配文檔內其他位置的<book>元素)

          所有的模式都必須指定其完整名稱——從根元素開始的完整路徑。唯一的例外是包含通配符(“*”)的模式,例如*/name模式匹配XML文檔內任何位置的<name>元素。但是根元素不必特別指出,因為所有的路徑都是從根元素開始的絕對路徑。

          當Digester發現一個指定的模式,它就執行關聯的任務。由此可見,Digester框架顯然與SAX解析器有著密切的關系(實際上,Digester類實現了org.xml.sax.ContentHandler,并維護著解析棧)。所有在Digester中使用的規則必須擴展org.apache.commons.digester.Rule,后者本身提供了一些類似于SAX的ContentHandler回調函數的方法。例如,當遇到匹配元素的開始標記和結束標記時,begin()方法和end()方法將分別被調用。

          一旦遇到匹配元素的內容,body()方法被調用;最后被調用的方法是finish(),這個方法在匹配元素的結束標記處理完畢之后被調用,用來執行可能需要的事后清理任務。然而,大多數時候我們不必關注這些方法,因為框架提供的標準規則很可能已經提供了所有必需的功能。

          要反配制一個文檔,首先創建一個org.apache.commons.digester.Digester類的實例,如果必要的話,進行一些配置操作,指定必需的模式和規則,最后向parse()方法傳遞一個XML文件的引用。下面的DigesterDriver示范了這一處理過程(必須在命令行上指定輸入XML文檔的名稱)。

          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();
                      }
                      }
                      }

          在上面的代碼中,我們首先創建了Digester類的一個實例digester,然后指定它不要用DTD驗證XML文檔的合法性——這是因為我們沒有為XML文檔定義DTD。接下來,我們指定了模式和關聯的規則:ObjectCreateRule創建指定類的一個實例,并將它壓入解析棧。SetPropertiesRule把Bean屬性設置成當前XML元素的屬性值——規則的第一個參數是XML屬性的名稱,第二個參數是Bean屬性的名稱。

          SetPropertiesRule獲取的是XML屬性的值,而BeanPropertySetterRule獲取的是位于當前元素內的原始字符數據值。使用BeanPropertySetterRule時不必指定要設置的Bean屬性名字,默認是當前XML元素的名稱。在上面的例子中,在匹配catalog/magazine/article/headline模式的規則定義中使用的就是默認值。最后,SetNextRule彈出解析棧頂部的對象,并把該對象傳遞給它下面對象的指定名稱的方法——通常用來把一個配置完畢的Bean插入父對象。

          注意,我們可以為同一個模式注冊多個規則。如果注冊了多個規則,則這些規則按照它們被加入到Digester的次序執行,例如,如果要處理catalog/magazine/article的

          元素,我們首先創建合適的article Bean,然后設置page屬性,最后彈出完成后的article Bean,并把它插入magazine。

          調用任意方法

          我們不僅可以設置Bean的屬性,而且還可以調用堆棧內對象的任意方法。這通過CallMethodRule完成,我們只需指定方法名字,如有必要,再說明調用的參數類型和數量。CallParamRule用來定義傳遞給被調用函數的參數值,參數值可以從當前XML元素的命名的屬性獲取,也可以從當前元素包含的原始字符數據獲取。例如,在前面實現DigesterDriver的例子中,我們可以不用BeanPropertySetterRule,而是通過顯式調用屬性的set方法達到同樣的目的:

          digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );
                      digester.addCallParam( "catalog/book/author", 0 );

          上面的第一行代碼給出了要調用的方法(即setAuthor()),以及該調用需要的參數數量(即1)。第二行代碼的意思是從元素包含的字符數據獲取函數參數的值,把它作為參數數組的第一個傳入(即索引是0的數組元素)。如果我們指定了XML元素屬性的名稱(例如digester.addCallParam( "catalog/book/author", 0, "author" );),則參數值將從當前元素的相應屬性值獲取。

          這里必須注意的是,“digester.addCallMethod( "pattern", "methodName", 0 );”這個語句不是指定了一個不帶參數的方法調用,而是指定了帶有一個參數的方法調用,它的值就是當前XML元素的字符數據!這樣,我們又有了另一種替代BeanPropertySetterRule的辦法:

          digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );

          如果要調用一個確實沒有參數的方法,必須采用如下形式:digester.addCallMethod( "pattern", "methodName" );。

          標準規則概要

          下面簡要說明所有標準規則。

          創建

          ObjectCreateRule:利用指定類的默認構造函數,創建該類的一個對象,并把對象壓入棧。當元素處理結束時,對象被彈出。被實例化的類可通過class對象或類的全稱給出。

          FactoryCreateRule:利用指定的工廠類創建一個對象,把對象壓入棧。對于沒有提供默認構造函數的類,這一規則很有用。用于該規則的工廠類必須實現org.apache.commons.digester.ObjectCreationFactory接口。

          設置屬性

          SetPropertiesRule:利用指定名稱的XML元素屬性值,設置頂層Bean的一個或者多個指定名稱的屬性。XML元素的屬性名稱和Bean的屬性名稱以String[]數組形式傳入該規則(通常用來處理

          之類的結構)。

          BeanPropertySetterRule:把頂層Bean的指定名稱的屬性設置成當前XML元素包含的字符數據。(通常用來處理<page>10</page>之類的結構)。

          SetPropertyRule:設置頂層Bean的一個屬性。無論是Bean屬性的名稱,還是賦予該屬性的值,都在當前XML元素中以屬性的形式指定,例如:<article key="page" value="10" />

          管理父/子關系

          SetNextRule:彈出棧頂的對象,把它傳遞給緊接其下的另一個對象的指定名稱的方法。通常用來把一個已經初始化的Bean插入到父對象。

          SetTopRule:把棧里面上數第二的對象傳遞給頂層的對象。當子對象提供了一個setParenet方法時,這一規則很有用。

          SetRootRule:調用棧底對象的一個方法,并把棧頂的對象作為參數傳入。

          調用任意方法

          CallMethodRule:調用頂層Bean的指定名稱的方法。被調用的方法可以有任意多個參數,參數的值通過后繼的CallParamRule給出。

          CallParamRule:表示方法調用的參數。參數的值或者取自指定名稱的XML元素的屬性,或者是當前元素包含的原始字符數據。這個規則要求用一個整數指定它在參數列表中的位置。

          通過XML指定規則

          在前面的內容中,我們用程序代碼的方式指定模式和規則,這些模式和規則都是在編譯的時候就已經確定,雖然從概念上來講比較簡單,但卻不能說盡善盡美:Digester框架的總體目標是在運行時識別和處理各種數據結構,但如果我們用編程的方法指定模式和規則,則所有行為在編譯時已經固定!如果Java源程序中包含了大量固定的字符串,通常意味著程序在執行某些配置操作,這部分操作可以被(或許是應該被)延遲到運行時進行。

          org.apache.commons.digester.xmlrules包解決了這個問題。這個包提供了一個DigesterLoader類,它能夠從XML文檔讀取模式/規則對,返回配置好的Digester對象。用來配置Digester對象的XML文檔必須遵從digester-rules.dtd,這個DTD是xmlrules包的一部分。

          下面就是本文例子的配置文件rules.xml。有幾點必須說明。

          首先,模式可以用兩種方式指定:或者使用<pattern>元素,或者通過代表規則的XML元素的屬性。這兩種辦法可以混合使用,且<pattern>元素是可以嵌套的。其次,<alias>元素和<set-properties-rule>一起使用,用來把XML屬性映射到Bean屬性。最后,就當前發行的Digester軟件包而言,我們不能在配置文件中指定BeanPropertySetterRule,正如前面所介紹的,我們用CallMethodRule來達到同樣的目標。

          <?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>

          現在,所有實際的操作都轉移到了Digester和DigesterLoader類,XmlRulesDriver類就變得相當簡單。運行下面的XmlRulesDriver時,在第一個命令行參數中指定目錄文檔的名字,在第二個參數中指定rules.xml(注意,DigesterLoader不是從File或者org.xml.sax.InputSource讀取rules.xml文件,而是要求指定一個URL,因此,下面代碼中File引用被轉換成了等價的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();
                      }
                      }
                      }

          結束語:本文對Jakarta Commons Digester的介紹就到這里結束。當然,還有許多內容這里尚未涉及。其中一個在這里忽略的主題是XML名稱空間:Digester允許把規則定義成只能對某一個名稱空間內定義的元素起作用。

          另外,我們簡單地提及了通過擴展Rule類開發定制規則的問題。按照習慣,Digester類提供了push()、peek()和pop()方法,使得開發者能夠自由地直接操作解析棧。

          參考:

          Jakarta Commons Digester Homepage

          Jakarta Struts Homepage

          posts - 104, comments - 33, trackbacks - 0, articles - 0

          Copyright © 林光炎

          主站蜘蛛池模板: 柳州市| 平原县| 蒙山县| 祁连县| 石门县| 永宁县| 安陆市| 高清| 开封县| 璧山县| 延庆县| 辽源市| 平邑县| 麟游县| 同仁县| 山东| 酉阳| 周至县| 洛浦县| 尖扎县| 同仁县| 墨竹工卡县| 赤壁市| 金溪县| 安徽省| 高碑店市| 阿拉尔市| 米林县| 盖州市| 阿瓦提县| 鲁山县| 扎赉特旗| 汉中市| 宝山区| 石渠县| 策勒县| 铁岭市| 枞阳县| 高州市| 汕尾市| 沾益县|