Java SAX

初めてSAXを勉強したのでご紹介します。

例として、気象庁が提供している天気予報を使います。以下のURLをクリックしてください。向こう1週間の気象情報がXML形式で表示されます(下で表示されるのは、千葉県の情報です)。

http://www.drk7.jp/weather/xml/12.xml

一部をコピーします。

<weatherforecast>
<pref id="千葉県">
 <area id="北東部">
    <geo>
        <long>140.6877</long>
        <lat>35.7765</lat>
    </geo>
    <info date="2015/08/22">
        <weather>くもり</weather>
        <img>http://www.drk7.jp/MT/images/MTWeather/200.gif</img>
        <weather_detail>南の風 海上 では 南の風 やや強く 晴れ 夕方 から くもり</weather_detail>
        <wave>波 4メートル うねり を伴う</wave>
        <temperature unit="摂氏">
        <range centigrade="max">27</range>
        <range centigrade="min">24</range>
        </temperature>
        <rainfallchance unit="%">
        <period hour="00-06">20</period>
        <period hour="06-12">20</period>
        <period hour="12-18">20</period>
        <period hour="18-24">30</period>
        </rainfallchance>
    </info>
    

XMLとしては単純です。このXMLドキュメントから次のような形にして表示したいと思います。

  千葉県 北東部
    日時  2015/08/22
      天気  くもり
      天気詳細 南の風 海上 では 南の風 やや強く 晴れ 夕方 から くもり
      波 4メートル うねり を伴う
      気温 24 から27度
      降水確率 00-06 20%
           06-12 20%
           12-18 20%
           18-24 20%
    日時  2015/08/23
       天気  くもり時々晴れ
       天気詳細 北東の風 のち やや強く くもり 所により 夜 雨
       波 4メートル うねり を伴う
       気温 24から27度
       降水確率 00-06 20%
            06-12 20%
            12-18 20%
            18-24 20%
   ・・・・・

気象庁のXML情報(以下XMLドキュメントといいます)のルートタグは<weatherforecast>です。上のように表示するには、このXMLの内、areaタグとその下部要素のinfoタグが必要です。

千葉県の情報なので、prefタグは一つですが、areaタグは複数(3つ)、areaタグの下にも複数のinfoタグがあります。

infoタグの下には、weather、weather_detail、waveタグがあり、更に、複数のtemperatureタグとrainfallchanceタグがあります。

前回ご説明しましたが、SAXはXMLドキュメントを上から順に調べていきます。

調べた情報をどのような形で集めるか。

一番いいのはクラスを定義して、このクラスに情報を書き込んでいくのがいいと思います。SAXがXMLを調査していく過程で、必要な情報をクラスに書き込み、最終的にそのクラス(リスト)を取り出して、更に必要な処理をします。

ここで、クラスAreaを定義します。

Areaのメンバー変数は、AreaName、とInfoサブクラスです。

	public class Area {
		public ArrayList<Info_data> o_Infolst;
		public String AreaName;

		public  class Info_data {
			public String dt;
			public String weather;
			public String weather_detail;
			public int[] range = new int[2];
			public String wave;
			public int[] rainfall = new int[4];
		}
	}

上のクラス定義は概念的なものです。正確ではありません。

さて、JavaでSAXを処理するために、DefaultHandlerクラスがあります。ユーザはこのクラスを継承した独自クラスを定義し、このDefaultHandlerにXMLドキュメントの処理を委ねます。

DefaultHandlerの核心は、startElement、endElementイベントハンドラです。DefaultHandlerがXMLドキュメントを読み進んでいて、開始タグに出会うとstartElementイベントが起動され、終了タグに出会うとendElementイベントが起動します。

その時なにをするかは、プログラマが目的に沿って独自にコードを書きます。

DefaultHandlerにはもう一つ、重要なしかしよくわからないcharactersというメソッドがあります。

DefaultHandlerが要素の内容(Text)に出会うとその内容を取り出すようですが、あまり詳しい動作は分かりません。以下に今回の処理のために作成したSAXコードの主要部分を示します。

public class SAXHandler extends DefaultHandler {

    private ArrayList<Area> o_lstArea = new ArrayList<Area>();
    private Area o_Area;

    private List<Area.Info_data> o_Infos;
    private Area.Info_data o_Info;

    private int rangCnt;
    private int periodCnt;

    private String tempVal;

    public ArrayList<Area> getAllAreaWeather() {
        return o_lstArea;
    }

    @Override
    public void startElement(String nsURI, String strippedName, String tagName,
                             Attributes attributes)   throws SAXException {
        tempVal = "";
        if (strippedName.equalsIgnoreCase("area")) {
            o_Area = new Area();
            o_Area.setAreaName(attributes.getValue("id"));

            o_lstArea.add(o_Area);
        } else if (strippedName.equalsIgnoreCase("info")) {
            o_Info = o_Area.makeInfo();
            o_Info.setDt(attributes.getValue("date"));
        }  else if (strippedName.equalsIgnoreCase("temperature")) {
            rangCnt = 0;
        }  else if (strippedName.equalsIgnoreCase("rainfallchance")) {
            periodCnt = 0;
        }
    }

    @Override
    public void endElement(String name, String localName, String qName) {
        if (qName.equalsIgnoreCase("weather")) {
            o_Info.setWeather(tempVal);
        } else if (qName.equalsIgnoreCase("weather_detail")) {
            o_Info.setWeather_detail(tempVal);
        } else if (qName.equalsIgnoreCase("wave")) {
            o_Info.setWave(tempVal);
        } else if (qName.equalsIgnoreCase("range")) {
            o_Info.getRange()[rangCnt++] = Integer.parseInt(tempVal);
        } else if (qName.equalsIgnoreCase("period")) {
            o_Info.getRainfall()[periodCnt++] = Integer.parseInt(tempVal);
        }
    }

    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
            tempVal = new String(ch, start, length);
    }
}

上のコードで、[o_Area.setInfo]や[o_Info.setWeather_detail]等は上では省略したAreaクラスのメソッドです。

SAXHandlerのstartElementイベントでは、SAXがXMLの要素(ノード)を読んだときに新たな[Areaオブジェクト]を作ったり、Areaオブジェクトのサブオブジェクトを作成したり、温度や降水確率用の配列インデックスをクリアしたりしています。

一方のendElementイベントでは、要素の内容(Text)を読みこんだときに、その内容をAreaオブジェクトやInfoオブジェクトに書き込んでいます。

その書き込んでいる情報というのが、charactersで取得しているtempValです。

メインプログラムはSAXHandlerをコールし、SAXHandlerが作成したリスト変数[o_lstArea]を取得し、必要な処理を続けます。

 

ところで、WindowsプログラムではSAXを使いません。同等のクラスとしてXmlReaderがあります。

SAXでは、XMLドキュメントの処理をSAXHandler(DefaultHandler)が処理します。すなわち、処理はSAXHandlerの中に記述しますが、XmlReaderはXMLドキュメントを読み込むだけで、あとはコール側で処理プログラムを書きます。

同じようなものですが、XmlReaderにはcharactersのようなよくわからないメソッドはなく、すべて可視的なので、趣味の問題かもしれませんがXmlReaderの方が安心できます。

更に、Windowsにはデータ処理の手法として標準でLINQがあり、[LINQ to XML]はXML処理に関してはもっと簡単かもしれません。

次回は、XmlReaderではこの問題をどのようにコーディングするのか、ご紹介します。続いて、XSLTのご紹介もしたいと思います。

error: コピーできません !!