MarkupBuilderで要素の作成を別メソッドにする方法がわからない。

GroovyでXMLを出力するにはMarkupBuilderを使えば簡単らしいのでやってみた。

<root>
  <id>1</id>
  <user>
    <name>yamada</name>
    <car>
      <name>car1</name>
    </car>
  </user>
  <company>
    <name>kaisha</name>
    <car>
      <name>car2</name>
    </car>
  </company>
</root>

このXMLを出力したい場合には以下のコードで出力可能。

import groovy.xml.MarkupBuilder
new MarkupBuilder().root(){
  id(1)
  user {
    name("yamada")
    car{
      name("car1")
    }
  }
  company {
    name("kaisha")
    car {
      name("car2")
    }
  }
}

さて、ここからが問題。
car要素が複数の出てるのでをまとめて記述したい場合のスマートな書き方がわからない。

とりあえずまとめるだけならroot内にクロージャーを定義すれば可能。
参考↓
http://groovy.my-notebook.net/435028f2-6101-496a-bc42-2521361faceb.html

import groovy.xml.MarkupBuilder
new MarkupBuilder().root(){
  // car要素を作成
  def c = {n ->
    car{
      name(n)
    }
  }
  
  id(1)
  user {
    name("yamada")
    c("car1")
  }
  company {
    name("kaisha")
    c("car2")
  }
}

少しはマシになったけど、外部に切り出せてるわけではないので微妙。
こんな感じで書きたい。

// carを作成
def c = {n ->
  car{
    name(n)
  }
}

// userを作成
def u = {
  user {
    name("yamada")
    c("car1")
  }
}

// companyを作成
def co = {
  company {
    name("kaisha")
    c("car2")
  }
}

// 出力
builder.root(){  
  id(1)
  u()
  c()
}

MarkupBuilderのクロージャー内で呼ばれる関数が何を返せばいいのかわかればよい?
そもそも、MarkupBuilderが「このような記述でXMLが出力されるのか」を理解する必要があるような気がする。

                      • -

とか思って、

呟いてたらid:nobeansさんが助言くれました。

つまり、MarkupBuilderに渡されたクロージャー内で定義されていないメソッドが呼ばれるとmethodMissingが呼ばれて、
メソッド名として記述されている値が要素として出力されると。

では、この場合はどういう風に処理が走ってる?

new MarkupBuilder().root(){
  def c = {n ->
    car{
      name(n)
    }
  }
  user {
    c("car1")
  }
}

最初のクロージャー内で「c」という変数名で定義されたクロージャーがあるのは特に問題なし。

user {
  c("car1")
}

というのは

user({c("car1")})

という事になって、userメソッドが呼び出される。
しかし、userメソッドは当然定義されていないのでmethodMissing()にuserという文字列と「{c("car1")}」というクロージャーが渡されると。

ならば、MarkupBuilder外にメソッド作成してクロージャー返せばいいのでは?
と、思ったけど出力されない。
が、例外になるわけでもない。

import groovy.xml.MarkupBuilder
def c = {n ->
  return {
    car{
      name(n)
    }
  }
}
new MarkupBuilder().root(){
  user {
    c("car1")
  }
}
/*
<root>
  <user />
</root> 
*/

これは、メソッドからクロージャーが返ってくるがこのクロージャーが実行されていないため。
んじゃ、クロージャー実行すればいいやん。
って事で返ってきたクロージャー実行するとMissingMethodExceptionが発生。

import groovy.xml.MarkupBuilder
def c = {n ->
  return {
    car{
      name(n)
    }
  }
}
new MarkupBuilder().root(){
  user {
    c("car1")()
  }
}
// groovy.lang.MissingMethodException: No signature of method: ConsoleScript11.car() <略>

MarkupBuilderの外だからmethodMissingメソッドが定義されていない。
だからMissingMethodExceptionが発生する。

じゃぁ、methodMissingで行なっている処理を行えばいい。
って思ったんですが、出力してるんですよね。
っとなると、手詰まり??

何か方法ありそうだけど。。。

追記(2012/12/23)

id:kiy0taka さんからコメント頂きました。
外部で定義するクロージャーでdelegateを使用すれば実現できるようです。

import groovy.xml.MarkupBuilder

def xml = new MarkupBuilder()
def c = {n ->
  car{
    name(n)
  }
}
c.delegate = xml

xml.root(){
  id(1)
  user {
    name("yamada")
    c("car1")
  }
  company {
    name("kaisha")
    c("car2")
  }
}

delegateというとクロージャー内でthis的に使うという事しかわからなかったので調べたら、↓という事らしい。

owner : そのクロージャを含むオブジェクト (this またはそのクロージャを囲んでいるクロージャ)
delegate : デフォルトでは owner と同じだが変更可能。例: builder や ExpandoMetaClass

http://groovy.codehaus.org/Japanese+Closures

つまり、外部で定義したクロージャーをMarkupBuilder内のクロージャーとして扱っているという事ですかね。
delegateってこうやって使うのね!