倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

システムリソースからスタイルシートを読み込む (2)

今回はシステムリソース内のスタイルシートが他のスタイルシートを読み込む(<import>, <include> 要素を持つ)場合を考えます。

何の設定もせずに前回と同じ方法をとると、「(読み込まれる方の)スタイルシートが見つからない」旨の例外が投げられます。 これは、読み込み先のスタイルシートをベースディレクトリ上のファイルとして読み込もうとしているためです。

これを解決するには、独自に javax.xml.transform.URIResolver の実装クラスを作成する必要があります(標準 API で提供されててもいい気はしますが)。 ここではその実装クラスを org.sample.SystemResourceURIResolver として作成します。

ソースコードの配置

ファイル名 パス 説明
Main.java org/sample/ XSL 変換を実行する main() メソッドをもつクラスです。
SystemResourceURIResolver.java org/sample/ スタイルシートをシステムリソースとして読み込むためのクラスです。
stylesheet.xsl org/sample/resources/xsl/ 開発者が作成したスタイルシート。 Main クラス内から直接読み込まれます。 このスタイルシート内から「includee-stylesheet.xsl」が読み込まれます。
includee-stylesheet.xsl org/sample/resources/xsl/ 開発者が作成したスタイルシートです。

stylesheet.xsl, includee-stylesheet.xsl


「includee-stylesheet.xsl」(読み込まれる側のスタイルシート)は特に制限はありません。

「stylesheet.xsl」は「includee-stylesheet.xsl」を読み込んでいるとします:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <!-- 「includee-stylesheet.xsl」を読み込んでいる -->
    <xsl:include href="includee-stylesheet.xsl"/>
    ...
</xsl:stylesheet>

SystemResourceURIResolver.java


javax.xml.transform.URIResolver インターフェースは、スタイルシート内に出てきた URI を独自の方法で解決したい際に用います。 定義されているメソッドは1つだけです:

package javax.xml.transform;

public interface URIResolver{
    Source resolve(String href, String base)throws TransformerException;
}

resolve() メソッドの引数に関して、「href」は <import>, <include> 要素の @href 属性の値、「base」は読み込み側のスタイルシートのベース URI です*1

さて、今の場合に必要な実装を具体的に見ていきましょう。 Java コードは以下の通り:

package org.sample;

import java.io.InputStream;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class SystemResourceURIResolver implements URIResolver{
    
    private final String basedir;
    
    public SystemResourceURIResolver(String basedir){
        this.basedir = basedir;
    }

    public Source resolve(String href, String base)throws TransformerException {
        InputStream is = ClassLoader.getSystemResourceAsStream(this.basedir+href);
        return new StreamSource(is);
    }
}

システムリソースの基点となるフォルダへのパス(ここで扱うサンプルでは "org/sample/resources/xsl/")を private フィールドとして保持するようにして、resolve() メソッドでは、そのフォルダと引数の「href」からシステムリソースへのパスを特定します。 リソースを読む際にはやはり ClassLoader.getSystemResourceAsStream() メソッドを用います。

Main.java


変換の手順は以下のようになります:

  1. TransformerFactory オブジェクトを生成する
  2. URIResolver オブジェクトを生成し、TransformerFactory にセットする
  3. Transformer オブジェクトを生成する
  4. 変換を実行

URIResolver オブジェクトを生成して TransformerFactory オブジェクトにセットする*2ステップが追加されています。 サンプルコードは以下のようになります:

package org.sample;

import java.io.InputStream;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class Main {

    private static final String BASEDIR = "org/sample/resources/xsl/";

    public static void main(String[] args) throws TransformerException{

        // 1. TransformerFactory オブジェクトを生成する
        TransformerFactory factory = TransformerFactory.newInstance();
        
        // 2. URIResolver オブジェクトを生成し、TransformerFactory にセットする
        factory.setURIResolver(new SystemResourceURIResolver(BASEDIR));
        
        // 3. Transformer オブジェクトを生成する
        InputStream stylesheet = ClassLoader.getSystemResourceAsStream(
                                                    BASEDIR + "stylesheet.xsl");
        Transformer transformer = factory.newTransformer(new StreamSource(stylesheet));
        
        // 4. 変換を実行
        transformer.transform(
                new StreamSource("sample.xml"), 
                new StreamResult(System.out));
    }
}

*1:絶対パスが分からない場合は null が渡されます。

*2:URIResolver オブジェクトは Transformer オブジェクトにセットしても構いません。 TransformerFactory にセットすると、それから生成した Transformer オブジェクトのデフォルト設定としてその URIResolver オブジェクトが用いられます。