Android SAX解析XML


解析XML的方式有很多種,大家比較熟悉的可能就是DOM解析。

DOM(文件對象模型)解析:解析器讀入整個文檔,然后構建一個駐留內存的樹結構,然后代碼就可以根據DOM接口來操作這個樹結構了。

  優點:整個文檔讀入內存,方便操作:支持修改、刪除和重現排列等多種功能。

  缺點:將整個文檔讀入內存中,保留了過多的不需要的節點,浪費內存和空間。

  使用場合:一旦讀入文檔,還需要多次對文檔進行操作,並且在硬件資源充足的情況下(內存,CPU)。

為了解決DOM解析存在的問題,就出現了SAX解析。其特點為:

  優點:不用實現調入整個文檔,占用資源少。尤其在嵌入式環境中,如android,極力推薦使用SAX解析。

  缺點:不像DOM解析一樣將文檔長期駐留在內存中,數據不是持久的。如果事件過后沒有保存數據,數據就會丟失。

  使用場合:機器有性能限制。

SAX解析XML文檔采用事件驅動模式。什么是事件驅動模式?它將XML文檔轉換成一系列的事件,由單獨的事件處理器來決定如何處理。

基於事件驅動的處理模式主要是基於事件源和事件處理器(或者叫監聽器)來工作的。一個可以產生事件的對象叫做事件源,而一個可以針對事件做出響應的對象就被叫做事件處理器。

在SAX接口中,事件源是org.xml.sax包中的XMLReader,他通過parse()方法開始解析XML文檔,並根據文檔內容產生事件。而事件處理器則是org.xml.sax包中的ContentHandler、DTDHandler、ErrorHandler,以及EntityResolver這四個接口。他們分別處理事件源在解析過程中產生不同類的事件(其中DTDHandler為解析文檔DTD時所用)。詳細介紹如下表:

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

//設置一個可以定位文檔內容事件發生位置的定位器對象

public void setDocumentLocator(Locator locator)

//用於處理文檔解析開始事件

public void startDocument()throws SAXException

//處理元素開始事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱,屬性類表等信息

public void startElement(String namespacesURI , String localName , String qName , Attributes atts) throws SAXException

//處理元素結束事件,從參數中可以獲得元素所在名稱空間的uri,元素名稱等信息

public void endElement(String namespacesURI , String localName , String qName) throws SAXException

//處理元素的字符內容,從參數中可以獲得內容

public void characters(char[] ch , int start , int length) throws SAXException

這里再介紹下XMLReader中的方法。

//注冊處理XML文檔解析事件ContentHandler
public void setContentHandler(ContentHandler handler)

//開始解析一個XML文檔
public void parse(InputSorce input) throws SAXException


SAX實現實體解析的步驟:

在android中使用SAX是有跡可循的,完全可以按照下面的方法就可以輕松找到xml里的tag,然后得到想要的內容。具體實現步驟如下:

(一)第一步:新建一個工廠類SAXParserFactory,代碼如下:

SAXParserFactory factory = SAXParserFactory.newInstance();

(二)第二步:讓工廠類產生一個SAX的解析類SAXParser,代碼如下:

SAXParser parser = factory.newSAXParser();

(三)第三步:從SAXPsrser中得到一個XMLReader實例,代碼如下:

XMLReader reader = parser.getXMLReader();

(四)第四步:把自己寫的handler注冊到XMLReader中,一般最重要的就是ContentHandler,代碼如下:

RSSHandler handler = new RSSHandler();
reader.setContentHandler(handler);

(五)第五步:將一個xml文檔或者資源變成一個java可以處理的InputStream流后,解析正式開始,代碼如下:

parser.parse(is);

上面幾個步驟中,最重要、最關鍵的就是第四步,handler的實現。

下面通過一個RSS解析的例子說明handler的實現:

我們先是自己見一個rss的xml文檔,實現本地解析,新建的rss文檔如下:

<?xml version="1.0" encoding="UTF-8"?>
<channel>
<title>RSS 解析練習</title>
<description>hehehaha</description>
<link>http://www.cnblogs.com/felix-hua/</link>
<language>zh-cn</language>


<item>
<title><![CDATA[頭條]]></title>
<link>http://mc.cz001.com.cn/images/menu/23_active.png</link>
<category>0</category>
<description>描述詳細信息的</description>
<pubDate>2012-01-09</pubDate>
</item>

<item>
<title><![CDATA[新聞]]></title>
<link>http://mc.cz001.com.cn/images/menu/23_active.png</link>
<category>0</category>
<description>描述詳細信息的</description>
<pubDate>2012-01-09</pubDate>
</item>

<item>
<title><![CDATA[首頁]]></title>
<link>http://mc.cz001.com.cn/images/menu/23_active.png</link>
<category>0</category>
<description>描述詳細信息的</description>
<pubDate>2012-01-09</pubDate>
</item>

<item>
<title><![CDATA[財經]]></title>
<link>http://mc.cz001.com.cn/images/menu/23_active.png</link>
<category>0</category>
<description>描述詳細信息的</description>
<pubDate>2012-01-09</pubDate>
</item>

建好后,我們命名為rssxml.xml,然后放到項目的根目錄下:

然后我們可以建立兩個實體類:

1、RSSFeed,與完整的xml文檔相對應;

2、RSSItem,與item標簽內的信息相對應。

這樣在解析xml時,我們就可以把解析出來的信息放到實體類里,然后直接操作實體類就可以了。下面給出代碼:

RSSFeed.java

package com.sax.org.entity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

public class RSSFeed {
private String title;
private int itemcount;
private List<RSSItem> itemlist;

public RSSFeed(){
itemlist = new Vector<RSSItem>(0);
}

/**
* 負責將一個RSSItem加入到RSSFeed類中
*
@param item
*
@return
*/
public int addItem(RSSItem item){
itemlist.add(item);
itemcount++;
return itemcount;
}

public RSSItem getItem(int location){
return itemlist.get(location);
}

public List<RSSItem> getAllItems(){
return itemlist;
}

/**
* 負責從RSSFeed類中生成列表所需要的數據
*
@return
*/
public List getAllItemForListView(){
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
int size = itemlist.size();
for(int i=0 ; i<size ; i++){
HashMap<String , Object> item = new HashMap<String, Object>();
item.put(RSSItem.TITLE, itemlist.get(i).getTitle());
item.put(RSSItem.PUBDATE, itemlist.get(i).getPubdate());
data.add(item);
}
return data;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public int getItemcount() {
return itemcount;
}

public void setItemcount(int itemcount) {
this.itemcount = itemcount;
}

public List<RSSItem> getItemlist() {
return itemlist;
}

public void setItemlist(List<RSSItem> itemlist) {
this.itemlist = itemlist;
}

}

RSSItem.java

package com.sax.org.entity;

public class RSSItem {
public static String TITLE = "title";
public static String PUBDATE = "pubdate";
public String title;
public String description;
public String link;
public String category;
public String pubdate;
public RSSItem() {
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getPubdate() {
return pubdate;
}
public void setPubdate(String pubdate) {
this.pubdate = pubdate;
}


}

下面就是最最重要的地方了,建立自己的ContentHandler.看下面的代碼:

RSSHandler.java

package com.sax.org.handler;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.sax.org.entity.RSSFeed;
import com.sax.org.entity.RSSItem;

public class RSSHandler extends DefaultHandler{
RSSFeed RssFeed;
RSSItem RssItem;
final int RSS_TITLE = 1;
final int RSS_LINK = 2;
final int RSS_DESCRIPTION = 3;
final int RSS_CATEGORY = 4;
final int RSS_PUBDATE = 5;
int currentstate = 0;

public RSSHandler(){}

public RSSFeed getFeed(){
return RssFeed;
}

@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
RssFeed = new RSSFeed();
RssItem = new RSSItem();
}

@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
if(localName.equals("channel")){
currentstate = 0;
return;
}
if(localName.equals("item")){
RssItem = new RSSItem();
return;
}
if(localName.equals("title")){
currentstate = RSS_TITLE;
return;
}
if(localName.equals("description")){
currentstate = RSS_DESCRIPTION;
return;
}
if(localName.equals("link")){
currentstate = RSS_LINK;
return;
}
if(localName.equals("category")){
currentstate = RSS_CATEGORY;
return;
}
if(localName.equals("pubDate")){
currentstate = RSS_PUBDATE;
return;
}
currentstate = 0;
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
if(localName.equals("item")){
RssFeed.addItem(RssItem);
return;
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub
String theString = new String(ch, start, length);
switch(currentstate){
case RSS_TITLE:
RssItem.setTitle(theString);
currentstate = 0;
break;
case RSS_DESCRIPTION:
RssItem.setDescription(theString);
currentstate = 0;
break;
case RSS_LINK:
RssItem.setLink(theString);
currentstate = 0;
break;
case RSS_PUBDATE:
RssItem.setPubdate(theString);
currentstate = 0;
break;
case RSS_CATEGORY:
RssItem.setCategory(theString);
currentstate = 0;
break;
default:
return;
}
}
}

就上面的代碼分析,實現一個ContentHandler一般要一下幾個步驟:

1、聲明一個類,繼承DefaultHandler。DefaultHandler是一個基類,這個類里面簡單實現了一個ContentHandler。我們只需要重寫里面的方法即可。

2、重寫 startDocument() 和 endDocument(),一般解析將正式解析之前的一些初始化工資放到startDocument()里面,收尾的工作放到endDocument()里面。

3、重寫startElement(),XML解析器遇到XML里面的tag時就會調用這個函數。經常在這個函數內是通過localName倆進行判斷而操作一些數據。

4、重寫characters()方法,這是一個回調方法。解析器執行完startElement()后,解析完節點的內容后就會執行這個方法,並且參數ch[]就是節點的內容。這個例子里我們根據currentstate的不同,來判斷當前那個tag的內容,並放到合適的實體類中。

5、重寫endElement()方法,這個方法與startElement()相對應,解析完一個tag節點后,執行這個方法。再找個例子中,如果解析一個item結束,就將RSSIiem添加到RSSFeed中。

 最后我們實現一個activity來展現解析的結果:

package com.sax.org;

import java.io.IOException;
import java.net.URL;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

import com.sax.org.entity.RSSFeed;
import com.sax.org.entity.RSSItem;
import com.sax.org.handler.RSSHandler;

public class SAXReaderActivity extends Activity {
/** Called when the activity is first created. */

public String rssUrl = "http://mc.cz001.com.cn/a/indexconfig/index.rss";
public RSSFeed feed;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
feed = getFeed(rssUrl);
showList();
}

public RSSFeed getFeed(String rssUrl) {
try {
// 這里我們實現了本地解析,所以注掉了這個取網絡數據的。
// URL url = new URL(rssUrl);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
RSSHandler handler = new RSSHandler();
reader.setContentHandler(handler);
InputSource is = new InputSource(this.getClassLoader().getResourceAsStream("rssxml.xml"));//取得本地xml文件
reader.parse(is);
return handler.getFeed();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}

public void showList() {
ListView rsslistview = (ListView) findViewById(R.id.rssList);
TextView rsstitle = (TextView) findViewById(R.id.rsstitle);
if (feed == null) {
rsstitle.setText("訪問失敗...");
return;
}
SimpleAdapter adapter = new SimpleAdapter(this,
feed.getAllItemForListView(),
android.R.layout.simple_list_item_2, new String[] {
RSSItem.TITLE, RSSItem.PUBDATE }, new int[] {
android.R.id.text1, android.R.id.text2 });
rsslistview.setAdapter(adapter);
}
}

展示下運行結果:

OK。

本文參考自《Google+Android入門與實踐》,謝謝。

 










 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM