JAXPでのデバッグ

もう何年もメンテナンスしているJavaのライブラリがあります.でもさわるのは2年に一度程度.元々海外からいただいたライブラリのなので、すべてを理解しているわけではなく、メンテナンスの都度ロジックを必死に思い出さねばなりません.なお話を複雑にしているのが、そのライブラリがXSLTプロセッサを通してXSLTスタイルシートから呼び出されることにあります.

   ↓
 XSLTプロセッサ(SaxonやXalan)
   ↓
 XSLTスタイルシート(XSLT1.0だったりXSLT2.0)
   ↓
 Javaライブラリ

普通はこういう構造です.でもライブラリの奥深いところを修正するとなると、この階層構造ではデバッグが出来ません.仕方がないのJAXP(Java API for XML Processing)を使ってXSLTプロセッサを呼び出し、それでデバッグするようにしています.

 Eclipse IDE環境
   ↓
 JAXPを使用するドライバープログラム
   ↓
 XSLTプロセッサ(SaxonやXalan)
   ↓
 XSLTスタイルシート(XSLT1.0だったりXSLT2.0)
   ↓
 Javaライブラリ

このドライバープログラムはもう何年も前に作ってほったらかしにしていました.今回Javaライブラリを修正したのでプロジェクトを再ビルドして動かしてみました.

ところが前はなんの問題もなく動いてくれていたはずのドライバでやたらエラーが出ます.全然まともに動いてくれません.例えば

java.lang.ClassCastException Message=net.sf.saxon.style.XSLStylesheet cannot be cast to net.sf.saxon.tree.DocumentImpl

これはXSLTスタイルシートがxsl:importやxsl:includeで別スタイルシートを呼び出すときに参照を解決するために作ったSystemResourceURIResolverクラス内で発生してしまいます.

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setURIResolver(new SystemResourceURIResolver());

何か変です.見てみるとこのSystemResourceURIResolverクラスではご丁寧に要求されたスタイルシートを読み込んでDOMを作り、そのドキュメントノードをjavax.xml.transform.Sourceとして返していました.

DOMSource styleSrc=new DOMSource([XSLTスタイルシートのDOMのドキュメントノード]);
styleSrc.setSystemId(path);
System.out.println("Loading stylesheet:"+path);
return styleSrc;

何故こんなプログラムにしたのか良くわからないのですが、たぶんサンプルをそのままデッドコピーしていたのでしょう.これはStreamSourceからSourceをコンストラクトするようにして解決しました.

Source xsl = new StreamSource(new File(path));
xsl.setSystemId(path);
System.out.println("Loading stylesheet:"+path);
return xsl;

ところが今度は

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setURIResolver(new SystemResourceURIResolver());
DOMSource xsl=new DOMSource([XSLスタイルシートのドキュメントノード]);
xsl.setSystemId(stylePath);
Transformer transformer = transformerFactory.newTransformer(xsl);
DOMSource domSource = new DOMSource([入力XMLファイルのドキュメントノード]);
File outf=new File("[XMLデータのパス]"+"/"+args[0]+".fo");
StreamResult streamResult=new StreamResult(outf);
transformer.transform(domSource, streamResult);

というコードで変換は動いてくれるようになったのですが、結果がやたら変です.XSL-FOを作っているのですが、XSLTスタイルシートJavaコマンドラインから動かしている時とは全然結果が違うのです.病状は

<xsl:template match="/">

のテンプレートがマッチしてくれていないような結果になっています.これもあきらめかけていたのですが、"/"にマッチしていないなら、入力XMLに問題があるのか?と思いDOMのドキュメントノードを渡していたのを

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setURIResolver(new SystemResourceURIResolver());
String stylePath="[XSLスタイルシートのパス]"+"/"+args[1]+".xsl";
Source xsl = new StreamSource(new File(stylePath));
xsl.setSystemId(stylePath);
Transformer transformer = transformerFactory.newTransformer(xsl);
String inputPath="[XMLデータのパス]"+"/"+args[0]+".xml";
Source inputSource = new StreamSource(new File(inputPath));
inputSource.setSystemId(inputPath);
File outf=new File("[XMLデータのパス]"+"/"+args[0]+".fo");
StreamResult streamResult=new StreamResult(outf);
transformer.transform(inputSource, streamResult);

とやったら一発で完璧に動いてくれました.時間はもう午前1時.現象から見て、XMLファイルのドキュメントノードからDOMSourceを作ったのではドキュメントノード「なし」の構造がスタイルシートに渡されていたということなのでしょう.でも以前は「間違いなく」動いていたので、Javaプラットフォームの変化につれて非互換部分が出てきたのだと思います.(何故ってライブラリのコードに設定したブレークポイントEclipseIDEでそのまま「生きて」いたからです.このドライバープログラムを使ってデバッグした証拠です.)

普段ほとんどJavaでプログラミングすることがないので、たまにやるとどうもこういう感覚についてゆけません.やはり、プログラムって日常的にさわっていないとダメですね.

ちなみに久しぶりにJavaでコーディングすると変数に再代入できることがとても不思議に感じられました.というよりなんいうか罪悪感すら感じられました.普段XSLTがほとんどで、最近ScalaやF#なんかをちょっと勉強しだしたのでmutableな変数というのはほぼ使う機会がないからです.