[Android] 要學會的關鍵技術—XML的讀取(2)

device-2012-03-23-142254  

系列文章:

[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>zhtw</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;

     /**
      * 建構子,必須將xmlParsingHandlernew出來
      *
      */

     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();

           /* 設定自定義的MyHandlerXMLReader */

           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)到指定物件格式就可以了

在〈[Android] 要學會的關鍵技術—XML的讀取(2)〉中有 5 則留言

  1. 請問 SimpleXMLParser.java 中用網址讀取XML的那段
    最後要 getData 時發生因型態不符的錯誤
    是不是上面那段 IputStream 型態的也要寫? 謝謝

  2. 我的程式碼 照著
    可是他出現在 java.net.MalformedURLException: no protocol Exception
    這該如何處理呢?
    我是用4.x 版以上

  3. 不好意思,我參考了您的程式碼,
    但是在debug的時候,出現了NullPointerException,
    在chracter()那邊,請問有可能是哪裡出錯了呢??

  4. 請問一下
    我參照您程式碼
    我執行的時候,按Button沒有反應

留言功能已關閉。