深入分析android中用SAX解析XML檔案並錯誤修正

來源:互聯網
上載者:User

在android中解析XML檔案有很多方法,今天主要介紹下SAX解析。

1、SAX簡介

SAX是基於事件驅動模型,可以捕獲到讀取文檔過程中產生的事件,比如開始文檔、結束文檔、開始元素、結束元素、常值內容事件等。通過定義一個事件處理器,在這些事件觸發後,來實現資料的擷取。通過使用XMLReader類來註冊事件處理器,在Android中有如下4個事件處理器介面,如:

補充:事件驅動模型是事件來源發出事件,監聽器捕獲事件並做出相應的過程。這裡,事件來源就是發出事件的對象,監聽器則是對事件感興趣的對象。

圖片引用:http://www.cnblogs.com/felix-hua/archive/2012/01/10/2317404.html

註:在實際應用中不需要全部實現這4個介面,SDK提供了DefaultHandler類來處理。DefaultHandler類已經實現了這4個介面,使用者只需繼承該類即可。

XMLReader中的方法:

//註冊處理XML文檔解析事件ContentHandlerpublic void setContentHandler(ContentHandler handler)//開始解析一個XML文檔public void parse(InputSorce input) throws SAXException

在上述四個介面中,最重要的就是ContentHandler這個介面,下面是對這個介面方法的詳細說明:

//開始XML文檔的回呼函數,在文檔開頭時調用,其中可做預先處理工作public void startDocument()throws SAXException//元素開始標籤的回呼函數,處理元素開始事件。//參數:namespacesURI為元素所在的名稱空間,localName為不帶命名空間首碼的標籤名,qName為帶命名空間首碼的標籤名,atts為元素的屬性名稱和值集合public void startElement(String namespacesURI , String localName , String qName , Attributes atts) throws SAXException//元素結束標籤的回呼函數,處理元素結束事件。public void endElement(String namespacesURI , String localName , String qName) throws SAXException//處理元素的字元內容,從參數中可以獲得內容//參數:ch為檔案的字串內容,start為讀到的字串在這個數組中的起始位置,length為讀到的字串在這個數組中的長度.public void characters(char[] ch , int start , int length)  throws SAXException//結束XML文檔的回呼函數,在文檔結束時調用,其中可做一些回收工作。public void endElement(String uri, String localName, String qName)throws SAXException

2、SAX實現解析的步驟

//1、建立一個工廠類SAXParserFactory,代碼如下:SAXParserFactory factory = SAXParserFactory.newInstance();//2、讓工廠類產生一個SAX的解析類SAXParser,代碼如下:SAXParser parser = factory.newSAXParser();//3、從SAXPsrser中得到一個XMLReader執行個體,代碼如下:XMLReader reader = parser.getXMLReader();//4、自己實現一個事件處理類handler,並將之註冊到XMLReader中,一般最重要的就是ContentHandler。代碼如下:ParserPerson parserPerson = new ParserPerson();//自己實現的handler,詳細代碼後面有介紹reader.setContentHandler(handler);//5、將一個xml文檔或者資源變成一個java可以處理的InputStream流in後,開始解析,代碼如下:parser.parse(in);  

3、SAX的優缺點:

優點:解析速度快,佔用記憶體少。解析時不用調入整個文檔,所有佔用資源少。尤其在嵌入式環境中,如android,極力推薦使用SAX解析。

缺點:串流,因而不像DOM解析一樣將文檔長期駐留在記憶體中,資料不是持久的。需要在事件中儲存資料。

4、應用執行個體:

代碼中所使用的類如下,另外考慮代碼的可擴充性,本例採用了策略模式。對每個事件處理器進行封裝,將每一個事件處理器封裝到一個具有共同介面的獨立類ParseXML中。從而使得它們可以在不影響用戶端情況下發生,也能相互替換。

//Person:人員基本資料,是一個bean類。//ParseXML類:抽象類別,定義一個抽象方法,用來擷取解析的結果。//ParsePersonXML類:ParseXML的子類,其含有繼承DefaultHandler類的內嵌類ParsePerson,該類實現了SAX解析的事件處理器。//ContextClient類:封裝事件處理器,屏蔽高層模組對事件處理器的直接存取。//MainActivity類: 通過一個TextView來顯示解析的結果。

首先定義一個XML檔案PersonItem,其目錄為res/raw/person_item.xml,內容如下:

<?xml version="1.0" encoding="UTF-8"?><Persons><Person  account="zhangsan">    <name>張三</name>    <email>zhangsan@gmail.com</email>    <tel>18866661111</tel>    <boss>manager</boss>    <newProject>1</newProject>    <superPower>1</superPower></Person><Person account="lisi">    <name>李四</name>    <email>lisi@gmail.com</email>    <tel>16688883333</tel>    <boss>manager</boss>    <newProject>0</newProject>    <superPower>0</superPower></Person></Persons>

然後相應代碼實現:

Person類:

public class Person {  String mName;//姓名  String mAccount;//賬戶  String mTel;//電話  String mEmail;//郵箱  String mBoss;//上司  String mCompetence;//超級許可權  String mProprietor;//建立項目權利  public Person()  {    this.mAccount = "";    this.mName = "";    this.mTel = "";    this.mEmail = "";    this.mBoss = "";    this.mCompetence = "";  }  public Person(String paramString1, String paramString2,  String paramString3, String paramString4,   String paramString5, String paramString6)  {    this.mAccount = paramString1;    this.mName = paramString2;    this.mTel = paramString3;    this.mEmail = paramString4;    this.mBoss = paramString5;    this.mCompetence = paramString6;  }  public String getmAccount()  {    return this.mAccount;  }  public String getmBoss()  {    return this.mBoss;  }  public String getmCompetence()  {    return this.mCompetence;  }  public String getmEmail()  {    return this.mEmail;  }  public String getmName()  {    return this.mName;  }  public String getmProprietor()  {    return this.mProprietor;  }  public String getmTel()  {    return this.mTel;  }  public void setmAccount(String paramString)  {    this.mAccount = paramString;  }  public void setmBoss(String paramString)  {    this.mBoss = paramString;  }  public void setmCompetence(String paramString)  {    this.mCompetence = paramString;  }  public void setmEmail(String paramString)  {    this.mEmail = paramString;  }  public void setmName(String paramString)  {    this.mName = paramString;  }  public void setmProprietor(String paramString)  {    this.mProprietor = paramString;  }  public void setmTel(String paramString)  {    this.mTel = paramString;  }}

ParseXML類:

import java.io.InputStream;import java.util.ArrayList;public abstract class ParseXML {public abstract ArrayList<Object> getXMLInfoByList(InputStream in);}

ParsePersonXML類:

import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import android.util.Log;public class ParsePersonXML extends ParseXML {@Overridepublic ArrayList<Object> getXMLInfoByList(InputStream in){Log.d("ParsePersonXML","not execution yet");ParserPerson parserPerson = new ParserPerson();SAXParserFactory localSAXParseFaxtory = SAXParserFactory.newInstance();try{SAXParser saxParser = localSAXParseFaxtory.newSAXParser();saxParser.parse(in, parserPerson);}catch(ParserConfigurationException e){e.printStackTrace();}catch(SAXException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}Log.d("ParsePersonXML","ParsePersonXML:"+parserPerson.getPersons().size());return parserPerson.getPersons();}private class ParserPerson extends DefaultHandler{private StringBuilder builder;//儲存當前正在解析結點的標籤名。private String currentElement;private Person person;private ArrayList<Object> personList;/** * 開始XML文檔的回呼函數,在文檔開頭時調用,其中可做預先處理工作 */@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubthis.personList = new ArrayList<Object>();super.startDocument();}/** * 元素開始標籤的回呼函數,處理元素開始事件。 * uri: 命名空間。 * localName: 不帶命名空間首碼的標籤名。 * qName:帶命名空間首碼的標籤名。 * attributes: 屬性名稱和值對的集合。 */@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubthis.currentElement = localName;if("Person".equals(this.currentElement)){this.person = new Person();this.personList.add(this.person);//擷取屬性值person.setmAccount(attributes.getValue("account"));}}/** * 結束XML文檔的回呼函數,在文檔結束時調用,其中可做一些回收工作。 */@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stub//Person標籤解析完畢,將其儲存到集合中,並清空person,準備下一個Person的解析。this.currentElement = null;}/** * 結束XML文檔的回呼函數,文檔結束時調用,其中可做一些回收工作。 */@Overridepublic void endDocument() throws SAXException {// TODO Auto-generated method stubsuper.endDocument();}/** * 處理元素的字元內容,從參數中可以獲得內容。 * ch: 檔案的字串內容. * start: 讀到的字串在這個數組中的起始位置. * length: 讀到的字串在這個數組中的長度. * 使用new String(ch,start,length)就可以擷取內容。 */@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {// TODO Auto-generated method stubif(this.currentElement == null)return;//根據標籤名,讀取相應的值。String str = new String(ch,0,length);if("name".equals(this.currentElement)){person.setmName(str);}else if("email".equals(this.currentElement)){person.setmEmail(str);}else if("tel".equals(this.currentElement)){person.setmTel(str);}else if("boss".equals(this.currentElement)){person.setmBoss(str);}else if("newProject".equals(this.currentElement)){person.setmProprietor(str);}else if("superPower".equals(this.currentElement)){person.setmCompetence(str);}}public ArrayList<Object> getPersons(){Log.d("ParsePersonXML","ParserPerson:"+this.personList.size());return this.personList;}}}

ContextClient類的實現:

import java.io.InputStream;import java.util.ArrayList;public class ContextClient {private ParseXML mParseXML;public ContextClient(ParseXML parseXML){this.mParseXML = parseXML;}public ArrayList<Object> getParseInfo(InputStream in){return this.mParseXML.getXMLInfoByList(in);}}

MainActivity類的實現:

import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends Activity {    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        TextView tv = (TextView)findViewById(R.id.tv1);        try{        ParsePersonXML parsePerson = new ParsePersonXML();        ContextClient client = new ContextClient(parsePerson);        InputStream in = getResources().openRawResource(R.raw.person_item);        ArrayList<Object> list = client.getParseInfo(in);        in.close();        for(Object p: list)        {        Person person = (Person)p;        if(person != null)        {        String str1 = ("1".equals(person.getmProprietor()))?"超級許可權":"普通許可權";        String str2 = ("1".equals(person.getmCompetence()))?"讀寫項目":"唯讀項目";        StringBuilder builder = new StringBuilder();        tv.append(builder.toString());        tv.append("\n\r姓名: "+person.getmName()+"\r\n賬戶:"+person.getmAccount()        + "\n\r電話: "+person.getmTel()+"\n\r郵箱: "+person.getmEmail()        +"\n\r許可權:  "+str1+" && "+str2+"\n\r ");        }        }        }catch(IOException e){        e.printStackTrace();        }            }}

下面是:

5、易犯錯誤校正

  在繼承DefaultHandler實現的ParsePerson事件處理器中,一定要理清解析的邏輯,再進行相應的處理。不然很容易解析結果為空白之類的錯誤。網上有些blog在處理文檔開頭、結束等回呼函數上,都會犯一些錯誤,這在某些情況下會直接導致解析結果為空白,因而手機端不會顯示結果為空白。根本原因沒有掌握好事件處理器的調用機制,解析的邏輯。

1)currentElement儲存當前解析的標籤,一定要注意初始化和清空的時機。在startElement函數中初始化,個人建議首先初始化然後再判斷處理,這樣比較符合解析邏輯。好些資料包括某些書裡,在startElement中都是先判斷處理再初始化。這在某些情況下是能順利解析出來的,但有些時候卻不行,比如只有一個標籤<Person account="zhangsan"><Person>,就解析不出資料的。最簡單的方法就是不用currentElement,後面會貼出標準代碼。

2)分配Person對象和將該對象添加到集合的時機。添加Person對象建議在分配空間時就添加到集合中,或者在endElement函數中添加。不建議在characters函數中添加,因為當前的currentElement不一定是Person。

3)在解析一個完整的<name>value</name>時,characters函數是很有可能會執行多次的。遇到內容中有"\n"或“、“t”就會再次調用該函數執行,利用StringBuilder對象將其追加上去,這樣就不會丟失內容。

最後我們可以得知上面的ParsePerson還有些繁瑣,甚至可能會丟失部分資料。我參考網上資料,整理出標準的ParsePerson事件處理器。繞了這麼一個大圈子,各位看官不要生氣,我只是希望您能夠真正理解SAX解析,希望下面的代碼對大家有用,如下:

private class ParserPerson extends DefaultHandler{private StringBuilder builder = new StringBuilder();private Person person;private ArrayList<Object> personList;/** * 開始XML文檔回呼函數,文檔開頭時調用,其中可做預先處理工作 *///@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubthis.personList = new ArrayList<Object>();super.startDocument();}/** * 元素開始標籤的回呼函數 * uri: 命名空間。 * localName: 不帶命名空間首碼的標籤名。 * qName:帶命名空間首碼的標籤名。 * attributes: 屬性名稱和值對的集合。 */@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubif("Person".equals(localName)){this.person = new Person();this.personList.add(this.person);//擷取屬性值person.setmAccount(attributes.getValue("account"));}this.builder.setLength(0);}/** * 遇到元素結束標籤的回呼函數, */@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stub//Person標籤解析完畢,將其儲存到集合中,並清空person,準備下一個Person的解析。if("name".equals(localName)){person.setmName(this.builder.toString());}else if("email".equals(localName)){person.setmEmail(this.builder.toString());}else if("tel".equals(localName)){person.setmTel(this.builder.toString());}else if("boss".equals(localName)){person.setmBoss(this.builder.toString());}else if("newProject".equals(localName)){person.setmProprietor(this.builder.toString());}else if("superPower".equals(localName)){person.setmCompetence(this.builder.toString()); }}/** * 結束XML文檔的回呼函數,文檔結束時調用,其中可做一些回收工作。 */@Overridepublic void endDocument() throws SAXException {// TODO Auto-generated method stubsuper.endDocument();}/** * 遇到元素值時回調此函數. * ch: 檔案的字串內容. * start: 讀到的字串在這個數組中的起始位置. * length: 讀到的字串在這個數組中的長度. * 使用new String(ch,start,length)就可以擷取內容。 */@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {// TODO Auto-generated method stub//不管調用多少次該函數,都不會丟失內容。this.builder.append(ch,start,length);}public ArrayList<Object> getPersons(){Log.d("ParsePersonXML","ParserPerson:"+this.personList.size());return this.personList;}}

在這個代碼裡面,主要是用了不帶命名空間首碼的標籤名localName來做相應處理,如果XML含有命名空間首碼,大家自行修改。

下面貼出XML檔案person_item

<?xml version="1.0" encoding="UTF-8"?><Persons>    <Person account="test"></Person>    <Person ></Person><Person account="zhangsan">    <name>張三</name>    <email>zhangsan@gmail.com</email>    <tel>18866661111</tel>    <boss>manager</boss>    <newProject>1</newProject>    <superPower>1</superPower></Person><Person account="lisi">    <name>李四</name>    <email>lisi@gmail.com</email>    <tel>16688883333</tel>    <boss>manager</boss>    <newProject>0</newProject>    <superPower>0</superPower></Person></Persons>

這是,Person指派至後也賦予了初始值,且在MainActivity中沒進行相應判斷處理,因而看起來有些怪異,見諒:

參考1:http://www.cnblogs.com/felix-hua/archive/2012/01/10/2317404.html

參考2:http://blog.csdn.net/feng88724/article/details/7013675

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.