倭マン's BLOG

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

ビルダーの本末転倒な使い方

Groovy のビルダーを使っていてチョット思ったことをメモ。

ビルダーは、XML のようなツリー構造を構築するのに便利なツールです。 例えば以下のような XML 文書は*1

<table customer='c' invoice='i'>
  <join c='invoice_id' i='id' />
  <and>
    <greater i.total='1000' />
    <like c.name='%Bill%' >desription</like>
  </and>
</table>

次のようなビルダーで構築できます:

def builder = new MarkupBuilder()
def result = builder.table(customer:'c', invoice:'i'){
    join(c:'invoice_id', i:'id')
    and{
        greater('i.total':1000)
        like('c.name':'%Bill%', 'description')
    }
}

このくらいのサンプルだとあまり有難味が分かりにくいですが、開始・終了タグでタグ名を書く必要がないとか、Groovy の定数、変数、メソッド呼び出しを使えるなど、ビルダーにはいろいろ便利なことがあります。

で、ここから本末を転倒させて、既存の(例えば独自の)ビルダーがあったときに、上記のような XML 文書を入力としてこのビルダーでツリー構造を構築させる方法を考えてみます。

まぁ、普通に考えて、Closure 使えば実装できそうですけど、拙者がイマイチ Closure に慣れてないせいかチョット実装の方向性がよく分からないので、とりあえず力業の実装を。 実装の流れとしては、ビルダーで構築するコードを StringBuilder で頑張って作って、それを GroovyShell で評価してやろう!って感じです。

def xml2builder = new XmlToBuilder(builder:new MarkupBuilder())
def result = xml2builder.build(xml)

class XmlToBuilder{

    def builder

    def build(String xmlText){
        String expr = buildExpression(xmlText)
        def binding = new Binding(builder:this.builder)
        def shell = new GroovyShell(binding)
        return shell.evaluate(expr)
    }

    def buildExpression(String xml){
        def node = new XmlParser().parseText(xml)
        def expr = new StringBuilder('builder.')
        buildNode(node, expr)
    }

    def buildNode(node, expr){
        expr << node.name()

        expr << '('
        node.attributes().each{ name, value ->
            expr << "'$name':'$value',"
        }

        if(node.text()){
            expr << "'${node.text()}');"

        }else{
            expr << '){'
            node.children().each{ buildNode(it, expr) }
            expr << '};'
        }
    }
}

GroovyShell で評価するコードはこんな感じに出来上がります:

builder.table('customer':'c','invoice':'i',){join('c':'invoice_id','i':'id',){};and(){greater('i.total':'1000',){};like('c.name':'%Bill%','desription');};};

ちょっとコンマ (,) とか セミコロン (;) が多いですが、まぁ読むのは GroovyShell さんなので問題なし。

これでとりあえず動きますが、ちょっと力ずく感が否めないので、Closure 使った実装を引き続き模索(予定)。

Groovyイン・アクション

Groovyイン・アクション

*1:このサンプルコードは『[asin:4839927278:title]』から拝借。 かつチョットだけ変更してます。