子供の時、アドベントカレンダー(チョコレートが入っているほう)でずいぶん楽しんでましたので、大人になった自分もこの季節を楽しめるようにPerl Advent Calendar(http://www.perladvent.org)のRSSリーダーを作成してみました。
RSSのXMLの構成はこんな構成
<entry>
<title>タイトル</title>
<id>URL</id>
<summary>要約</summary>
<updated>更新時間</updated>
</entry>
なので、以下のようなPOJOを作成しました。
public class Entry implements Serializable { //後でIntentのBundleに入れるようにSerializableを実装 private String title; private String link; private String summary; private String updated; public void setTitle(String title) { this.title = title; } public String getTitle() { return this.title; } //...長いので、linkとsummaryのゲッターとセッターを略します public void setUpdated(String updated) { String dateOnly = updated.substring(0, 10); this.updated = dateOnly; } public String getUpdated() { return this.updated; } }
SAXパーサーを使うことにしました。ハンドラークラスを使ってstartElementメソッドで適当なタグ名を見つけたらフラグを立って、中身のテキストを全部引っ張ってきて、最後にendElementメソッドでフラグを消すという訳ですが、なぜかSAXの仕様でcharactersのメソッドが何回も呼ばれることもあるらしくて、たまにテキストが2・3回読み込まれてて、困りました・・・
長い分を読み込むためにcharactersを複数回呼ぶことが必要と思うので、あまりいい解決方法ではないかと思いますけど、文字を追加してくれるbuilder.appendを呼ぶ直前にbuilder.setLength(0)でリセットすることでとりあえず解決できました・・・
builder.setLength(0); builder.append(ch, start, length);
奇麗に表示できました。
ハンドラークラスはこんな感じになっています:
public class RSSHandler extends DefaultHandler { private ArrayListentries; private Entry currentEntry; private StringBuilder builder; boolean inTitle; boolean inLink; boolean inSummary; boolean inUpdated; boolean inEntry; public ArrayList getEntries() { return this.entries; } @Override public void startDocument() throws SAXException { super.startDocument(); entries = new ArrayList (); builder = new StringBuilder(); } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { super.startElement(uri, localName, name, attributes); if (localName.equalsIgnoreCase(ENTRY)) { this.currentEntry = new Entry(); inEntry = true; } if (localName.equalsIgnoreCase(TITLE)) { inTitle = true; } if (localName.equalsIgnoreCase(LINK)) { inLink = true; } if (localName.equalsIgnoreCase(SUMMARY)) { inSummary = true; } if (localName.equalsIgnoreCase(UPDATED)) { inUpdated = true; } } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); if (inEntry) { if (inTitle) { builder.setLength(0); builder.append(ch, start, length); } if (inLink) { builder.setLength(0); builder.append(ch, start, length); } if (inSummary) { builder.append(ch, start, length); } if (inUpdated) { builder.setLength(0); builder.append(ch, start, length); } } } @Override public void endElement(String uri, String localName, String name) throws SAXException { super.endElement(uri, localName, name); if (this.currentEntry != null) { if (localName.equalsIgnoreCase(TITLE)) { currentEntry.setTitle(builder.toString()); inTitle = false; } else if (localName.equalsIgnoreCase(LINK)) { currentEntry.setLink(builder.toString()); inLink = false; } else if (localName.equalsIgnoreCase(SUMMARY)) { currentEntry.setSummary(builder.toString()); inSummary = false; } else if (localName.equalsIgnoreCase(UPDATED)) { currentEntry.setUpdated(builder.toString()); inUpdated = false; } if (localName.equalsIgnoreCase(ENTRY)) { entries.add(currentEntry); inEntry = false; } } } }
そしてAsyncTaskでSAXを呼ぶことにして、出来上がったArrayListを返すことにしました。
@Override protected ArrayListdoInBackground(Void... params) { try { URL url = new URL(xmlLocation); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); RSSHandler handler = new RSSHandler(); xr.setContentHandler(handler); //ハンドラークラスを設定 xr.parse(new InputSource(url.openStream())); entries = handler.getEntries(); } catch (MalformedURLException e) { Log.e("LoadFeedData", "MalformedUrlException: ", e); } catch (Exception e) { Log.e("LoadFeedData", "Parsing exception", e); } return entries; }
後は、ArrayAdapterに渡して、そこで項目ごとにListViewにデーターを入れるだけです。
ちなみに、詳細はViewはこんな感じに表示してみました。コードはHighlighted Syntaxで読んだほうが楽なので、結局ブラウザーで読みたくて「ブラウザーで表示」みたいなボタンも用意しておきました(汗)
以前にJSONデータを読み込んでListViewで表示するようなアプリは作ったことはあったんですが、XMLは初めてですので、勉強になりました。SAX Parserの仕様をもっと深く勉強する必要があると思いますけど、一応、このRSSリーダーでアドベントカレンダーを読んでおきます。