Index > Groovy を使った設定ファイルの作成 (スタイル設定用 ミニDSL の自作)
Mon, January 11, 2010

Groovy を使った設定ファイルの作成 (スタイル設定用 ミニDSL の自作)

Groovyのビルダを使うと、見かけ上設定ファイルのような記述ができるのですが、 それがそのまま実はGroovyのコードとして動かせるということができます。 これが便利なのは、単なる設定ファイルのように見えるものの、 実際はGroovyのコードなので、 変数やロジック等をそこに混ぜることができるという点です。

Groovy In Action の Chapter 8 "ビルダーの利用" では、 Lispの野望、実行可能な仕様に近づく というように表現されています。

具体例

現在関わっている XML + FrameMaker での組版プロジェクトでは、 XMLのフォーマットに関して独自のアプリケーションを使っていて、具体的には、 以下のように XML属性を使用して段落書式(スタイル)を設定します。

たとえば、 title要素に対して

でフォーマットしたい場合は、 以下のようにします。(font-size,font-name 属性を設定する)

<title font-size="10.5" font-name="Helvetica">Hello World.</title>

さて問題は、 このスタイルに関する属性情報を上手に管理するには?です。
処理対象となる出版物は ページ数が多いので、 一つ一つ手作業で属性設定をするのはまったく非現実的です。

解決策1 XSLT を使う

普通の解決方法は、XSLTを使います。
→test.xml を test.xsl で変換する。

test.xsl

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

    <xsl:template match="@*|node()" >
        <xsl:copy>
                <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="title" >
        <xsl:copy>
            <xsl:attribute name="font-size">10.5</xsl:attribute>
            <xsl:attribute name="font-name">Helvetica</xsl:attribute>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

test.xml

<doc>
<title>Hello World.</title>
</doc>

変換処理

$ xsltproc test.xsl test.xml

変換結果

<?xml version="1.0"?>
<doc>
<title font-size="10.5" font-name="Helvetica">Hello World.</title>
</doc>

まとめ

XSLTによる方法は標準的ですが、これは冗長すぎです。 title 要素に対して font-size=10.5,font-name=Helvetica を追加したい だけなのに 余計な記述が多すぎます。

XSLTの知識のない人がこの変換設定ファイルをメンテナンスできないのも問題です。

解決策2 GroovyのBuilderを使う

Groovyを使えば、問題領域に特化した情報だけをシンプルに表現することができます。

test.groovy

b = new MyBuilder()
b.mystyle{
    'title'{
        'font-size'(10.5)
        'font-name'('Helvetica')
    }
}

b.output()

この設定ファイル的Groovyコード(test.groovy)を実行すると 以下のように先ほどと同じXSLTスタイルシートを生成します。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:template match="@*|node()" >
        <xsl:copy>
        <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="title" >
        <xsl:copy>
            <xsl:attribute name="font-size">10.5</xsl:attribute>
            <xsl:attribute name="font-name">Helvetica</xsl:attribute>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

MyBuilder.goovy

test.groovy は普通のGroovyコードですが、ほとんど設定ファイルのように見えます。 これから 変換用のXSLTスタイルシートを作り出すには、 BuilderSupport を extends したカスタムビルダ(MyBuilder.groovy)を使います。

class MyBuilder extends BuilderSupport{

    def rootNode

    def createNode(name){
        def currentNode = new javax.swing.tree.DefaultMutableTreeNode(name)
        if( name=='mystyle' )
            this.rootNode = currentNode

        return currentNode
    }
    def createNode(name,value){
        createNode( name + ':' + value )
    }
    def createNode(name, Map attribute){
        //println name + ':' + attribute
    }
    def createNode(name, Map attribute, value){
        //println name + ':' + attribute + ':' + value
    }

    void setParent(parent, child){
        parent.add( child )
    }
    //void nodeCompleted(parent, node){ }

    void output(){

        def br = System.getProperty('line.separator')
        def sb = ''<<''
        assert sb.getClass().name == 'java.lang.StringBuffer'

        sb << tab(0)+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' << br
        sb << tab(1)+ '<xsl:template match="@*|node()" >' << br
        sb << tab(2)+ '<xsl:copy>' << br
        sb << tab(2)+ '<xsl:apply-templates select="@*|node()" />' << br
        sb << tab(2)+ '</xsl:copy>' << br
        sb << tab(1)+ '</xsl:template>' << br


        for( nodeAsXPath in this.rootNode.children() ){

            def xpath = nodeAsXPath.getUserObject()

            sb << tab(1)+ "<xsl:template match=\"$xpath\" >" << br
            sb << tab(2)+ '<xsl:copy>' << br

            for( nodeAsMystyle in nodeAsXPath.children() ){

                def attrname  = nodeAsMystyle.getUserObject().split(':')[0]
                def attrvalue = nodeAsMystyle.getUserObject().split(':')[1]

                sb << tab(3)+ "<xsl:attribute name=\"$attrname\">$attrvalue</xsl:attribute>" << br
            }
            sb << tab(3)+ '<xsl:apply-templates select="@*|node()" />' << br
            sb << tab(2)+ '</xsl:copy>' << br
            sb << tab(1)+ '</xsl:template>' << br

        }
        sb << tab(0)+ '</xsl:stylesheet>' << br

        print sb
    }

    def tab(int indentCount){
        return '    '*indentCount
    }


}

MyBuilder.groovy でやっていることは、 rootNode( javax.swing.tree.DefaultMutableTreeNode ) に Builderの定義を読み込んで、 output() 内でXSLTスタイルシートとして出力しているだけです。

今回はわかりやすさやデバッグのしやすさを考慮して、いったん XSLTスタイルシート に変換しましたが、 XMLSlurperを使うなどして Groovy で直接 XML を変換してしまえば、もっとスマートでしょう。

 Twitter
follow me on Twitter
 Categories