在RSS概述中簡單介紹了RSS,本節將為大家講解RSS文件的解析,而又因為RSS是用XML編寫的,所以這裡就直接為大家講解怎樣解析XML文件。
一、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時所用)。
SAX是一種占用內存少且解析速度快的解析器,它采用的是事件啟動,它不需要解析完整個文檔,而是按照內容順序 看文檔某個部分是否符合xml語法,如果符合就觸發相應的事件,所謂的事件就是些回調方法(callback),這些方法定義在ContentHandler中,下面是其主要方法:
startDocument:當遇到文檔的時候就觸發這個事件,調用這個方法可以在其中做些預處理工作。
startElement(String namespaceURI,String localName,String qName,Attributes atts):當遇開始標簽的時候就會觸發這個方法。
endElement(String uri,String localName,String name):當遇到結束標簽時觸發這個事件,調用此法可以做些善後工作。
charachers(char [] ch,int start,int length):當遇到xml內容時觸發這個方法,用new String(ch,start,length)可以接受內容。
二、建立pojo類
在解析之前,我們需要建立pojo類來對應RSS中的元素。首先是RSS feed,我們知道<channel> 元素用於描述RSS feed,但它不是描述RSS的重點,它下面的三個必須子元素<title><link><description>是描述feed的主要信息。因為我們在解析之前就事先獲取了RSS地址,所以在這裡我們就不需要建立一個RSS的link了。主要建立link,item列表以及description,因為是標題,所以把description就換成時間來表達,一般的RSS也是這樣做的。如圖:
下面是建立的RSSFeed:
Java代碼
- public class RSSFeed
- {
- private String title = null;標題
- private String pubdate = null;發布日期
- private int itemcount = 0;//用於計算列表數目
- private List<RSSItem> itemlist;聲明一個RSSItem類型的泛型集合類List對象itemlist,用於描述列表 item
- public RSSFeed()
- {
- itemlist = new Vector(0); 構造函數初始化itemlist
- }
- public int addItem(RSSItem item)
- {
- itemlist.add(item);
- itemcount++;
- return itemcount;
- }
- public RSSItem getItem(int location)
- {
- return itemlist.get(location);
- }
- public List getAllItems()
- {
- return itemlist;
- }
- public List getAllItemsForListView(){
- 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;
- }
- int getItemCount()
- {
- return itemcount;
- }
- public void setTitle(String title)
- {
- this.title = title;
- }
- public void setPubDate(String pubdate)
- {
- this.pubdate = pubdate;
- }
- public String getTitle()
- {
- return title;
- }
- public String getPubDate()
- {
- return pubdate;
- }
- }
建立完RSSFeed類後,我們開始建立RSSItem:
Java代碼
- public static final String TITLE="title";
- public static final String PUBDATE="pubdate";
- private String title = null;
- private String description = null;
- private String link = null;
- private String category = null;
- private String pubdate = null;
-
-
- public RSSItem()
- {
- }
- public void setTitle(String title)
- {
- this.title = title;
- }
- public void setDescription(String description)
- {
- this.description = description;
- }
- public void setLink(String link)
- {
- this.link = link;
- }
- public void setCategory(String category)
- {
- this.category = category;
- }
- public void setPubDate(String pubdate)
- {
- this.pubdate = pubdate;
- }
- public String getTitle()
- {
- return title;
- }
- public String getDescription()
- {
- return description;
- }
- public String getLink()
- {
- return link;
- }
- public String getCategory()
- {
- return category;
- }
- public String getPubDate()
- {
- return pubdate;
- }
- public String toString()
- {
- if (title.length() > 20)
- {
- return title.substring(0, 42) + "...";
- }
- return title;
- }
- }
三、XML解析
新建helper類RSSHandler,用於對rss進行xml解析,並將解析結果包裝為RSSFeed和RSSItem對象,方便在UI界面中顯示:
Java代碼
- public class RSSHandler extends DefaultHandler
- {
-
- RSSFeed rssFeed;//用於保存解析過程中的channel
- RSSItem rssItem;//用於保存解析過程中的item
- String lastElementName = ""; //標記變量,用於標記在解析過程中我們關心的幾個標簽,若不是我們關心的標簽,記做 0
- final int RSS_TITLE = 1;//若是title標簽,記做 1,注意有兩個title,但我們都保存在item的title成員變量中
- final int RSS_LINK = 2;//若是link標簽,記做 2
- final int RSS_DESCRIPTION = 3;//若是description標簽,記做 3
- final int RSS_CATEGORY = 4;//若是category標簽,記做 4
- final int RSS_PUBDATE = 5; //若是pubdate標簽,記做 5,注意有兩個pubdate,但我們都保存在item的pubdate成員變量中
- int currentstate = 0;
-
- public RSSHandler(){}
- public RSSFeed getFeed()
- {
- return rssFeed;//通過這個方法把解析結果封裝在 RSSFeed 對象中並返回
- }
-
- //下面通過重載 DefaultHandler 的 5 個方法來實現 sax 解析
- public void startDocument() throws SAXException
- {
-
- //這個方法在解析xml文檔的一開始執行,一般我們需要在該方法中初始化解析過程中有可能用到的變量
- rssFeed = new RSSFeed();
- rssItem = new RSSItem();
- }
- public void endDocument() throws SAXException
- {
-
- //這個方法在整個xml文檔解析結束時執行,一般需要在該方法中返回或保存整個文檔解析解析結果,但由於
-
- //我們已經在解析過程中把結果保持在rssFeed中,所以這裡什麼也不做
-
-
- }
- public void startElement(String namespaceURI, String localName,String qName, Attributes atts) throws SAXException
- {
- //這個方法在解析標簽開始標記時執行,一般我們需要在該方法取得標簽屬性值,但由於我們的rss文檔
-
- //中並沒有任何我們關心的標簽屬性,因此我們主要在這裡進行的是設置標記變量currentstate,以
-
- //標記我們處理到哪個標簽
-
-
- if (localName.equals("channel"))
- {//channel這個標簽沒有任何值得我們關心的內容,所以currentstate置為0
- currentstate = 0;
- return;
- }
- if (localName.equals("item"))
- {
- //若是item標簽,則重新構造一個RSSItem,從而把已有(已經解析過的)item數據扔掉,當
-
- //然事先是已經保存到rssFeed的itemlist集合中了
-
-
- rssItem = new RSSItem();
- return;
- }
- if (localName.equals("title"))
- {
- //若是title標簽,置currentstate為1,表明這是我們關心的數據,這樣在characters
-
- //方法中會把元素內容保存到rssItem變量中
-
-
- currentstate = RSS_TITLE;
- return;
- }
- if (localName.equals("description"))
- {
- //若是description標簽,置currentstate為3,表明這是我們關心的數據,這樣在characters
-
- //方法中會把元素內容保存到rssItem變量中
-
-
- currentstate = RSS_DESCRIPTION;
- return;
- }
- if (localName.equals("link"))
- {
- //若是link標簽,置currentstate為2,表明這是我們關心的數據,這樣在characters
-
- //方法中會把元素內容保存到rssItem變量中
-
-
- currentstate = RSS_LINK;
- return;
- }
- if (localName.equals("category"))
- {
- //若是category標簽,置currentstate為4,表明這是我們關心的數據,這樣在characters
-
- //方法中會把元素內容保存到rssItem變量中
-
-
- currentstate = RSS_CATEGORY;
- return;
- }
- if (localName.equals("pubDate"))
- {
- //若是pubDate標簽,置currentstate為5,表明這是我們關心的數據,這樣在characters
-
- //方法中會把元素內容保存到rssItem變量中
-
-
- currentstate = RSS_PUBDATE;
- return;
- }
-
- currentstate = 0;//如果不是上面列出的任何標簽,置currentstate為0,我們不關心
- }
-
- public void endElement(String namespaceURI, String localName, String qName) throws SAXException
- {
-
-
- //如果解析一個item節點結束,就將rssItem添加到rssFeed中。
- if (localName.equals("item"))
- {
- rssFeed.addItem(rssItem);
- return;
-
- }
- }
-
- public void characters(char ch[], int start, int length)
- {//這個方法在解析標簽內容(即開始標記-結束標記之間的部分)時執行,一般我們在裡這獲取元素體內容
- String theString = new String(ch,start,length); //獲取元素體內容
- switch (currentstate)
- {//根據currentstate標記判斷這個元素體是屬於我們關心的哪個元素
- case RSS_TITLE:
- rssItem.setTitle(theString);//若是title元素,放入rssItem的title屬性
- currentstate = 0;
- break;
- case RSS_LINK:
- rssItem.setLink(theString);//若是link元素,放入rssItem的link屬性
- currentstate = 0;
- break;
- case RSS_DESCRIPTION:
- rssItem.setDescription(theString);
- currentstate = 0;
- break;
- case RSS_CATEGORY:
- rssItem.setCategory(theString);
- currentstate = 0;
- break;
- case RSS_PUBDATE:
- rssItem.setPubDate(theString);
- currentstate = 0;
- break;
- default:
- return;
- }
-
- }
-
- }
之後就可以按照一定的步驟來進行解析了,具體如下:
Java代碼
- private RSSFeed getFeed(String urlString)
- {
- try
- {
- URL url = new URL(urlString);
- SAXParserFactory factory = SAXParserFactory.newInstance(); // 構建Sax解析工廠
- SAXParser parser = factory.newSAXParser(); // 使用Sax解析工廠構建Sax解析器
- XMLReader xmlreader = parser.getXMLReader(); // 使用Sax解析器構建xml Reader
-
- RSSHandler rssHandler = new RSSHandler(); // 構建自定義的RSSHandler作為xml Reader的處理器(或代理)
- xmlreader.setContentHandler(rssHandler); // 構建自定義的RSSHandler作為xml Reader的處理器(或代理)
-
-
- InputSource is = new InputSource(url.openStream()); // 使用url打開流,並將流作為xml Reader的輸入源並解析
- xmlreader.parse(is);
-
- return rssHandler.getFeed(); // 將解析結果作為 RSSFeed 對象返回
- }
- catch (Exception ee)
- {
-
-
- return null;
- }
- }
關於如何解析RSS文件就先講這些。後面一節我們將介紹怎樣在列表裡顯示RSS內容,望大家繼續關注。