scalateのembeddingでハマった話

scala用のテンプレートエンジン、[Scalate](http://scalate.github.io/scalate/)をplay2から使おうとして小一時間ハマってたので共有します。

Scalateはググっても情報が少なくて寂しいです。みなさんScalaでAPIサーバ書いてるからテンプレートエンジンはあまり使わないのでしょうか。


## ハマり1: 数値に勝手にカンマが挿入される

たとえば、以下のように出力したいときに、

```
<Ad id="9000">
```

次のように3桁毎にカンマが挿入されます

```
<Ad id="9,000">
```

**余計なお世話だよ！！！**

### 原因: java.text.DecimalFormatの仕業

ScalateのRenderContextが数値出力時のフォーマットに抽象クラスのNumberFormatを使うようになっており、実体はサブクラスのDecimalFormatが使われてる(多分。ロケール依存)


```scala:RenderContext.scala
  /////////////////////////////////////////////////////////////////////
  //
  // custom object rendering
  //
  /////////////////////////////////////////////////////////////////////

  private[this] val _numberFormat = new Lazy(NumberFormat.getNumberInstance(locale))
  private[this] val _percentFormat = new Lazy(NumberFormat.getPercentInstance(locale))
  private[this] val _dateFormat = new Lazy(DateFormat.getDateInstance(DateFormat.FULL, locale))
```

以下はjava8のDecimalFormatの[APIリファレンス](https://docs.oracle.com/javase/jp/8/docs/api/java/text/DecimalFormat.html)からの抜粋
>グループ区切り子は一般に1000ごとに区切るために使用しますが、国によっては10000ごとに使用するところもあります。グループ区切りのサイズとは、100,000,000の場合は3、1,0000,0000の場合は4というように、グループ区切り文字間の一定の桁数です。複数のグループ区切り文字を持つパターンを指定すると、最後の区切り文字と末尾の整数との間が、この間隔として使用されます。したがって、"#,##,###,####" == "######,####" == "##,####,####"となります。


setGroupingUsedメソッドにfalseを設定し、グループ化をoffにする必要があるらしい


### 解決方法: NumberFormatを自分で設定する

以下のようにTemplateEngineを継承したカスタムクラスを作り、RenderContextを生成するcreateRenderContextメソッドをオーバーライドし、あとはよしなにって感じです。

``` scala:CustomTemplateEngine.scala
class CustomTemplateEngine extends TemplateEngine {
  
  override protected def createRenderContext(uri: String, out: PrintWriter): RenderContext = {
    val context = new DefaultRenderContext(uri, this, out)
    val df = new DecimalFormat()
    df.setGroupingUsed(false)
    context.numberFormat = df
    context
  }
}
```


## ハマり2: レイアウト機能が効かない

[Layouts](http://scalate.github.io/scalate/documentation/user-guide.html#layouts)のことです。大体のテンプレートエンジンにはあると思うんですが、骨組みとなるテンプレートを作っておいて、別のテンプレートから骨組みテンプレートを呼び出すと、自身のレンダリング結果を埋め込んでもらえる的なやつ。

骨組みテンプレートを指定しているのにも関わらず、骨組みに埋め込まれず、そのままレンダリングされてしまいます。

これはググっても全然でてこなかったので、デバッガでソースを追ったところ、以下のソースにたどり着きました

``` scala:TemplateEngine.scala
  var layoutStrategy: LayoutStrategy = NullLayoutStrategy
```

**Nullだと。。。！！！！**

以下がNullLayoutStrategyの定義

``` scala:LayoutStrategy.scala
/**
 * A <code>LayoutStrategy</code> that renders the given template without
 * using any layout.
 */
object NullLayoutStrategy extends LayoutStrategy {
  def layout(template: Template, context: RenderContext) = template.render(context)
}
```

レイアウト使わずにそのままレンダリングするとかいってますね。フザケンナ


### 解決方法：DefaultLayoutStrategyに差し替える

``` scala:ScalateModule.scala
engine.layoutStrategy = new DefaultLayoutStrategy(engine)
```

layoutStrategy変数はvarで定義されており、あとから差し替えることが可能なので、DefaultLayoutStrategyに変更してしまいます。

どこでやるかはいろいろパターンあると思うんですが、私はPlay2のDIコンテナ(Guice)に登録する用のモジュールの中でやりました。


## 感想

自分のアプリや、フレームワークの中にScalateを組み込むことをEmbeddingというようですが、今回の件がEmbedding用途での注意事項として[ドキュメント](http://scalate.github.io/scalate/documentation/scalate-embedding-guide.html)に特に記載がないのがちょっと厳しいと感じました。(NumberFormatの件はJavaのAPIのデフォルト挙動なので、Scalateに責任は無いけどｗ)

ハマり1はskinny-frameworkの[ソース](https://github.com/skinny-framework/skinny-framework/blob/13c4b2a971d5bccaa3c247565130140dd2a14640/framework/src/main/scala/skinny/controller/feature/ScalateTemplateEngineFeature.scala#L83)を見て気づき、ハマり2はいったんデバッガで見つけたあとに、[play-scalateプラグイン](https://github.com/scalate/play-scalate)の中で同様のことをやってるのに気づいたんですが、これらが無かったら+1時間はハマってたでしょうね。

先達に感謝。

(play-scalateプラグインそのまま使いたかったのですが、プラグイン機能は2.6でオミット。。。。)