2009年6月19日

【Java】XML文書をSAXで処理してみるサンプル【C#】

 DOM(Document Object Model)はXML文書全体を読み込んでツリー構造に展開するから遅いしメモリを喰うんだぜ、ってことで作られたのがSAX(Simple API for XML)です。 ただし順次参照専用のAPIなので、単純な構造のXML文書を読み込む処理にしか向きません。(´・∀・`)ヘー

入力するXMLファイル

 お題にはDOMプログラムのときと同じGundamList.xmlを使います。
<?xml version="1.0" encoding="Shift_JIS" ?>

<!DOCTYPE gundamList SYSTEM "GundamList.dtd">

<gundamList xmlns="urn:gundam:anime">
<gundam media="TV">
<name>機動戦士ガンダム</name>
<calendar jpn="宇宙世紀" eng="U.C.">0079</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="TV">
<name>機動戦士Zガンダム</name>
<calendar jpn="宇宙世紀" eng="U.C.">0087</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="TV">
<name>機動戦士ガンダムZZ</name>
<calendar jpn="宇宙世紀" eng="U.C.">0088</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="MOVIE">
<name>機動戦士ガンダム 逆襲のシャア</name>
<calendar jpn="宇宙世紀" eng="U.C.">0093</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="OVA">
<name>機動戦士ガンダム0080 ポケットの中の戦争</name>
<calendar jpn="宇宙世紀" eng="U.C.">0080</calendar>
<director>高山文彦</director>
</gundam>
<gundam media="MOVIE">
<name>機動戦士ガンダムF91</name>
<calendar jpn="宇宙世紀" eng="U.C.">0123</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="OVA">
<name>機動戦士ガンダム0083 STARDUST MEMORY</name>
<calendar jpn="宇宙世紀" eng="U.C.">0083</calendar>
<director>加瀬充子</director>
<director>今西隆志</director>
</gundam>
<gundam media="TV">
<name>機動戦士Vガンダム</name>
<calendar jpn="宇宙世紀" eng="U.C.">0153</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="TV">
<name>機動武闘伝Gガンダム</name>
<calendar jpn="未来世紀" eng="F.C.">60</calendar>
<director>今川泰宏</director>
</gundam>
<gundam media="TV">
<name>新機動戦記ガンダムW</name>
<calendar jpn="アフターコロニー" eng="A.C.">195</calendar>
<director>池田成</director>
<director>高松信司</director>
</gundam>
<gundam media="TV">
<name>新機動戦記ガンダムX</name>
<calendar jpn="アフターウォー" eng="A.W.">15</calendar>
<director>高松信司</director>
</gundam>
<gundam media="OVA">
<name>機動戦士ガンダム 第08MS小隊</name>
<calendar jpn="宇宙世紀" eng="U.C.">0079</calendar>
<director>神田武幸</director>
<director>飯田馬之介</director>
</gundam>
<gundam media="OVA">
<name>新機動戦記ガンダムW Endless Waltz</name>
<calendar jpn="アフターコロニー" eng="A.C.">196</calendar>
<director>青木康直</director>
</gundam>
<gundam media="OVA">
<name>機動戦士ガンダム 第08MS小隊 特別編 ラスト・リゾート</name>
<calendar jpn="宇宙世紀" eng="U.C.">0079</calendar>
<director>森邦宏</director>
<director>仕舞屋鉄</director>
</gundam>
<gundam media="TV">
<name>∀ガンダム</name>
<calendar jpn="正暦" eng="C.C.">2345</calendar>
<director>富野由悠季</director>
</gundam>
<gundam media="TV">
<name>機動戦士ガンダムSEED</name>
<calendar jpn="コズミック・イラ" eng="C.E.">71</calendar>
<director>福田己津央</director>
</gundam>
<gundam media="TV">
<name>機動戦士ガンダムSEED DESTINY</name>
<calendar jpn="コズミック・イラ" eng="C.E.">73</calendar>
<director>福田己津央</director>
</gundam>
<gundam media="OVA">
<name>機動戦士ガンダムSEED C.E.73 STARGAZER</name>
<calendar jpn="コズミック・イラ" eng="C.E.">73</calendar>
<director>西澤晋</director>
</gundam>
<gundam media="TV">
<name>機動戦士ガンダム00 ファーストシーズン</name>
<calendar jpn="西暦" eng="A.D.">2307</calendar>
<director>水島精二</director>
</gundam>
<gundam media="TV">
<name>機動戦士ガンダム00 セカンドシーズン</name>
<calendar jpn="西暦" eng="A.D.">2312</calendar>
<director>水島精二</director>
</gundam>
</gundamList>


SAXプログラムの出力

 作成するSAXプログラムは「GundamList.xmlの中から富野由悠季監督のガンダム作品を抽出する」プログラムで、次のような出力結果を目指します。
U.C.0079 機動戦士ガンダム
U.C.0087 機動戦士Zガンダム
U.C.0088 機動戦士ガンダムZZ
U.C.0093 機動戦士ガンダム 逆襲のシャア
U.C.0123 機動戦士ガンダムF91
U.C.0153 機動戦士Vガンダム
C.C.2345 ∀ガンダム
以上、7作品


SAXプログラムの設計

 それでは設計開始。(`・ω・´)シャキーン SAXはイベント駆動型APIなので、欲しいデータがどのイベントで手に入るか、データが揃って処理できるタイミングはいつなのかを整理していきます。
  • eng値 => startElement("calendar")イベントのAttributes.getValue("eng")。
  • year値 => startElement("calendar")イベントに続くcharactersイベント。
  • name値 => startElement("name")イベントに続くcharactersイベント。
  • 出力判定 => startElement("director")イベントに続くcharactersイベント。
  • 出力位置 => endElement("gundam")イベント。


Javaでの実装

 Javaでの実装(GundamSax.java)は次のようになりました。 SAXではstartElementイベントcharactersイベントを繋ぐ一時変数が必須ですね。
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class GundamSax extends DefaultHandler
{
public static void main(String args[])
{
try
{
// XMLReaderオブジェクトの生成
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader reader = sp.getXMLReader();
reader.setFeature("http://xml.org/sax/features/validation", true);
reader.setFeature("http://xml.org/sax/features/namespaces", true);

// イベントハンドラを登録
DefaultHandler handler = new GundamSax();
reader.setContentHandler(handler);

reader.parse("GundamList.xml");
}
catch(Exception ex)
{
System.out.println(ex.toString());
}
}


// 出力用変数
private String parent = null;
private String name = null;
private String eng = null;
private String year = null;
private boolean directed = false;
private int count = 0;


// 要素の開始イベント実装
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException
{
if(localName.equals("name"))
{
parent = localName;
}
else if(localName.equals("calendar"))
{
parent = localName;

// calendar要素のeng属性を保持
eng = atts.getValue("eng");
}
else if(localName.equals("director"))
{
parent = localName;
}
}


// 文字データイベントの実装
public void characters(char ch[], int start, int length) throws SAXException
{
String value = new String(ch, start, length);

if(parent == null)
{
// 無視
return;
}
else if(parent.equals("name"))
{
// name要素の値を保持
name = value;
}
else if(parent.equals("calendar"))
{
// calendar要素の値を保持
year = value;
}
else if(parent.equals("director"))
{
// director要素(複数)の値を判定
if(directed == false)
{
directed = value.equals("富野由悠季");
}
}
}


// 要素の終了イベント実装
public void endElement(String uri, String localName, String qName) throws SAXException
{
if(localName.equals("gundam"))
{
// 富野作品のみ出力
if(directed == true)
{
System.out.println(eng + year + " " + name);

// 作品数をカウント
count++;
}

// 出力用変数を初期化
directed = false;
}

parent = null;
}


//文書の終了イベント実装
public void endDocument() throws SAXException
{
// 作品数を出力
System.out.println("以上、" + count + "作品");
}
}


C#での実装

 それでは続いてC#での実装を~っと思って調べてみたら、C#(.NET Framework)のXmlReaderは、SAXで定義されているXMLReaderとは別物であることが発覚。(;゚Д゚)(゚Д゚;(゚Д゚;)ナ、ナンダッテー!!

C#のXmlReaderはイベント駆動(プッシュ型)でなく、プル型(自分でループを回してread()を呼ぶ)のAPIとなっているそうです。


 そんなC#での実装(GundamReader.cs)は次のようになりました。 SAXではありませんから、XMLマスター:プロフェッショナル試験とは無関係です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace GundamReader
{
class GundamReader
{
static void Main(string[] args)
{
// XMLReaderオブジェクトの生成
XmlReaderSettings setting = new XmlReaderSettings();
setting.ProhibitDtd = false;
setting.ValidationType = ValidationType.DTD;
setting.IgnoreWhitespace = true;
XmlReader reader = XmlReader.Create(@"../../GundamList.xml", setting);


// 出力用変数
bool directed = false;
int count = 0;

while (reader.Read())
{
if (reader.IsStartElement("gundam"))
{
// name要素の値を保持
reader.Read();
reader.ReadStartElement("name");
String name = reader.ReadString();

// calendar要素の値とeng属性を保持
reader.ReadToNextSibling("calendar");
String eng = reader.GetAttribute("eng");
String year = reader.ReadString();

// director要素(複数)の値を判定
while (directed == false &&
reader.ReadToNextSibling("director"))
{
String director = reader.ReadString();
directed = director.Equals("富野由悠季");
}

// 富野作品のみ出力
if (directed == true)
{
Console.WriteLine("{0}{1} {2}", eng, year, name);

// 作品数をカウント
count++;
}

// 出力用変数を初期化
directed = false;
}
}

// 作品数を出力
Console.WriteLine("以上、{0}作品", count);
}
}
}


 APIの使い方を調べながらだったので苦労しましたけど、プル型のAPIの方がSAXよりシンプルなコードが書けて良いですね。ъ(゚Д゚)グッジョブ!!

なんでもかんでも継承したりイベントハンドラで処理させようとするのは、オブジェクト指向が生んだ悪しき慣習だと思います。( ゚д゚)、ペッ



 試験対策本ではありますが、JavaでのXMLプログラミングを学ぶ入門書としても役立ちます。 つか他に碌な本が無いよね。( ゚Д゚)y─┛~~
章の始めに各インターフェースのプロパティとメソッド一覧が載ってますけど、眠たくなるので最初は読み飛ばして後で戻ってチェックするのがお薦め。

【関連記事】

0 件のコメント:

コメントを投稿