xml文件解析辦法

 xml文件解析辦法
xml是爲了提高web數據交換量而出現的,雖然他現在web應用中並不廣泛,但是由於他的格式良好,經常被用做配置文件的格式。比如tomcat的主配置文件server.xml,web.xml等等。
 
首先我們看一下需求。我們的目的主要是提取xml文件中的特定內容,又因爲xml文件本身的格式良好,這種查詢是非常有規律的,非常有利於我們找到需要的信息。有時我們還可能把特定信息寫回xml中,但是這種需求並不是必需的,因爲配置文件都不會太大,我們完全可以通過手工辦法進行修改。
 
對xml進行解析的標準有兩種,sax以及dom。
首先這兩種標準並不是針對java的,他們在各種語言環境下都可以實現。dom是真正的國際標準。sax是事實的標準,他不由任何商業組織維護,而是由一個非商業的組織在運作。就像iso7層模型和tcp/ip一樣,雖然sax不是正式的標準,但是一點不影響他在xml解析領域的地位。
 
dom實現的原理是把整個xml文檔一次性讀出,放在一個樹型結構裏。在需要的時候,查找特定節點,然後對節點進行讀或寫。他的主要優勢是實現簡單,讀寫平衡;缺點是比較佔內存,因爲他要把整個xml文檔都讀入內存,文件越大,這種缺點就越明顯。
 
sax的實現方法和dom不同。他只在xml文檔中查找特定條件的內容,並且只提取需要的內容。這樣做佔用內存小,靈活,正好滿足我們的需求。他的缺點就是寫,有些資料介紹了寫入的方法,但是我感覺這對本例沒有必要。後面主要講解用sax2.0實現xml文檔解析。
 
 
首先講一下sax的工作流程,以下面的book.xml爲例(不做dtd定義的驗證,如果有這方面需求,可以查更詳細的文檔)。
<?xml version="1.0"?>
<books>
       <book type="computer">
              <title>java 2</title>
              <page>600</page>
              <author>Jim</author>
       </book>
              <book type="fiction">
              <title>fly to moon</title>
              <page>300</page>
              <author>Vernia</author>
       </book>
</books>
 
1.我們需要註冊一個實現了sax標準的解析器,sun,java,apache等廠商和組織都實現了自己的解析器,大家可以直接拿過來用。
2.然後告訴解析器,我們會用哪個xml解析程序來處理xml文檔。這個解析程序是由我們自己來實現的。
3在解析開始時,解析器會觸發解析程序的startDocument()方法,告訴應用程序,文檔解析開始了。
要注意以下幾點:
1.區分解析器,解析程序的概念。
2.sax實現是事件驅動的,由解析器觸發應用程序,而不是由應用程序來調用解析器。這和ui裏的Actionlistener實現差不多。
3.startDocument()方法是由ContentHandler接口定義的,我們必須要實現他。xml解析程序就是用來實現這些方法的。爲什麼要這麼做?因爲sax不會定義在接收到方法觸發後,會採取什麼動作。只有我們自己才知道在解析的過程中,我們會做什麼。不明白沒有關係,再往下看。
4.當遇到<books>後,解析器會觸發解析程序的startElement()方法,告訴應用程序,我遇到一個開始的標籤。這個startElement()方法也是由ContentHandler接口定義的,他只是提醒應用程序他遇到一個標籤的開始,至於是什麼標籤,他不知道,也不想知道。而由xml解析程序實現了的startElement()方法,功能就大了。比如我們可以判斷這個標籤的內容是什麼,如果是books,好,正是我們需要的,要記到內存裏;如果不是,放棄,繼續往下走。
5.過了<books>後,解析器會觸發解析程序的characters()方法,告訴應用程序,我遇到了標籤的內容。同樣的原理,由xml解析程序實現了的characters()方法會處理這個內容。當然瞭如果是我們需要的,就留下;如果不是就放棄。在這個例子裏,<books>後面是空格,沒有實際價值。
6.再往下遇到了<book type="computer">標籤,同樣觸發的是startElement()方法。以此類推,在標籤結束時,會觸發endElement()方法,在文檔結束時會觸發endDocument()方法。至於每次觸發一個方法後,產生什麼動作,都是由我們的解析程序來控制的。
 
下面是一個程序例子,解析book.xml文件,把是book,並且類型是fiction的內容挑出來。
 
文件XmlParseAction,接收xml文件輸入,調用xml解析程序,並返回結果。
package myb.hi.sample.action;
 
import java.io.File;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.DispatchAction;
import org.xml.sax.*;
import org.apache.xerces.jaxp.*;
import myb.hi.sample.form.XmlFileForm;
import myb.hi.sample.business.*;
import javax.servlet.*;
 
public class XmlParseAction extends DispatchAction{
 
       /**
        * Accept the jsp request, parset the xml file
        * @param mapping
        * @param form
        * @param request
        * @param response
        * @return ActionForward
        */
       public ActionForward xmlParse(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception{
              log.info("Start xml parse: XmlParseAction.xmlParse()");
              XmlFileForm userForm=(XmlFileForm)form;
              try{
                     //註冊一個解析器,我用的是apache xerces的,所以導入了org.apache.xerces.jaxp.*
                     XMLReader xmlReader=new SAXParserFactoryImpl().newSAXParser().getXMLReader();
                     //聲明一個XmlParseBusiness類,這個類就是我們自己的xml解析程序
XmlParseBusiness xmlParseBusiness=new XmlParseBusiness();
//把我們自己的解析程序註冊到解析器,告訴解析器誰來接收事件
                     xmlReader.setContentHandler(xmlParseBusiness);
                     //解析文件
                     // userForm.getFileName()是指定的xml文件名稱,本例子中就是book.xml
                     // getPath(servlet)方法找到book.xml的路徑
                     //.toURL().toString()把文件轉換成url形式
                     //parse()方法的參數爲inputSource,可以是字符流,字節流或文件的url字符串,所以必須要把以上幾種轉換成inputSource
                     xmlReader.parse(new File(getPath(servlet)+"/WEB-INF/classes/"+userForm.getFileName()).toURL().toString());
                    
                     request.setAttribute("iValue",xmlParseBusiness.getI());
                     request.setAttribute("bookList",xmlParseBusiness.getBookList());
                     log.info("End xml parse: XmlParseAction.xmlParse()");
              }catch(Exception ex){
                     System.out.println("Action Exception: xmlParse, caused by: "+ex);
              }
              return mapping.findForward("xmlParseResult");
       }
 
       /**
        * Return the absolute path of the servlet
        */
       //這個方法就是查找此應用程序的絕對路徑,供解析用,因爲sax不會自動識別上下文路徑
       private String getPath(Servlet servlet){
              String strPath=servlet.getServletConfig().getServletContext().getRealPath("");
              return strPath;
       }
 
}
 
 
文件XmlParseBusiness,接收解析事件,查找符合book,類型是fiction的內容
 
package myb.hi.sample.business;
 
import java.util.*;
 
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;
// DefaultHandler是一個實現了ContentHander等接口的類,繼承這個類就不用挨個實現接口了 
public class XmlParseBusiness extends DefaultHandler{
       //定義整形變量i,存放共有幾本書
       private int i=0;
       //接收標籤內容,因爲characters()方法是以字符流的形式接收內容,如果用string,有可能造成內容缺失
       private StringBuffer xmlContent=new StringBuffer();
       //聲明一個mapp對象,並不做初始化
       private HashMap bookMap=null;
       //定義list對象,存放書的列表
       private List bookList=new ArrayList();
       //定義堆棧,存放上下文信息。在解析過程中,我們需要記錄一些信息,比如我現在是在<book></book>之間,等等。堆棧是一個很好的辦法,他採用後進先出,後面定義的方法,有他的實現
       private Stack context=new Stack();
      
       /**
        * Default Constructor
        */
       public XmlParseBusiness(){
             
       }
      
       /**
        * Start Document
        */
       public void startDocument(){
             
       }
      
       /**
        * End Document
        */
       public void endDocument(){
             
       }
      
       /**
        * Start Element
        * @param uri
        * @param localName
        * @param qName
        * @param attr
        * @return void
        */
       public void startElement(String uri, String localName,String qName,Attributes attribute) throws SAXException{
              //聲明一個ElementDetails類的實例,這個類存放的就是標籤信息,目的是放到堆棧中
              ElementDetails elem=new ElementDetails(uri, localName,qName, attribute);
              //把信息推入堆棧
              context.push(elem);
              //如果標籤是<book>,就執行下面代碼
              if(qName.equals("book")){
                     //如果book類型是fiction,就執行下面代碼
                     if(isFictionBook()){
                            //初始化bookMap爲一個map對象實例
                            bookMap=new HashMap();
                     }
                     //給i自增,代表又多了一本書
                     i++;
              }
              //給stringbuffer清空,以便接收新內容
              xmlContent.setLength(0);
       }
      
       /**
        * End Element
        */
       public void endElement(String uri, String localName,String qName) throws SAXException{
//根據上下文做判斷,如果還在<book></book>之間
              if(isBook()){
                     //如果是</title>
                     if(qName.equals("title")){
                            //把書名內容放到map裏
                            bookMap.put("title",xmlContent.toString());
                     //如果是</page>
                     }else if(qName.equals("page")){
                            //把書的頁數放到map裏
                            bookMap.put("page",xmlContent.toString());
                     //如果是</author>
                     }else if(qName.equals("author")){
                            //把作者名稱放到map裏
                            bookMap.put("author",xmlContent.toString());
                  //如果是</book>
                     }else if(qName.equals("book")){
                            //說明book標籤結束了,把整本書放到列表裏
                            bookList.add(bookMap);
                     }
              }
              //給stringbuffer清空,以便接收新內容
              xmlContent.setLength(0);
              //把最後進來的對象彈出堆棧,因爲他的標籤已經結束,沒有再存在的必要了(後進先出
              context.pop();
       }
      
       /**
        * Get i value
        */
       public int getI(){
              return i;
       }
      
       /**
        *Handle the context between the element
        *@param ch[]
        *@param start
        *@param length
        *@return void
        */
        public void characters (char ch[], int start, int length) throws SAXException{
//把標籤內容存到一個stringbuffer對象裏,以備處理
               xmlContent.append(ch,start,length);
        }
      
       /**
       * Get strA value
       */
       public String getContent(){
              return xmlContent.toString();
       }
      
       /**
        * Return bookList
        */
       public List getBookList(){
              return bookList;
       }
      
       /**
        * Define a internal Class, for transfor the element details
        */
       //定義一個內部類,接收標籤元素信息,供堆棧用
       private class ElementDetails {
              private String uri;
              private String localName;
              private String qName;
              private Attributes attribute;
             
              /*
               * Defalut Constructor
               */
              public ElementDetails(String uri, String localName,String qName,Attributes attribute){
                     this.uri=uri;
                     this.localName=localName;
                     this.qName=qName;
                     //注意Attributes是一個接口,所以要把他轉化爲一個AttributesImpl對象
                     this.attribute=new AttributesImpl(attribute);
              }
 
 
              public Attributes getAttribute() {
                     return attribute;
              }
 
 
              public void setAttribute(Attributes attribute) {
                     this.attribute = new AttributesImpl(attribute);
              }
 
 
              public String getLocalName() {
                     return localName;
              }
 
              public void setLocalName(String localName) {
                     this.localName = localName;
              }
 
              public String getQName() {
                     return qName;
              }
 
              public void setQName(String name) {
                     qName = name;
              }
 
              public String getUri() {
                     return uri;
              }
 
              public void setUri(String uri) {
                     this.uri = uri;
              }    
       }
      
       /**
        * Estimate the element content, if it's 'book', return true, otherwise, false
        */
       //利用堆棧,判斷是否還在<book></book>之間
       private Boolean isBook(){
              //判斷堆棧裏對象數目,並做循環
              for(int p=context.size()-1;p>=0;p--){
                     //把位置p出的對象取出來,是一個ElementDetails類的實例
                     ElementDetails elem=(ElementDetails)context.elementAt(p);
                     //如果這個標籤的信息是<book>,返回true,不用再往下循環了。因爲</book>後,會被彈出堆棧,所以不會有2個<book>在堆棧裏。除非xml不規範,有相同的標籤嵌套出現,像<book><book></book></book>這樣,但是在這裏因爲後進先出的原則不會出問題,相反程序裏的其他判斷就要出亂子了
                     if(elem.getQName().equals("book")){
                            return true;
                     }
              }
              return false;
       }
 
       /**
        * Estimate the element content, if it's a "fiction book", return true, otherwise, false
        */
       private Boolean isFictionBook(){
              for(int p=context.size()-1;p>=0;p--){
                     ElementDetails elem=(ElementDetails)context.elementAt(p);
                     if(elem.getQName().equals("book") && elem.getAttribute().getValue("type").equals("fiction")){
                            return true;
                     }
              }
              return false;
       }
}
 
注意:上面程序只實現了ContentHandler接口的部分方法。並且對帶dtd驗證的xml解析,以及錯誤處理沒有做講解和實例,感興趣的朋友可以自己去參考文檔。不過對於解析簡單的xml配置文檔,這些也足夠了。
 
Sax的api包含在j2se的api文擋中,在org.xml.sax包裏。apache的xerces包,ibm,oracle等廠商發行的xml解析器包都會包含他。
Xerces是apache的子項目,專門做xml解析,他不僅包含了sax實現,也包含了dom實現。
最後講一下sax1和sax2的幾點差別:
1. sax2中,用XMLReader代替了Parser
2. sax2支持namespace
3. sax2中用ContentHandler代替了DocumentHandler
4. sax2 DefaultHandler代替了HandlerBase
發佈了75 篇原創文章 · 獲贊 3 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章