CLOVER🍀

That was when it all began.

Handlebars.scalaを使ってみる

Scalaでテンプレートエンジンを使うなら、何がいいんだろうと思う今日この頃です。Javaでテンプレートエンジンを使う場合もそうですが!

で、今回は最近JAX-RSと組み合わせて遊んでいたHandlebars.scalaをちょっとマジメに見てみることにしました。

Handlebars.scala
https://github.com/mwunsch/handlebars.scala

http://tech.gilt.com/post/59708050425/handlebars-scala-a-handlebars-for-scala

Scalaのパーサー・コンビネーターを使って実装している、HandlebarsのScala実装です。規模も小さめなので、コード自体には目を通しやすいかと思います。

あ、Handlebars自体は、当方ド素人でございます。

Mustacheは以前試してみていたので、そのあたりの知識は少々役に立ちましたね。

テンプレートエンジンmustacheを使ってみる
http://d.hatena.ne.jp/Kazuhira/20121209/1355042467

Handlebars.scala本家のドキュメントはかなり薄いので、こちらのサイトも見ながら試してみました。

Handlebars
http://handlebarsjs.com/

Basic Usage
http://handlebarsjs.com/expressions.html

Basic Blocks
http://handlebarsjs.com/block_helpers.html

Handlebars.scalaの未サポートの機能について
NOTSUPPORTED
https://github.com/mwunsch/handlebars.scala/blob/master/NOTSUPPORTED.md

では、いってみましょう。

準備

sbt上の依存関係は、こちらを足しておけばOKです。

libraryDependencies += "com.gilt" %% "handlebars-scala" % "2.0.1"

続いて、使い方は順を追ってコードで見ていきます。

テンプレートにバインドさせる型のサンプルとして、このようなものも用意しておきました。

case class Person(name: String, age: Int)

case class Family(people: List[Person])

テンプレートを使う

Handlebars.scalaは、StringとFileをテンプレートとして使う実装を提供しています。

Stringをテンプレートとして使う

まずは、Stringをテンプレートをして扱う例。

    val template = """|Person
                      | name = {{name}}
                      | age = {{age}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Person("磯野カツオ", 11))

    println(result)

{{ }}で囲って参照するのが特徴ですね。

結果。

Person
 name = 磯野カツオ
 age = 11
Fileをテンプレートとして使う

続いて、Fileをテンプレートとして使います。

こちらは、テンプレートをファイルとして用意。
template/person.handlebars

Person
 name = {{name}}
 age = {{age}}

使用コード。

    val templateFile = new File("template/person.handlebars")

    val handlebars = Handlebars(templateFile)
    val result = handlebars(Person("磯野カツオ", 11))

    println(result)

結果。

Person
 name = 磯野カツオ
 age = 11

ファイルのエンコーディングは?という疑問がありますが、これはデフォルトエンコーディングになりますね…。

参考)
https://github.com/mwunsch/handlebars.scala/blob/2.0.0/src/main/scala/com/gilt/handlebars/scala/parser/ProgramHelper.scala#L24

エスケープについて

変数を出力すると、デフォルトでエスケープがかかります。

    val template = "エスケープされる? => {{.}} {{this}}"

    val handlebars = Handlebars(template)
    val result = handlebars("<p>タグ入り</p>")

    println(result)

現在の値を参照するには、{{.}}か{{this}}のようです。

結果。

エスケープされる? => &lt;p&gt;タグ入り&lt;/p&gt; &lt;p&gt;タグ入り&lt;/p&gt;

解除するには、{{{ }}}か{{& }}で。

    val template = """|エスケープされる? => {{{.}}} {{{this}}}
                      |エスケープされる? => {{&.}} {{&this}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars("<p>タグ入り</p>")

    println(result)

Mapをバインドしてみる

Mapをテンプレートにバインドしても、ふつうに参照できます。

    val template = """|Person
                      | name = {{name}}
                      | age = {{age}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("name" -> "磯野カツオ", "age" -> 11))

    println(result)

結果。

Person
 name = 磯野カツオ
 age = 11

このあたりは、最初にPersonというクラスをバインドしましたが、大差ないですね。

繰り返し

{{# }}を使うと、繰り返しが表現できます。

    val template = """|People
                      |{{#people}}
                      |  Person
                      |    name = {{name}}
                      |    age = {{age}}
                      |{{/people}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Family(List(Person("フグ田サザエ", 24),
                                        Person("磯野カツオ", 11),
                                        Person("磯野ワカメ", 9))))

    println(result)

結果。

People

  Person
    name = フグ田サザエ
    age = 24

  Person
    name = 磯野カツオ
    age = 11

  Person
    name = 磯野ワカメ
    age = 9

空のコレクションを渡すと

    val template = """|People
                      |{{#people}}
                      |  Person
                      |    name = {{name}}
                      |    age = {{age}}
                      |{{/people}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Nil)

    println(result)

何も出力されません。

People

分岐

{{# }}にtrue/falseを返すものを使用すると、if文的な感じで使うことができます。

    val template = """|{{#trueVal}}
                      | こっちは表示される
                      |{{/trueVal}}
                      |{{#falseVal}}
                      | こっちは表示されない
                      |{{/falseVal}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("trueVal" -> true, "falseVal" -> false))

    println(result)

結果。

 こっちは表示される

else

{{# }}では、バインドした値がfalseだったり空のコレクションだった場合に、elseを使うと別の定義を評価させることができます。

空のコレクションをバインド。

    val template = """|People
                      |{{#people}}
                      |  Person
                      |    name = {{name}}
                      |    age = {{age}}
                      |{{/people}}
                      |{{^people}}
                      | 0件でしたよ
                      |{{/people}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Nil)

    println(result)

結果。

People


 0件でしたよ

falseな値をバインド。

    val template = """|{{#falseVal}}
                      | こっちは表示されない
                      |{{/falseVal}}
                      |{{^falseVal}}
                      | こっちは表示される
                      |{{/falseVal}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("falseVal" -> false))

    println(result)

結果。


 こっちは表示される

コメント

コメントを書く場合は、{{! }}か{{!-- --}}で表現します。

    val template = """|コメント {{!-- これは表示されない --}}
                      |コメント {{! これも表示されない }}
                      |{{name}}""".stripMargin

    // ただし、{{!-- {{name}} --}}みたいな入れ子の表記は不可

    val handlebars = Handlebars(template)
    val result = handlebars(Person("磯野カツオ", 11))

    println(result)

結果。

コメント 
コメント 
磯野カツオ

上位のスコープを参照する

{{# }}で囲うと、スコープが1段下がるのですが、「..」を使うことで上位のスコープを参照することができます。

    val template = """|タイトル:{{title}}
                      |{{#family}}
                      |{{#people}}
                      |  Person
                      |    title = {{../../title}}
                      |    name = {{name}}
                      |    age = {{age}}
                      |{{/people}}
                      |{{/family}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("title" -> "サザエさん",
                                "family" -> Family(List(Person("フグ田サザエ", 24),
                                                        Person("磯野カツオ", 11),
                                                        Person("磯野ワカメ", 9)))))

    println(result)

上記のようなテンプレートで、{{people}}の繰り返し中に{{title}}を参照するためには、{{../../title}}と表記します。

結果。

タイトル:サザエさん


  Person
    title = サザエさん
    name = フグ田サザエ
    age = 24

  Person
    title = サザエさん
    name = 磯野カツオ
    age = 11

  Person
    title = サザエさん
    name = 磯野ワカメ
    age = 9

テンプレート中で、サブテンプレートを読み込む

{{> }}を使います。

Stringテンプレートで使う

こんな感じになります。

    val template1 = """|タイトル:{{title}}
                       |{{#family}}
                       |{{> person_each}}
                       |{{/family}}""".stripMargin

    val template2 = """|{{#people}}
                       |  Person
                       |    name = {{name}}
                       |    age = {{age}}
                       |{{/people}}""".stripMargin

    val handlebars = Handlebars(template1)
    val result = handlebars(Map("title" -> "サザエさん",
                                "family" -> Family(List(Person("フグ田サザエ", 24),
                                                        Person("磯野カツオ", 11),
                                                        Person("磯野ワカメ", 9)))),
                            partials = Map("person_each" -> Handlebars(template2)))

    println(result)

template1から、template2を参照する形になっています。

partialsに、追加のテンプレートを設定するところがポイントです。

結果。

タイトル:サザエさん


  Person
    name = フグ田サザエ
    age = 24

  Person
    name = 磯野カツオ
    age = 11

  Person
    name = 磯野ワカメ
    age = 9

Fileテンプレートで使う

本体のテンプレートを用意。
template/people.handlebars

タイトル:{{title}}
{{#family}}
{{> person_each}}
{{/family}}

拡張子は「.handlebars」が勝手に付けられます。

サブテンプレートを用意。
template/person_each.handlebars

{{#people}}
  Person
    name = {{name}}
    age = {{age}}
{{/people}}

結果。

タイトル:サザエさん


  Person
    name = フグ田サザエ
    age = 24

  Person
    name = 磯野カツオ
    age = 11

  Person
    name = 磯野ワカメ
    age = 9

ちなみに、サブテンプレートを読み込む場合も、やっぱりデフォルトエンコーディングとなります。
https://github.com/mwunsch/handlebars.scala/blob/2.0.0/src/main/scala/com/gilt/handlebars/scala/partial/PartialHelper.scala#L51

せめてUTF-8固定とかにしてくれれば…このあたりが気になる場合や、Stringで頑張るか自分で拡張するんでしょうかねぇ…。

下位のパス(?)を参照する

バインドした変数のメンバなどにアクセスする場合は、「.」か「/」を使用します。

「.」を使った場合。

    val template = """|Person
                      | name = {{person.name}}
                      | age = {{person.age}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("person" -> Person("磯野カツオ", 11)))

    println(result)

結果。

Person
 name = 磯野カツオ
 age = 11

「/」を使った場合。

    val template = """|Person
                      | name = {{person/name}}
                      | age = {{person/age}}""".stripMargin

    val handlebars = Handlebars(template)
    val result = handlebars(Map("person" -> Person("磯野カツオ", 11)))

    println(result)

結果。

Person
 name = 磯野カツオ
 age = 11

Helper

別エントリを起こしました。

Handlebars.scalaのHelperを使ってみる
http://d.hatena.ne.jp/Kazuhira/20140920/1411202608

とりあえず、基本的なところはわかった感じ?