Digesterを使ってXMLをパースする際に文字コードでハマった経験があるので、覚え書き。
今回のお題は「MS932のくせにencoding="Shift_JIS"と記述しているXMLをDigesterに食わせるとどうなるか。」です。
サンプルとして下記のようなXMLをDigesterを使ってパースしてみたいと思います。
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE sample SYSTEM "../dtd/932sample.dtd">
<sample>
<item>
<id>1</id>
<value>あああ</value>
</item>
<item>
<id>2</id>
<value>全角のにょろ「〜」</value>
</item>
<item>
<id>3</id>
<value>全角のマイナス「−」</value>
</item>
</sample>
最初のXML宣言のところで、
<?xml version="1.0" encoding="Shift_JIS"?>
と、エンコーディングをShift_JISで指定しています。が、もし、このXMLファイルがShift_JISのつもりなのに実際はMS932で作成されていたらどうなるでしょうか?
エンコーディングを気にせずに普通にDigester#parse(url)メソッドでパースすると、〜や−が文字化けしてしまいます。
/////////////////何も考えずにパースするの例///////////////////////////
/** パース対象のXML */
public static final String TARGET = "./xml/932sample.xml";
public Sample parseNormal() throws IOException, SAXException {
Digester digester = createDigester();
Sample sample = (Sample)digester.parse(TARGET);
return sample;
}
public static void main(String[] args) {
Digester932Test test = new Digester932Test();
try {
Sample sample = test.parseNormal();
System.out.println(sample);
} catch (IOException ex) {
System.out.println(ex.toString());
} catch (SAXException ex) {
System.out.println(ex.toString());
}
}
パースした結果を表示させて見ると、下記のように文字化けするはずです。
item = [ id:1, value:あああ ]
item = [ id:2, value:全角のにょろ「?」 ]
item = [ id:3, value:全角のマイナス「?」 ]
そこで、今度はparseする際にXMLファイルのURLを指定するのではなく、文字エンコーディングを指定したInputSourceを利用してみましょう。
/////////////////エンコーディングを指定してパースするの例///////////////////////////
public Sample parseByInputSource() throws IOException, SAXException {
InputSource is = null;
InputStreamReader reader = null;
//エンコーディングを指定したInputStreamReaderを作成してInputSourceを初期化する
reader = new InputStreamReader(
new FileInputStream(TARGET), "MS932");
is = new InputSource(reader);
Digester digester = createDigester();
Sample sample = (Sample)digester.parse(is);
return sample;
}
さて、これでエンコーディングを指定したので、ちゃんとパースされるはず。
ですが、実行してみると、環境によっては下記のようなエラーが発生します。
java.io.FileNotFoundException: /path/to/932sample.dtd (指定されたパスが見つかりません。)
どうも、XMLのDOCTYPEでDTDが相対パスで指定されている場合、
- Digester#parse(url)を使用した場合:xmlファイルからの相対パスでDTDを探しに行く。
- Digester#parse(inputsource)を使用した場合:javaを実行したディレクトリからの相対パスでDTDを探しに行く。
という動きになるように見受けられます。
これではイカンのでもう一工夫必要のようです。色々試してみた結果、下記の工夫に行き着きました。
InputSource#setSystemIdメソッドでXMLファイルを指定すると、XMLファイルからの相対パスでDTDを探しにいくようだ。
/////////////////エンコーディングを指定してパースするの例///////////////////////////
public Sample parseByInputSource() throws IOException, SAXException {
InputSource is = null;
InputStreamReader reader = null;
//エンコーディングを指定したInputStreamReaderを作成してInputSourceを初期化する
reader = new InputStreamReader(
new FileInputStream(TARGET), "MS932");
is = new InputSource(reader);
//XMLファイルのURLをSystemIdとしてsetする。
is.setSystemId(TARGET);
Digester digester = createDigester();
Sample sample = (Sample)digester.parse(is);
return sample;
}
こうすると、下記のように文字化けしないでパースできました。
item = [ id:1, value:あああ ]
item = [ id:2, value:全角のにょろ「〜」 ]
item = [ id:3, value:全角のマイナス「−」 ]
と、ここまで試してみて、InputSource#setEncodingというメソッドがあることに気がついてこれも試してみました。
InputSource is = new InputSource();
is.setSystemId(TARGET);
is.setEncoding("MS932");
APIドキュメント的にはこれで期待通りに動いてくれるはず。。。だったのですが、こちらはきれいに文字化けしてしまいました。
うーむ。なんか納得いかない。
本日のまとめ。
エンコーディングを指定したInputStreamReaderを渡してInputSourceを初期化する。
InputSource#setSytemIdでXMLファイルへのURLを指定する。
そのInputSourceをDigester#parseメソッドへ渡す。