系列文章:
上篇講清楚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
publicvoid 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)到指定物件格式就可以了
請問 SimpleXMLParser.java 中用網址讀取XML的那段
最後要 getData 時發生因型態不符的錯誤
是不是上面那段 IputStream 型態的也要寫? 謝謝
我的程式碼 照著
可是他出現在 java.net.MalformedURLException: no protocol Exception
這該如何處理呢?
我是用4.x 版以上
不好意思,我參考了您的程式碼,
但是在debug的時候,出現了NullPointerException,
在chracter()那邊,請問有可能是哪裡出錯了呢??
請問一下
我參照您程式碼
我執行的時候,按Button沒有反應
請問一下
我按button都沒有反應
該怎麼處理