系列文章:
[Android] 要學會的關鍵技術—XML的讀取(1)
[Android] 要學會的關鍵技術—XML的讀取(2)
[Android] 要學會的關鍵技術—XML的讀取(3)
上篇講清楚XML的讀取方式之後,接下來上程式碼
之前說了,我比較喜歡設計一個類別(Class)把解析完的內容塞進去
新聞類別 RssNews.java
package com.J_Test.XMLParserTest;
import java.io.Serializable;
/* * <rss version=”2.0″> * <channel version=”2.0″> * <title> 中央氣象局警報、特報 </title> * <link>http://www.cwb.gov.tw/</link> * <description> 中央氣象局 RSS 服務 </description> * <language> zh – tw </language> * <lastBuildDate>Sat, 03 Sep 2011 19:00:02 GMT</lastBuildDate> * <ttl> 1 </ttl> * <item> * <pubDate>Sat, 03 Sep 2011 13:34:26 GMT</pubDate> * <title>09/03 21:30 解除大雨特報發布 </title> * <link>http://www.cwb.gov.tw/V7/prevent/warning/w26.htm?&id=20110904030002</link> * <description>(… 略 )</description> * </item> * </channel> * </rss> */
@SuppressWarnings ( “serial” )public class RssNews implements Serializable
{
private String title = null ;
private String link = null ;
private String pubDate = null ;
public void setTitle(String title)
{
this . title = title;
}
public String getTitle()
{
return title ;
}
public void setLink(String link)
{
this . link = link;
}
public String getLink()
{
return link ;
}
public void setPubDate(String pubDate)
{
this . pubDate = pubDate;
}
public String getPubDate()
{
return pubDate ;
}
@Override
public String toString()
{
return “RssNews [title=” + title + “, link=” + link + “, pubDate=” + pubDate + “]” ;
}
}
嗯,這複習了一下資料類別的寫法 我只記錄RSS文章的標題、連結、摘要…等我要的訊息 大多程式碼給eclipse自己產生,沒甚麼特別
繼承Serializable 只是為了到時候方便寫入到檔案而已,可以不寫
XML剖析器 SimpleXMLParser.java
package com.J_Test.XMLParserTest.Module;
import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;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;
public class SimpleXMLParser
{
protected SimpleXMLParsingHandler xmlParsingHandler ;
/** * 建構子,必須將 xmlParsingHandler 給 new 出來 * */
public SimpleXMLParser(SimpleXMLParsingHandler parser)
{
xmlParsingHandler = parser;
}
/** * 從 XML 的剖析出物件 * * @param inputStream * 來源的 FileInputStream * @return 回傳包含物件陣列的資料 ( 回傳可以不只一個物件陣列 ) * @throws SAXException * @throws ParserConfigurationException * @throws IOException */
public Object[] getData(InputStream inputStream) throws SAXException,
IOException, ParserConfigurationException
{
Object[] data;
/* 產生 SAXParser 物件 */
SAXParserFactory spf = SAXParserFactory.newInstance ();
SAXParser sp = spf.newSAXParser();
/* 產生 XMLReader 物件 */
XMLReader xr = sp.getXMLReader();
/* 設定自定義的 MyHandler 給 XMLReader */
if ( xmlParsingHandler == null )
{
throw new NullPointerException( “xmlParsingHandler is null” );
} else
{
xr.setContentHandler( xmlParsingHandler );
/* 解析 XML */
xr.parse( new InputSource(inputStream));
/* 取得 RSS 標題與內容列表 */
data = (Object[]) xmlParsingHandler .getParsedData();
}
inputStream.close();
return data;
}
/** * 從 XML 的剖析出物件 ( 多載方法 ) * * @param urlPath * url 網址 * @throws IOException * @throws SAXException * @throws ParserConfigurationException */
public Object[] getData(String urlPath) throws SAXException, IOException,
ParserConfigurationException
{
URL url = new URL(urlPath);
HttpURLConnection uc = (HttpURLConnection) url.openConnection();
uc.setConnectTimeout(15000);
uc.setReadTimeout(15000); // 設定 timeout 時間
uc.connect(); // 開始 連線
int status = uc.getResponseCode();
if (status == HttpURLConnection. HTTP_OK )
{
Object[] data = getData(url.openStream());
return data;
}
return null ;
}
}
好了,這裡就是XML讀取的核心程式
只有二個方法,一個輸入InputStream, 另一個是網址
這樣預留,到時候可以 讀取XML檔案
這裡提的是網路抓取嘛,所以當然是使用網址的方法摟
這裡有出現一個
uc.connect();
所以說,這是要放在另一個執行緒去執行的 (Android 4.0以後的環境,直接跑絕不會給過)
至於SimpleXMLParsingHandler 是甚麼?等等就會看見了
XML剖析處理細節 SimpleXMLParsingHandler.java
package com.J_Test.XMLParserTest.Module;
import java.util.Stack;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import android.util.Log;
/** * 處理 XML 的方法細節的 Class 有四個方法必須覆寫而且要呼叫 super 方法 */
public abstract class SimpleXMLParsingHandler extends DefaultHandler
{
protected static final String TAG = “ParsingHandler” ;
/** XML 解析 Node 的堆疊 */
private Stack<String> in_node ;
public Stack<String> getInNode()
{
return in_node ;
}
/** 除錯模式 ( 手動設定成 true 會顯示 Log) */
private boolean debugMode = false ;
/** * 將轉換的資料 回傳 */ public abstract Object getParsedData();
/** * XML 文件開始解析時呼叫此 method ,這裡要加上 new 出自訂的物件的程式 */
@Override public void startDocument() throws SAXException
{
in_node = new Stack<String>();
}
/** * XML 文件結束解析時呼叫此 method */
@Override public void endDocument() throws SAXException
{
}
/** * 解析到 Element 的開頭時呼叫此 method */
@Override public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException
{
if (isDebugMode())
{
Log.v ( TAG , “startElement: qName=” + qName);
for ( int i = 0; i < atts.getLength(); i++)
{
Log.v ( TAG , “tt atts[“ + i + “]getQName=” + atts.getQName(i));
Log.v ( TAG , “tt atts[“ + i + “]getValue=” + atts.getValue(i));
}
}
in_node .push(qName);
}
/**
* 解析到 Element 的結尾時呼叫此 method
*/
@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException
{
if (isDebugMode())
Log.v ( TAG , “endElement: qName=” + qName);
in_node .pop();
}
/** 取得 Element 的開頭結尾中間夾的字串 */
@Override
public void characters( char ch[], int start, int length)
{
String fetchStr = new String(ch).substring(start, start + length);
// printNodePos();
if (isDebugMode())
Log.v ( TAG , “t characters: ch=” + fetchStr);
characters(fetchStr);
}
/** * 取得 Element 的開頭結尾中間夾的字串 這裡需要做「新增 Node 的上所有資料」 * * @param fetchStr * 取得到的字串 */
public void characters(String fetchStr)
{
}
// ————————————————————————————–
/**
* 印出現在 Node 的位置,除錯用。例如 :rss -> channel -> title
*/
public String printNodePos()
{
StringBuffer sb = new StringBuffer();
// 印出現在 Node 的位置
for ( int i = 0; i < in_node .size(); i++)
{
if (i > 0)
sb.append( ” -> “ );
sb.append( in_node .get(i));
}
sb.append( “n” );
return sb.toString();
}
/**
* 在參數堆中找到我要的參數
*/
public static String findAttr(Attributes atts, String findStr)
{
int i;
for (i = 0; i < atts.getLength(); i++)
{
if (atts.getQName(i).compareToIgnoreCase(findStr) == 0)
{
break ;
}
}
return atts.getValue(i);
}
public void setDebugMode( boolean debugMode)
{
this . debugMode = debugMode;
}
public boolean isDebugMode()
{
return debugMode ;
}
}
這個class 呢…因為有abstract ,所以必須要繼承實作 (當做函式庫了嘛)
其中加了很多不錯的函式,例如:
public static String findAttr(Attributes atts, String findStr)
找到我要的屬性
public String printNodePos()
印出路徑等等
路徑的概念就是上篇所說的,用個堆疊(Stack) 就可以搞定!
眼尖的你會發現,怎麼會有個
public void characters(String fetchStr)
然後甚麼都沒寫的放在那裡
其實在標準的
public void characters( char ch[], int start, int length)
就可看出端倪,我只是把擷取到的文字直接去呼叫只有String的參數的方法 到時候便於判斷
到目前為止,跟我們的RSS沒甚麼關連….
RSS新聞資料處理 RssNewsXMLParsingHandler.java
package com.J_Test.XMLParserTest.ParsingHandler;
import java.util.Stack;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import android.util.Log;import com.J_Test.XMLParserTest.RssNews;import com.J_Test.XMLParserTest.Module.SimpleXMLParsingHandler;
/**
* 記錄著 Rss 新聞資料的解析 XML 處理方式的 class (繼承了 SimpleXMLParsingHandler )
*/
public class RssNewsXMLParsingHandler extends SimpleXMLParsingHandler
{
private static final String TAG = “RssNews ParsingHandler” ;
/** 用來儲存 Rss 新聞的物件 */
private RssNews newsItem ;
/** 用來儲存 Rss 新聞的物件 Stack( 堆疊 ) */
private Stack<RssNews> mNewsItem_list ;
/** 建構子 */
public RssNewsXMLParsingHandler()
{
}
/**
* @return 回傳 RssNews[] 。程式會將讀到的物件 { RssNews[] } 包成 Object[] 然後回傳
*/
@Override
public Object[] getParsedData()
{
RssNews[] Arr_RssNews = (RssNews[]) mNewsItem_list
.toArray( new RssNews[ mNewsItem_list .size()]);
// 解析結果回報
Log.v ( TAG , String.format ( “RssNews Count=%d” , Arr_RssNews. length ));
return new Object[] { Arr_RssNews };
}
/**
* XML 文件開始解析時呼叫此 method
*/
@Override
public void startDocument() throws SAXException
{
super .startDocument();
// 在文件開始的時候,宣告出該 RssNews 形態的 Stack( 堆疊 )
mNewsItem_list = new Stack<RssNews>();
}
/**
* XML 文件結束時呼叫此 method
*/
@Override
public void endDocument() throws SAXException
{
super .endDocument();
}
/**
* 解析到 Element 的開頭時呼叫此 method
*/
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException
{
super .startElement(namespaceURI, localName, qName, atts);
// 若搞不清楚現在在哪裡的話可以用 printNodePos();
// printNodePos();
if (getInNode().size() >= 3 && getInNode().get(getInNode().size() – 3).equals( “rss” ) && getInNode().get(getInNode().size() – 2).equals( “channel” ) && getInNode().get(getInNode().size() – 1).equals( “item” ))
{
// 在 rss -> channel -> item 這個位置中
// 新增一個 RssNews
newsItem = new RssNews();
}
}
/**
* 解析到 Element 的結尾時呼叫此 method
*/
@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException
{
if (getInNode().size() >= 3 && getInNode().get(getInNode().size() – 3).equals( “rss” ) && getInNode().get(getInNode().size() – 2).equals( “channel” ) && getInNode().get(getInNode().size() – 1).equals( “item” ))
{
// 在 rss -> channel -> item 這個位置中
// 新增一筆新聞資料到 Stack( 堆疊 ) 裡
mNewsItem_list .add( newsItem );
newsItem = null ;
}
super .endElement(namespaceURI, localName, qName);
}
/**
* 取得 Element 的開頭結尾中間夾的字串
*/
@Override
public void characters(String fetchStr)
{
if (getInNode().size() >= 4 && getInNode().get(getInNode().size() – 4).equals( “rss” ) && getInNode().get(getInNode().size() – 3).equals( “channel” ) && getInNode().get(getInNode().size() – 2).equals( “item” ))
{
// 在 rss -> channel -> item -> XXX 這個位置中
// 新增 Node 的上所有資料
// 在 rss -> channel -> item -> title 這個位置中
if (getInNode().lastElement().equals( “title” ))
// 設定標題
newsItem .setTitle(fetchStr);
// 在 rss -> channel -> item -> link 這個位置中
else if (getInNode().lastElement().equals( “link” ))
// 設定連結
newsItem .setLink(fetchStr);
// 在 rss -> channel -> item -> pubDate 這個位置中
else if (getInNode().lastElement().equals( “pubDate” ))
// 設定發佈日期
newsItem .setPubDate(fetchStr);
}
}
}
這就是我們的重點了,因為有我們自訂的SimpleXMLParsingHandler 幫忙
程式碼簡潔多了
我們只要判斷 getInNode() 裡面的內容就行了
配合characters(String fetchStr) 和endElement()
就可以把資料塞進物件裡然後依序排好
順帶一提,最後
public Object[] getParsedData()
會把整理好的物件陣列,用包陣列的方式回傳
到時候只要強制轉型(cast)到指定物件格式就可以了