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
エスケープについて
変数を出力すると、デフォルトでエスケープがかかります。
val template = "エスケープされる? => {{.}} {{this}}" val handlebars = Handlebars(template) val result = handlebars("<p>タグ入り</p>") println(result)
現在の値を参照するには、{{.}}か{{this}}のようです。
結果。
エスケープされる? => <p>タグ入り</p> <p>タグ入り</p>
解除するには、{{{ }}}か{{& }}で。
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
とりあえず、基本的なところはわかった感じ?