[iOS][Android] ライブラリを使わずに XML をパースする

2016.06.09

そんなに難しくないよ

XML のパースと聞くと「複雑」や「難しい」などのイメージが強く、なかなか手が出しづらいと思っていませんか?
私も前はそうでした。
実は実際に手を動かしてみると簡単なんです。
今回は XML のパースについて、ライブラリを使わずに OS の組み込みクラスのみを用いたミニマムレベルのサンプルをご紹介します。

使用する XML は こちら
a と入力した時の Google Suggest の結果です。
この XML をパースして、サジェストの結果を表示してみましょう。

説明を簡略化するため通信は行わず、XML はテキストで保持しておくことにします。

iOS

iOS で XML をパースするためには NSXMLParser クラスを使用します。

サンプルコード

@interface ViewController () <NSXMLParserDelegate>

@property (nonatomic, readonly) NSString *xmlString;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self parseXml];
}

- (void)parseXml
{
    NSData *xmlData = [self.xmlString dataUsingEncoding:NSUTF8StringEncoding];
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    parser.delegate = self;

    [parser parse];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqualToString:@"suggestion"]) {
        NSString *keyword = attributeDict[@"data"];
        NSLog(@"%@", keyword);
    }
}

- (NSString *)xmlString
{
    return @"\
    <toplevel> \
      <CompleteSuggestion> \
        <suggestion data=\"amazon\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"american airlines\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"aol mail\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"aol\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"autozone\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"amazon prime\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"airbnb\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"apple\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"agario\"/> \
      </CompleteSuggestion> \
      <CompleteSuggestion> \
        <suggestion data=\"american express\"/> \
      </CompleteSuggestion> \
    </toplevel>";
}

@end

実行結果

amazon
american airlines
aol mail
aol
autozone
amazon prime
airbnb
apple
agario
american express

解説

長いコードに見えますが、実際に XML に関する処理を行っているのは 10 行程度です。
長いのはデータの文字列です。


NSData *xmlData = [self.xmlString dataUsingEncoding:NSUTF8StringEncoding];

ここで文字列を NSData に変換しています。
NSXMLParser のイニシャライザに渡すためです。
適切なエンコーディングを設定しましょう。


NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
parser.delegate = self;

NSXMLParser のインスタンスを生成し、デリゲートをこのクラスに設定しています。


[parser parse];

このコードでパース処理が実行され、適切なタイミングでデリゲートメソッドが呼ばれるようになります。


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqualToString:@"suggestion"]) {
        NSString *keyword = attributeDict[@"data"];
        NSLog(@"%@", keyword);
    }
}

NSXMLParser クラスのデリゲートメソッドの 1 つです。
要素の開始タグが読み込まれた時に呼ばれます。

今回の例ですと、空要素 suggestion の属性 data の値が欲しいだけなので、このメソッド 1 つで事足ります。
ここでやっていることは、suggestion の開始タグが読み込まれた時に data 属性の値を keyword という変数に代入し、ログ出力するというものです。


この程度の XML ならばこれだけの処理でパースできてしまいます。
簡単ですね。

Android

Android で XML をパースするためには XmlPullParser クラスを使用します。

サンプルコード

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            parseXml();
        } catch (XmlPullParserException | IOException e) {
            e.printStackTrace();
        }
    }

    private void parseXml() throws XmlPullParserException, IOException {
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        XmlPullParser parser = factory.newPullParser();
        parser.setInput(new StringReader(xmlString()));

        int eventType = parser.getEventType();

        while (eventType != XmlPullParser.END_DOCUMENT) {
            if (eventType == XmlPullParser.START_TAG) {
                if (parser.getName().equals("suggestion")) {
                    String keyword = parser.getAttributeValue(null, "data");
                    System.out.println(keyword);
                }
            }

            eventType = parser.next();
        }
    }

    private String xmlString() {
        return ""
                + "<toplevel>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"amazon\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"american airlines\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"aol mail\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"aol\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"autozone\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"amazon prime\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"airbnb\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"apple\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"agario\"/>"
                + "</CompleteSuggestion>"
                + "<CompleteSuggestion>"
                + "<suggestion data=\"american express\"/>"
                + "</CompleteSuggestion>"
                + "<toplevel>";
    }
}

実行結果

amazon
american airlines
aol mail
aol
autozone
amazon prime
airbnb
apple
agario
american express

解説

やっていることは iOS と同じです。


XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();

ファクトリクラスを利用して XmlPullParser のインスタンスを生成しています。
ここではファクトリに対して設定は行っていません。


parser.setInput(new StringReader(xmlString()));

パーサに対して XML データを渡しています。


int eventType = parser.getEventType();

getEventType() でパースを開始しています。
開始時は START_DOCUMENT という値が返ってきます。


while (eventType != XmlPullParser.END_DOCUMENT) {
    // 省略

    eventType = parser.next();
}

XML が全て読み込まれるまで、繰り返し次のイベントを取得していきます。


if (eventType == XmlPullParser.START_TAG) {
    if (parser.getName().equals("suggestion")) {
        String keyword = parser.getAttributeValue(null, "data");
        System.out.println(keyword);
    }
}

開始タグが読み込まれた時、その要素名が suggestion だった場合に data 属性の値を keyword という変数に代入し、ログ出力しています。

まとめ

  • XML のパースは以下のクラスを使用すれば実現可能
    • iOS: NSXMLParser
    • Android : XmlPullParser
  • ライブラリを使わずとも、短いコードで書くことができる
  • そんなに難しくない

リンク

ミレニアム・ファルコン製作日記 #19

19 号 表紙

mfd_19_1

パーツ

mfd_19_2

mfd_19_3

mfd_19_4

成果

mfd_19_5

mfd_19_6

今回の作業は以下の 2 つでした。

  • 穴メカのディテールアップ
  • 通路チューブを組み立てる

穴メカ(あなめか) と読むのでしょうか?
どうやらこれはミレニアム・ファルコン専用の言葉のようです。
穴の中に見えるメカってことかな。

このパーツは船体底面に取り付けられます。
細かいパーツを 7 つほど接着しています。
映画の中では船体下部はあまり確認できないので、今回のようにじっくり観察できたのは楽しかったです。
相変わらず「こだわってるなぁ」と感じました。

はい来ました、アーチの作成。
内部の通路を組み立て、アーチを 3 つ取り付けました。
アーチの作成も一周回って楽しくなってきました。

それではまた次回。

May the Force be with you!