XMLを生成するKotlin DSLを作ってみる～拡張関数版のinvoke operatorを活用する～

まずはこのコードをみてください。これはKotlinとして正しい文法になっています。

```kotlin
xml {
    "resources" {
        "color"("name" to "primary") { "#202020" }
        "color"("name" to "primaryDark") { "#000000" }
        "color"("name" to "accent") { "#2196F3" }
    }
}.toString().let { println(it) }
```

これを実行すると以下の出力が得られます。

```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<resources>
    <color name="primary">#202020</color>
    <color name="primaryDark">#000000</color>
    <color name="accent">#2196F3</color>
</resources>
```

ここでは、このようなDSLの作り方を解説します。

# 拡張関数版のinvoke operator

Kotlinにはinvokeというoperatorがありますね。

以下のようにinvoke operatorを定義します

```kotlin
class F {
    operator fun invoke() {
       ...
    }
}
```

使うときは以下のように、クラスインスタンスをメソッド名と見なしたような呼び出しが可能になります。

```kotlin
val f = F()
f() // <- これでinvoke()を実行できる
f.invoke() // <- こうも書ける
```

最近、拡張関数版のinvoke operatorというものを知りました。

例えば、Stringをレシーバーとするinvokeという拡張関数をつくってoperatorを指定します。

```kotlin
class F {
    operator fun String.invoke() {
       ...
    }
}
```

すると以下のように、まるで任意Stringがメソッドになったような記述で呼び出しが可能になります。

```kotlin
val f = F()
with(f) {
    "hogehoge"() // <- これでString.invoke()が実行可能
}
```

そう書けるからっていってもどこで使うんだよ、というのはごもっともですが、DSLを作る場合は便利そうですね。

# XMLを生成するKotlin DSL

XMLを作るDSLを考えるに当たって、XMLはタグ名や属性名などは任意の文字列が使われうるので、タグ名のメソッドを使って～という方法は使えませんが、拡張関数版のinvoke operatorを使えばうまく作れそうですね。

ということで、かなりやっつけで、用途も限定される小さいなものですが、以下のようなクラス、メソッドを用意してみます。

```kotlin
fun xml(block: NODE.() -> Unit): NODE = NODE(newDocument()).also { it.block() }

private fun newDocument(): Document =
    DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()

class NODE(
    private val node: Node
) {
    private val document: Document = if (node is Document) node else node.ownerDocument

    operator fun String.invoke(vararg attr: Pair<String, String>, block: NODE.() -> Any = { Unit }) {
        val child = document.createElement(this).also { node.appendChild(it) }
        attr.forEach { child.setAttribute(it.first, it.second) }
        NODE(child).block().let { if (it is String) child.textContent = it }
    }

    override fun toString(): String {
        val sw = StringWriter()
        TransformerFactory.newInstance().newTransformer().also {
            it.setOutputProperty(OutputKeys.INDENT, "yes")
            it.transform(DOMSource(document), StreamResult(sw))
        }
        return sw.toString()
    }
}
```

`toString()`は表示用です。これを除くと本当に小さなクラスとメソッドです。

これだけで簡単なDSLとして機能するようになります。
これを使ってXMLを作ってみましょう。

冒頭の例の再掲です。簡単に作っただけにKotlinっぽさが残っていますが（消す必要も無いとは思いますが）、XMLの最小限の要素だけで生成コードが作れてます。

```kotlin
xml {
    "resources" {
        "color"("name" to "primary") { "#202020" }
        "color"("name" to "primaryDark") { "#000000" }
        "color"("name" to "accent") { "#2196F3" }
    }
}.toString().let { println(it) }
```

これを実行した結果が以下です。

```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<resources>
    <color name="primary">#202020</color>
    <color name="primaryDark">#000000</color>
    <color name="accent">#2196F3</color>
</resources>
```

Kotlinなので、制御文を含めることも当然できます。

```kotlin
xml {
    "resources" {
        repeat(0x10) {
            val v = "%02X".format(it * 0x11)
            "color"("name" to "gray_$v") { "#$v$v$v" }
        }
    }
}.toString().let { println(it) }
```

出力結果

```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<resources>
    <color name="gray_00">#000000</color>
    <color name="gray_11">#111111</color>
    <color name="gray_22">#222222</color>
    <color name="gray_33">#333333</color>
    <color name="gray_44">#444444</color>
    <color name="gray_55">#555555</color>
    <color name="gray_66">#666666</color>
    <color name="gray_77">#777777</color>
    <color name="gray_88">#888888</color>
    <color name="gray_99">#999999</color>
    <color name="gray_AA">#AAAAAA</color>
    <color name="gray_BB">#BBBBBB</color>
    <color name="gray_CC">#CCCCCC</color>
    <color name="gray_DD">#DDDDDD</color>
    <color name="gray_EE">#EEEEEE</color>
    <color name="gray_FF">#FFFFFF</color>
</resources>
```

ちゃんと使えるようにするにはもう少し工夫が必要そうですが、簡単にDSLが作れて楽しいですね。
Kotlinはいいぞ！