JavaでXMLを簡単に解析する方法

JAXB(Java Architecture for XML Binding)を使う方法を,Yahoo校正支援APIを題材に解説します.

どこら辺が「簡単」なのか?

各種API系のサービスで配布されている,XMLスキーマファイル(xsd)から,Javaクラスファイル群を自動生成することで,
自前のコードはほんの一握り(テンプレートにできそうなぐらい,一部のみです)になり,かつ自前でParseすることを考えたら,手間も品質も雲泥の差です.

正規表現で無理やり,とか怪しすぎるし,SAXやDOMを使えば意のままにコーディングできるけど拡張大変だし.
というわけで,JAXBに丸投げします.

前提環境

0. JAXB用のクラスファイルの自動生成

まず,http://jlp.yahooapis.jp/KouseiService/V1/kousei.xsd をダウンロードします.次に,Terminalで,以下のコマンドを入力

$ xjc kousei.xsd

すると,以下のディレクトリ,ファイルが生成されます.

./yahoo
`-- jp
    `-- jlp
        `-- kouseiservice
            |-- ObjectFactory.java
            |-- ResultSet.java
            |-- WordType.java
            `-- package-info.java

私は,この後に,kouseiserviceと同じところにkousei.xsdを移動しました.

1. サクッとテストプログラム作成

Kousei.javaを作成

import java.net.URL;
import java.net.URLEncoder;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.xml.sax.SAXException;
import yahoo.jp.jlp.kouseiservice.*;
/**
 *
 * @author Syo Takasaki
 */
public class Kousei {
    private static String appid  = "dummy";
    private static String appuri = "http://jlp.yahooapis.jp/KouseiService/V1/kousei";
    private static String path_xsd    = "yahoo/jp/jlp/kouseiservice/kousei.xsd";
    private static URL schemaURL;
    private static Schema schema;
    private static String path_context = "yahoo.jp.jlp.kouseiservice";
    private static JAXBContext context;
    private static Unmarshaller unmarshaller;

    public static void main(String[] args) throws Exception{
        Kousei exe = new Kousei();
        ResultSet results = exe.getResults("遙か彼方に小形飛行機が見える.");
        System.out.println("SENTENCE:遙か彼方に小形飛行機が見える.");
        for(WordType data : results.getResult()){
            System.out.print(data.getStartPos() + "\t");
            System.out.print(data.getLength() + "\t");
            System.out.print(data.getSurface() + "\t");
            System.out.print(data.getShitekiInfo() + "\t");
            System.out.println(data.getShitekiWord());
        }
        results = exe.getResults("モルジブ大学行く.大学行く.");
        System.out.println("SENTENCE:モルジブ大学行く.大学行く.");
        for(WordType data : results.getResult()){
            System.out.print(data.getStartPos() + "\t");
            System.out.print(data.getLength() + "\t");
            System.out.print(data.getSurface() + "\t");
            System.out.print(data.getShitekiInfo() + "\t");
            System.out.println(data.getShitekiWord());
        }
    }

    public Kousei() throws SAXException, JAXBException{
        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        schemaURL = this.getClass().getClassLoader().getResource(path_xsd);
        schema = factory.newSchema(schemaURL);
        context = JAXBContext.newInstance(path_context);
        unmarshaller = context.createUnmarshaller();
        unmarshaller.setSchema(schema);
    }

    public ResultSet getResults(String sentence){
        ResultSet results = null;
        try{
            URL _url = new URL(appuri + "?appid=" + appid + "&sentence=" + URLEncoder.encode(sentence, "UTF-8"));
            results = (ResultSet)unmarshaller.unmarshal(_url);
        }catch (Exception e){
            System.err.println("ERROR:" + e);
        }
        return results;
    }
}

2. 実行

$ javac Kousei.java
$ java Kousei
SENTENCE:遙か彼方に小形飛行機が見える.
0        2        遙か        表外漢字あり        ●か        
2        2        彼方        用字        彼方(かなた)、かなた        
5        5        小形飛行機        誤変換        小型飛行機        
SENTENCE:モルジブ大学行く.大学行く.
0        4        モルジブ        外国地名        モルディブ        
9        4        大学行く        助詞不足の可能性あり   

後書きと懺悔

きっかけは,Play framework上でGroovyがうまく使えなかったから(テンプレートエンジンはGroovyだけど,それ以外のところで使いたかった).

「GroovyならサクッとXMLパースもできるんだけど……Pure Javaか」……と意気消沈したところで,調べてみたわけ.

で,エイヤと作ったはいいものの,エラーハンドリングは適当過ぎるし,いくつかのサイトを参考にしたからコーディングスタイルもごちゃごちゃだし.
そもそも引数でテスト文字列与えられないってどういうことよ,自分orz

でも動いたから満足してしまった.path_xsdとpath_contextが良く分からず,いろんな指定の組み合わせをがんばって,一番良さげな書き方に落ち着いたところが一番満足.

くれぐれも「このまま」は使わないように.

なお,生成は以下のURLの1番目を参考にしてください.
marshallerを追加するだけでいけると思います.