JavaAPIのParserDelegatorを利用してHTMLをパースする方法。

HTMLをパースする際、サードパーティのライブラリを利用したHTMLのパース方法はいくつか存在するが、 ここでは、JavaAPIを利用してHTMLをパースする方法を紹介したい。 今回HTMLパースに利用するJavaAPIは、ParserDelegatorというクラスである。 このクラスは、javax.swing.text.html.parserパッケージに同梱されている。 詳しくは、 ParserDelegatorのjavadoc を参照して欲しい。

ParserDelegatorに関連するクラスは、以下の通りである。

HTMLのパースに関連するクラス

  • javax.swing.text.html.parser.ParserDelegator
  • javax.swing.text.MutableAttributeSet
  • javax.swing.text.html.HTML
  • javax.swing.text.html.HTMLEditorKit
  • javax.swing.text.html.HTML.Tag
  • javax.swing.text.html.HTMLEditorKit.ParserCallback

今回は、下記のHTMLファイル(file.html)をパースし、inputタグだけを抽出し、inputタグのname属性とvalue属性をListに詰め込み、 詰め込まれたListの中身を標準出力するという、とても単純なサンプルプログラムを書いてみる。

file.html

<html>
<p>aaaaaaaaaaa</p>
<form name="form1" action="hogeAction">
	<p>ここからformです。</p>
	<input name="text1" type="text" value="value1" />
	<input name="text2" type="text" value="value2" />
	<input name="hidden1" type="hidden" value="value3" />
	<input name="hidden2" type="hidden" value="value4" />
	<input name="submit1" type="submit" value="value5" />
	<input name="submit2" type="submit" value="value6" />
	<p>ここまでformです。</p>
</form>
<a href="aaa.html">bbbbbb</a>
<p>ccccccc<p>
</html>

上記のHTMLをパースするJavaのソースコードは以下のようになる。

HtmlParserExample.java

package net.inuwasi.example.htmlparser;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
import javax.swing.text.html.parser.ParserDelegator;

/**
 * HTMLをパースするExample
 * <p>
 * {@link ParserDelegator} と {@link HTMLEditorKit.ParserCallback}
 * を利用してHTMLをパースするExampleです。<br>
 * このExampleでは、file.htmlというhtmlファイルを読み込み、htmlを解析し、
 * formタグ内のinputタグのnameとvalueをMapに詰め込みます。
 * </p>
 * 
 * @author admin@inuwasi.net
 */
public class HtmlParserExample {

    /** formタグ開始フラグ */
    private boolean startFormTag = false;

    /** inputタグのnameとvalueを格納するためのリスト */
    private List<String> list = new ArrayList<String>();

    public static void main(String[] args) throws Exception {
        new HtmlParserExample();
    }

    public HtmlParserExample() throws IOException {
        // file.htmlを読み込みます。
        BufferedReader reader = new BufferedReader(new FileReader("file.html"));

        // パーサーコールバック作成
        ParserCallback parserCallback = new HTMLEditorKit.ParserCallback() {

            /**
             * @see javax.swing.text.html.HTMLEditorKit.ParserCallback#handleText(char[],
             *      int)
             */
            @Override
            public void handleText(char[] data, int pos) {
                super.handleText(data, pos);
                // テキストがdataに入ってきます。
                // 例えば、"<p>ホゲ</p>" のようなタグがあった場合、"ホゲ" の部分がテキストにあたります。
                System.out.println("data=" + new String(data));
            }

            /**
             * @see javax.swing.text.html.HTMLEditorKit.ParserCallback#handleSimpleTag(javax.swing.text.html.HTML.Tag,
             *      javax.swing.text.MutableAttributeSet, int)
             */
            @Override
            public void handleSimpleTag(Tag t, MutableAttributeSet a, int pos) {
                super.handleSimpleTag(t, a, pos);

                // formタグが開始されていなければ処理を抜ける
                if (!startFormTag) {
                    return;
                }

                // inputタグのnameとvalueを取得
                if (t.equals(Tag.INPUT)) {
                    String name = (String) a.getAttribute(HTML.Attribute.NAME); // name属性を取得
                    String value = (String) a
                            .getAttribute(HTML.Attribute.VALUE); // value属性を取得
                    System.out.println("name=" + name + ", value=" + value);

                    // "name=value" の形式でリストに追加
                    list.add(name + "=" + value);
                }
            }

            /**
             * @see javax.swing.text.html.HTMLEditorKit.ParserCallback#handleStartTag(javax.swing.text.html.HTML.Tag,
             *      javax.swing.text.MutableAttributeSet, int)
             */
            @Override
            public void handleStartTag(Tag t, MutableAttributeSet a, int pos) {
                super.handleStartTag(t, a, pos);

                // formタグかどうかを判断
                if (t.equals(Tag.FORM)) {
                    System.out.println("formタグが開始しました。");
                    startFormTag = true;
                }
            }

            /**
             * @see javax.swing.text.html.HTMLEditorKit.ParserCallback#handleEndTag(javax.swing.text.html.HTML.Tag,
             *      int)
             */
            @Override
            public void handleEndTag(Tag t, int pos) {
                super.handleEndTag(t, pos);

                // formタグかどうかを判断
                if (t.equals(Tag.FORM)) {
                    System.out.println("formタグが終了しました。");
                    startFormTag = false;
                }
            }
        };

        // パーサーデリゲータ作成
        ParserDelegator parserDelegator = new ParserDelegator();

        // htmlをパースします。
        parserDelegator.parse(reader, parserCallback, false);

        // 出力してみる。
        System.out.println("listの中身を出力⇒ " + list.toString());
    }
}

上記のクラスを実行すると以下のように標準出力される。

data=aaaaaaaaaaa
formタグが開始しました。
data=ここからformです。
name=text1, value=value1
name=text2, value=value2
name=hidden1, value=value3
name=hidden2, value=value4
name=submit1, value=value5
name=submit2, value=value6
data=ここまでformです。
formタグが終了しました。
data=bbbbbb
data=ccccccc
listの中身を出力⇒ [text1=value1, text2=value2, hidden1=value3, hidden2=value4, submit1=value5, submit2=value6]

コードの解説

HTMLEditorKit.ParserCallbackをオーバーライド実装している部分が今回の肝となる。 handleStartTagメソッドでformタグの開始を判断し、 handleEndTagメソッドで、formタグの終了を判断している。 handleSimpleTagメソッドの中では、現在読み込まれているタグを評価し、それがinputタグであれば、 そのinputタグのname属性とvalue属性を取得し、結果表示用のリストへ格納している。 ParserCallbackが実装できたら、file.htmlを読み込んだInputStreamと、先ほどのParserCallback実装のインスタンスを parserDelegator#parse に渡し、 HTMLのパースを実行している。 そして最後は、結果表示用のリストを標準出力している。

このように、JavaAPIであるParserDelegatorHTMLEditorKit.ParserCallbackを利用すれば、面倒なHTMLパース処理も割と簡単に実現できるのである。

お薦めのJava本

Effective Java 第2版 (The Java Series)
Joshua Bloch
ピアソンエデュケーション
売り上げランキング: 4859
おすすめ度の平均: 5.0
5 正しいプログラミングを学べる
5 読んで、理解して、実践できれば、一人前
5 中級以上なら必須のマナー
5 Javaの良書

筆者お薦めの一冊。

金融のしくみは全部ロスチャイルドが作った (5次元文庫)

我々の生活に最も身近な存在であるはずの「お金」、しかし最も未知の部分が多い存在でもある。 本書を読めば、「お金の真実」を知る事が出来るかもしれない。