初めて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のご紹介もしたいと思います。