在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