antでループを実現する

XSLTではなくて本当はantの話なのですが、ついでに書きます.

与えられているミッションはプログラムのバージョン(3バージョン)×XSLTプロセッサ(約3種類)×50言語のテストデータというものです.これをテストしなければなりません.バージョン、XSLTプロセッサと1言語というビルドファイルは作ったのですが、これをバッチファイルで個々に起動させるというのはあまりに芸がありません.一発のantのビルドで少なくともプログラムのバージョン、XSLTプロセッサは固定でも50言語は繰り返しで処理してしまいたいというのが人情です.

でも、いくらantのUsers Manualを見ていても妙案は浮かびません.それもそのはず、繰り返し処理は必ずある変数(プロパティ)を順に変えながらそれをパラメータとして目的の処理を呼び出すという機構がどうしても必要だからです.antではここで変数はプロパティでしょう.しかしantのプロパティはimmutable(イミュータブル:不変⇒書き換え禁止)なのです.最初に設定した値が有効になります.例えばいくらビルドファイルの中で

    <property name="xml.lang" value="en"/>

としていても、antのコマンドラインで-Dxml.lang="ja-JP"とすればそちらが優先されます.つまり、プロパティの値を順に変えてという事自体が(純正のantでは)不可能なのでしょう.

しかし、繰り返しを一発のビルドでやってしまいたいというのは誰でも考えることです.Stack OverflowなんかのQ&Aを参考にして、antで<for>が書けるライブラリを見つけました.

  Ant-Contrib Tasks

ここでant-contrib-1.0b3.jarをant/libフォルダにコピーすればあっけなく使えるようになってしまいます.例えば次のような具合です.

    <!-- テストデータ作成用データファイルdita/testdata/sample_gen.xmlより
         test/@xml:langを抽出し、","区切りでプロパティlang.propsにセットする.
      -->
    <target name="iterate.lang" depends="build.saxon91.classpath">
        <property name="temp.dir" location="${basedir}${file.separator}temp"/>
        <mkdir dir="${temp.dir}"/>
        <local name="input.file.path"/>
        <property name="input.file.path" location="${basedir}${file.separator}..${file.separator}data${file.separator}testdata${file.separator}sample_gen.xml"/>
        <property name="output.property.path" location="${temp.dir}${file.separator}target_lang.xml"/>
        <local name="stylesheet.path"/>
        <property name="stylesheet.path" location="${basedir}${file.separator}stylesheet${file.separator}common${file.separator}make_lang_property.xsl"/>
        <java classname="net.sf.saxon.Transform" classpathref="saxon91.class.path"
            failonerror="true">
            <arg line='-o:"${output.property.path}"'/>
            <arg line='-s:"${input.file.path}"'/>
            <arg line='-xsl:"${stylesheet.path}"'/>
        </java>
        <xmlproperty file="${output.property.path}"/>
        <echo message="$${lang.props}=${lang.props}"/>
    </target>

    <!-- テストデータで定義された言語分のXSL-FO, PDFをビルドする.
     -->
    <target name="build.all.lang" depends="iterate.lang">
        <taskdef resource="net/sf/antcontrib/antlib.xml"/>
        <for list="${lang.props}" delimiter="," param="lang">
            <sequential>
                <local name="xml.lang"/>
                <property name="xml.lang" value="@{lang}"/>
                <echo message="$${xml.lang}=${xml.lang}"/>
                <property name="skip.pdf.generation" value="yes"/>
                <antcall target="build.xml.to.pdf.transform">
                    <param name="prm.xml.lang" value="${xml.lang}"/>
                </antcall>
            </sequential>
        </for>
    </target>

ここでlang.propsプロパティには"iterate.lang"で処理したい言語を","区切りで格納しています.例えば次のような具合です.

<echo message="$${lang.props}=${lang.props}"/>

${lang.props}=ar,bg,ca,cs,da,de,el,en,es,et,fa,fi,fr,he,hi,hr,hu,id,is,it,kk,km,kn,lo,lt,lv,ms,my,nl,no,pl,pt,ro,ru,si,sk,sl,sv,sw,ta,te,th,tl,tr,uk,vi

これをつくるXSLTスタイルシートも簡単なものです.単に言語タグを","で区切って並べるだけです.

[make_lang_property.xsl]
  <xsl:template match="tests">
    <lang.props>
      <xsl:apply-templates select="test"/>
    </lang.props>
  </xsl:template>
  
  <xsl:template match="test">
    <xsl:variable name="xmlLang" as="xs:string" select="string(@xml:lang)"/>
    <xsl:if test="position() gt 1">
      <xsl:text>,</xsl:text>
    </xsl:if>
    <xsl:value-of select="$xmlLang"/>
  </xsl:template>

元のテストデータは

<tests>
  <test xml:lang="ar"/>
  <test xml:lang="bg"/>
  ...
</tests>

という単純なものです.

しかし、こういうantの拡張は批判もあるようですね.Stack Overflowでは"ant-contrib-xx.jarはevilだ!"などという声もありました.しかし私にとっては背に腹は代えられません.この"Ant-Contrib Tasks"のおかげで、検証テストはぐっと簡単になりました.よろしくければ参考にしてください.