Quantcast
Channel: 未分類 –アララグループの技術者ブログ
Viewing all articles
Browse latest Browse all 25

Androidアプリ開発記【第八回】XMLの扱い

$
0
0

こんにちは、つじつじです。
またまた、前回から時間が経ってしまいました。

前回、API実行後のレスポンスのJSONの扱いについて触れましたが、レスポンスがXMLで返されるケースというのもあるかと思います。
今回は、Androidアプリ上でXMLを扱う方法について軽く触れたいと思います。

参考・引用元はこちらです。

AndroidでXMLを扱う場合にはJavaのXMLPULL V1 APIで用意されているインタフェイス、クラスを利用するか、XMLPULL V1 APIのインタフェイスと、その実装としてAndroidのXmlクラスを利用します。

ここでは、AndroidのXmlクラスを利用する方法を取り上げます。

対象のXMLの構造を把握する

XmlPullParserでの処理は、前回のJSONの処理で行ったように、基本的には

  • XMLを最初から読み込んでいき
  • 目的とするタグがあったら、その子要素のタグを読み込んでいく
  • 目的外のタグの場合はスキップし、次のタグの読み込みを行う

という流れで行います。従って、予め処理対象のXMLの構造を把握しておくことが重要となります。

ここでは以下のようなStackOverflowのAPIを実行した結果として以下のようなXMLをパースすることを前提とします。

entryという個別の投稿のタグの内側から、それぞれtitleとlink、summaryを取得していきます。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ...">
<title type="text">newest questions tagged android - Stack Overflow</title>
...
    <entry>
    ...
    </entry>
    <entry>
        <id>http://stackoverflow.com/q/9439999</id>
        <re:rank scheme="http://stackoverflow.com">0</re:rank>
        <title type="text">Where is my data file?</title>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/>
        <author>
            <name>cliff2310</name>
            <uri>http://stackoverflow.com/users/1128925</uri>
        </author>
        <link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" />
        <published>2012-02-25T00:30:54Z</published>
        <updated>2012-02-25T00:30:54Z</updated>
        <summary type="html">
            <p>I have an Application that requires a data file...</p>
        </summary>
    </entry>
    <entry>
        ...
    </entry>
...
</feed>

XMLのパース処理

上記のXMLをXMLを扱うためのクラスは以下のようになります。

public class StackOverflowXmlParser {
    // 今回は名前空間は使用しない
    private static final String ns = null;

    public List parse(InputStream in) throws XmlPullParserException, IOException {
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            parser.setInput(in, null);
            parser.nextTag();
            return readFeed(parser);
        } finally {
            in.close();
        }
    }

    private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
        List entries = new ArrayList();

        parser.require(XmlPullParser.START_TAG, ns, "feed");
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                 continue;
            }
            String name = parser.getName();
            // Starts by looking for the entry tag
            if (name.equals("entry")) {
                entries.add(readEntry(parser));
            } else {
               skip(parser);
            }
        }
        return entries;
    }

    public static class Entry {
        public final String title;
        public final String link;
        public final String summary;

        private Entry(String title, String summary, String link) {
            this.title = title;
            this.summary = summary;
            this.link = link;
        }
    }

    // entryタグの内部をパースしていき、title,summary,linkタグのいずれかの場合はそれぞれを読み込むメソッドを呼び出す
    // それ以外のタグの場合はスキップする
    private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
        parser.require(XmlPullParser.START_TAG, ns, "entry");
        String title = null;
        String summary = null;
        String link = null;
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                continue;
            }
            String name = parser.getName();
            if (name.equals("title")) {
                title = readTitle(parser);
            } else if (name.equals("summary")) {
                summary = readSummary(parser);
            } else if (name.equals("link")) {
                link = readLink(parser);
            } else {
                skip(parser);
            }
        }
        return new Entry(title, summary, link);
    }

    // titleタグの内容を読み込む
    private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {
        parser.require(XmlPullParser.START_TAG, ns, "title");
        String title = readText(parser);
        parser.require(XmlPullParser.END_TAG, ns, "title");
        return title;
    }

    // linkタグの内容を読み込む
    private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {
        String link = "";
        parser.require(XmlPullParser.START_TAG, ns, "link");
        String tag = parser.getName();
        String relType = parser.getAttributeValue(null, "rel");
        if (tag.equals("link")) {
            if (relType.equals("alternate")){
                link = parser.getAttributeValue(null, "href");
                parser.nextTag();
            }
        }
        parser.require(XmlPullParser.END_TAG, ns, "link");
        return link;
    }

    // summaryタグの内容を読み込む
    private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {
        parser.require(XmlPullParser.START_TAG, ns, "summary");
        String summary = readText(parser);
        parser.require(XmlPullParser.END_TAG, ns, "summary");
        return summary;
    }

    // titleまたはsummaryタグの場合はそのtextを取得する
    private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
        String result = "";
        if (parser.next() == XmlPullParser.TEXT) {
            result = parser.getText();
            parser.nextTag();
        }
        return result;
    }

    // 無関係のタグをスキップする
    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            throw new IllegalStateException();
        }
        int depth = 1;
        while (depth != 0) {
            switch (parser.next()) {
                case XmlPullParser.END_TAG:
                    depth--;
                    break;
                case XmlPullParser.START_TAG:
                    depth++;
                    break;
            }
        }
    }
}
  1. 5行目から15行目のparse()メソッドで、XmlPullParserインスタンスの取得し、APIのレスポンスのInputStreamを渡し、10行目のnextTag()でXMLの読み込みを開始します。
  2. 17行目からのreadFeed()で実際にXMLのパース処理を行います。
    1. 20行目で、次のXMLの開始タグがfeedであるかをチェックし、feedの内側を、終了タグを読み込むまで走査します。
    2. また、22行目では、feedの内側の開始タグの名前にまず関心があるため、各要素の開始タグ以外は次の要素に進むようにしています。
    3. 25行目でタグの名前を取得し、entryである場合は、さらにreadEntry()に進み、投稿の内容の取得を行い、そうでない場合はfeed内の同じ階層の次のタグのへ進みます。(skipは後述)
    4. readEntry()で読み込んだ結果は、一投稿毎に36行目で定義しているEntryクラスのオブジェクトに格納し、それをさらにentriesというListに格納します。
  3. readEntry以下でも同様にタグの名前をチェックし、title,summary,link毎に異なる読み込み処理を行います。
    1. 82行目のreadLink()内では、getAttributeValue()によって、更にタグの属性のチェックを行っています。
  4. skip()では読み込んだタグがentryではなかった場合に、その子要素の読み込みを全てスキップして、entryと同じ階層の次のタグの読み込みを行うための処理を行っています。
    1. スキップしたい親要素の読み込み開始時の階層を1とします。(120行目)
    2. 特定の要素の開始タグと終了タグは1対1で同じ階層にあるという前提を基に、次に読み込んだのが開始タグである場合には階層が1段階深くなったものとし、(126行目)、終了タグである場合にはその要素が終了し階層が1段階浅くなったものとします。
    3. 最終的に階層が0になった時点でスキップしたい要素が終了したと判断できます。
    4. 今回のXMLで<author>をスキップする場合を例に取ると、
      1. <author>の最初の子要素の開始タグは<name>で階層は1から2になります。
      2. </name>で最初の子要素が終了するので階層は1に戻ります。
      3. 次の子要素が<uri>によって開始され、階層は2になります。
      4. </uri>で終了され、階層は再び1に戻ります。
      5. </author>で更に終了タグが読み込まれ、階層が0となるため、<author></author>の読み込みが全で終了されます。

非常にざっくりとですが、XMLの処理はこんな感じになります。

JSONだと、オブジェクト化する方法もあったりするのですが、(前回そちらを使えばよかったと思っています。)XMLの場合もそうはいかないものでしょうか。


Viewing all articles
Browse latest Browse all 25

Trending Articles