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