テンプレートエンジンPebble(Java) - タグ・フィルター・関数・テスト・演算子編

TwigとかDjangoっぽい構文のJavaのテンプレートエンジンPebbleのドキュメントです。
結構カスタムできるみたいです。
Twig/Django使いの人も参考になる？？？

導入編的なのはこちらに書きました↓
[テンプレートエンジンPebble - ガイド編 - Qiita](https://qiita.com/momosetkn/items/a0c01b29d6695fece453)

この記事は以下ドキュメントを意訳・補足追加して作成しております。
http://www.mitchellbosecke.com/pebble/documentation
記載内容間違ってても責任取りませんが、教えていただけるとありがたいです。

#タグ

##autoescape

autoescapeタグを使うと一時的に有効／無効にできます。
あと、テンプレートの一部のエスケープを変更できます。

```django
{{ danger }} {# デフォルト（HTML）のエスケープでエスケープします #}
{% autoescape false %}
	{{ danger }} {# エスケープされません #}
{% endautoescape %}
```
```django
{{ danger }} {# htmlのエスケープでエスケープします #}
{% autoescape "js" %}
	{{ danger }} {# jsのエスケープでエスケープします #}
{% endautoescape %}
```


詳しくはガイド編の[escaping](https://qiita.com/momosetkn/items/a0c01b29d6695fece453#%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97)のところに書いてあります

##block

blockタグは2つの関数を実行します。
親となるテンプレートに使用した場合、指定したセクションが、子となるテンプレートでOverrideできます。
子テンプレートで使用した場合、親で宣言したコンテンツをOverrideします。
テンプレートの継承を実装する方法は[extends](#extends)タグを参照してください。

blockタグの中のコンテンツは子テンプレートでOverrideしない場合に使用されます。
プレースホルダとして空のブロックを定義して、プレースホルダーとして、子テンプレートへ提供する使い方が有用です。

`block`と記述した後に、ブロック名を記述します。この名前で子テンプレートで使えます。
endblockタグの直後のブロック名は省けますが、可読性向上のため、ブロック名を書いてもよいです。
補足：{% endblock %}と書いてもよい

例です。

```django
{% block header %}
	<h1> Introduction </h1>
{% endblock header %}
```
子テンプレートはblockの外側はコンテンツは記述できません。
子テンプレートは親テンプレートのblockタグをOverrideすることしかできません。
補足：オブジェクト指向言語の継承と一緒ですね。

##cache

cacheはテンプレートの一部をキャッシュします。
cacheの名前は式かStringのリテラル値のみ使用できます。
キャッシュ名かロケールをキャッシュのキーとして使用できます。
補足：Pebbleのキャッシュにはソースを見るとテンプレートキャッシュとタグキャッシュがあるようです。
デフォルトだとどちらも200件までキャッシュするようです。

```django
{% cache 'menu' %}
    {% for item in items %}
        {{ item.text }}
        ....
    {% endfor %}
{% endcache %}
```

PebbleEngine.Builderを使うことで、cacheの実装・上書きできます。

```java
 return new PebbleEngine.Builder()
                .loader(this.templateLoader())
                .tagCache(CacheBuilder.newBuilder().maximumSize(200).build())
                .build();
```

##extends

extendsタグを使って親テンプレートを指定します。
子テンプレートは最初にこのタグを使用しないといけません。
それと親テンプレートは1つしか指定できません。

例を見てもらったほうが早いでしょう。

```html+django:親テンプレート
<html>
	<head>
		<title>{% block title %} {% endblock %}</title>
	</head>
	<body>
		<div id="content">
			{% block content %}
				Default content goes here.
			{% endblock %}
		</div>

		<div id="footer">
			{% block footer %}
				Default footer content
			{% endblock %}
		</div>
	</body>
</html>
```

baseを継承する例です。

```html+django:子テンプレート
{% extends "base" %}

{% block title %} Home {% endblock %}

{% block content %}
	Home page content.
{% endblock %}
```

最終的にはこのように'Home'と出力されます。

```html
<html>
	<head>
		<title> Home </title>
	</head>
	<body>
		<div id="content">
			Home page content will override the default content.
		</div>

		<div id="footer">
			Default footer content
		</div>
	</body>
</html>
```

要するに、親テンプレートはblockを定義し、子テンプレートはblockをOverrideする。
子テンプレートがOverrideしなかったら、親テンプレートのコンテンツがそのまま使われる。
継承チェーン（継承の継承…と連続する形）には制限なく継承ができます。
子テンプレートに子テンプレートを持たせられます。
このことから、階層を作って継承先テンプレートで最小限の記述へ抑えるというポテンシャルがあります。
補足：Java等のオブジェクト指向言語と一緒だが、継承すると親と子の依存度が強くなるため、
ある程度自由を持たせたい場合はmacroタグ等を使ったほうがいいですね。
「継承よりコンポジション」みたいな？

###動的継承

extendsタグは式を許容しているので、実行時に継承元を判定してうまいこと動作します。

```django
{% extends ajax ? 'ajax' : 'base' %}
```

##filter

filterタグはテンプレートにフィルター機能を付与します。

```django
{% filter upper %}
	hello
{% endfilter %}}

{# output: 'HELLO' #}
```

複数のフィルターも繋げて書けます。

```django
{% filter upper | escape %}
	hello<br>
{% endfilter %}}

{# output: 'HELLO&lt;br&gt;' #}
```

##flush

flushタグはWriterをflushします。
補足：用途不明…スミマセン。

```django
{{ headerText }}
{% flush %}
{{ content }}
```

##for

forタグは配列か`java.lang.Iterable`を実装したリスト、Mapオブジェクトに使用できます。

```django
{% for user in users %}
	{{ user.name }} lives in {{ user.city }}.
{% endfor %}
```

ループの内部では特別な変数が使えます。

* `loop.index` - ゼロ始まりのループカウンタ
* `loop.length` - ループに使うオブジェクトのサイズ
補足：公式ドキュメントには記載がないですが、ソースコードを見る限り以下も使えるようです。

* `loop.first` - 最初の要素
* `loop.last` - 最後の要素
* `loop.revindex` - 今の要素を最後の要素から数えてのインデックス（ゼロ始まり）

```django
{% for user in users %}
	{{ loop.index }} - {{ user.id }}
{% endfor %}
```
また、forタグはiterableオブジェクトが空かどうかをチェックできる便利なタグも用意しています。

```django
{% for user in users %}
	{{ loop.index }} - {{ user.id }}
{% else %}
	There are no users to display.
{% endfor %}
```
mapオブジェクトは以下のようにしてループさせることができます。

```django
{% for entry in map %}
    {{ entry.key }} - {{ entry.value }}
{% endfor %}
```

##if

ifタグは式の結果に応じて指定したコンテンツの内容を出力できます。

```django
{% if users is empty %}
	There are no users.
{% elseif users.length == 1 %}
	There is only one user.
{% else %}
	There are many users.
{% endif %}
```

ifタグにはよく[is](#is)演算子が用いられます。

##import

`import`タグは他テンプレートで定義された[マクロ](https://qiita.com/momosetkn/items/a0c01b29d6695fece453#%E3%83%9E%E3%82%AF%E3%83%AD)を使用できます。


`form_util`というテンプレートのなかにある`input`というマクロがあると仮定します。
そうすると、以下のようにimportできます。

```django
{% import "form_util" %}

{{ input("text", "name", "Mitchell") }}
```
###動的インポート

実行時にインポートする式を決定します。

```django
{% import modern ? 'ajax_form_util' : 'simple_form_util' %}

{{ input("text", "name", "Mitchell") }}
```

##include

includeタグは他のテンプレートから挿入します。
挿入されたテンプレートは現在のテンプレートと同じ変数にアクセスできます。

```django
Top Content
{% include "advertisement" %}
Bottom Content
{% include "footer" %}
```

挿入されたテンプレートに付加的な変数をcontextへ追加できます。
`with`キーワードの後に連想配列を記述します。
挿入されたテンプレートは現在のテンプレートと同じ変数、１つの`With`キーワードの後ろの連想配列
にアクセスできます。

```django
{% include "advertisement" with {"foo":"bar"} %}
```

###動的include

実行時にincludeする式を決定します。

```django
{% include admin ? 'adminFooter' : 'defaultFooter' %}
```

##macro

`macro`タグは再利用可能なコンテンツの部品を作れます。
マクロは複数回呼び出すこともできますし、
`import`タグを使用しての他テンプレートから呼び出すこともできます。


マクロはどこに定義しても問題ありません。
マクロを呼び出す前や後でもどちらでもマクロ定義ができます。

```django
{% macro input(type="text", name, value) %}
	<input type="{{ type }}" name="{{ name }}" value="{{ value }}" />
{% endmacro %}
```
マクロは引数を省略できます。

```django
{{ input(name="country") }}
{# will output: <input type="text" name="country" value="" /> #}
```

他テンプレートからマクロを使いたい場合は、`import`タグを最初に記述してください。

```django
{% import "form_util" %}
{{ input("text", "country", "Canada") }}
```
マクロは残りのテンプレートがアクセス可能な変数へはアクセスできません。
マクロはマクロ内の作業用変数、仮引数にしかアクセスできません。
//TODO ほんやくをもっとましにすること。残りのテンプレートとは

##parallel
このタグは新しいスレッドでレンダリングします。
PebbleEngineに設定したExecutorServiceで実行します。

```django
{{ upperContent }}

{% parallel %}
    {{ calculation.slowCalculation }}
{% endparallel %}

{{ lowerContent }}
```

##set
現在のcontextに変数をセットできます。変数がすでに存在するかどうかは関係なく使えます。

```django
{% set header = "Test Page" %}

{{ header }}
```

##verbatim
verbatimタグを使うとPebbleの構文解析をせず、そのまま出力されます。

```django
{% verbatim %}
	{% for user in users %}
		{{ user.name }}
	{% endfor %}}
{% endverbatim %}
```

#フィルター
##abbreviate
abbreviateフィルターは省略記号(...)を使って省略します。
1つの引数をとります。省略記号を含む指定された桁数に収めて出力します。

```django
{{ "this is a long sentence." | abbreviate(7) }}
```
この例は`this...`と出力されます。

* 引数は`length`です

##abs
absフィルターは絶対値を出力します。

```django
{{ -7 | abs }}

{# output: 7 #}
```

##capitalize
capitalizeフィルターは最初の文字を大文字にします。

```django
{{ "article title" | capitalize }}
```
この例では`Article title`と出力します。

// TODO titleも参照ください

##date
dateフィルターは`java.util.Date`オブジェクトのフォーマットに使われます。
このフィルターは`java.text.SimpleDateFormat`のインスタンスを生成し、
それでフィルターへ渡された`Date`オブジェクトをフォーマットします。

```django
{{ user.birthday | date("yyyy-MM-dd") }}
```

もう１つのこのフィルターの使い道ですが、２つの引数を使います。
１つ目の引数には出力される際の出力フォーマットです、
２つ目の引数には入力の文字列を`java.util.Date`へ変換する際のフォーマットです。

```django
{{ "July 24, 2001" | date("yyyy-MM-dd", existingFormat="MMMM dd, yyyy") }}
```
この例では`2001-07-24`と出力されます。

###引数
* format
* existingFormat
* 

##default
defaultフィルターはフィルターされる値が空のときに、デフォルト値をレンダリングします。
空と判定される値はnull、空文字、空コレクション、空マップです。

```django
{{ user.phoneNumber | default("No phone number") }}
```
この例では、foo,bar,bazのどれかが空の場合でも、defaultフィルターとして完璧に動作します。

```django
{{ foo.bar.baz | default("No baz") }}
```

通常、`strictVariables`がtrueのときに`AttributeNotFoundException`例外がスローされますが、
このフィルターを使うとその例外を抑制します。


###引数
* default

##escape
escapeフィルターはXSS脆弱性を避けるために、特殊文字を安全な文字へ変換します。
このフィルターは一般的にはautoescapingをOFFにした場合に使います。

```django
{{ "<div>" | escape }}
{# output: &lt;div&gt; #}
```

エスケーピングガイドも参照ください
//TODO

###引数
* strategy

##first
`first`フィルターはコレクションの先頭の文字列、または文字列の先頭をレンダリングします。

```django
{{ users | first }}
{# will output the first item in the collection named 'user' #}

{{ 'Mitch' | first }}
{# will output 'M' #}
```

##join
`join`フィルターはコレクションの要素を連結します。
任意で引数に要素と要素の間の文字列を指定できます。

```django
{#
    List<String> names = new ArrayList<>();
    names.add("Alex");
    names.add("Joe");
    names.add("Bob");
#}
{{ names | join(',') }}
{# will output: Alex,Joe,Bob #}
```

###引数
* separator

##last
`lsft`フィルターはコレクションの最後の文字列、または文字列の最後をレンダリングします。

```django
{{ users | last }}
{# will output the last item in the collection named 'user' #}

{{ 'Mitch' | last }}
{# will output 'h' #}
```

##length
`length`フィルターはコレクション、マップまたは文字列の長さを返します。

```django
{% if users|length > 10 %}
    ...
{% endif %}
```

##lower
`lower`フィルターは小文字へ変換します。

```django
{{ "THIS IS A LOUD SENTENCE" | lower }}
```
上記の例ではこのように出力されます。
`this is a loud sentence`

##numberformat
`numberformat`フィルターは数字のフォーマットに使います。
`java.text.DecimalFormat`を使用してフォーマットしています。

```django
{{ 3.141592653 | numberformat("#.##") }}
```
上記の例ではこのように出力されます。
`3.14`
###引数
* format

##raw
`raw`フィルターはオートエスケープによるエスケープを抑制します。
`raw`フィルターは各演算子の中で必ず最後に実行されるようにしてください。
そうしないと、オートエスケープが作動し安全でないとみなしてエスケープしてしまいます。

```django
{% set danger = "<div>" %}
{{ danger | upper | raw }}
{# ouptut: <DIV> #}
```
`raw`フィルターを最後に使わなかった場合の例です。

```django
{% set danger = "<div>" %}
{{ danger | raw | upper }}
{# output: &lt;DIV&gt; #}
```
//TODO エスケーピングガイドを参照すると、エスケープの動作が詳しく載っています。

##rsort
`rsort`フィルターは逆順にソートします。`Comparable`が実装されている必要があります。

```django
{% for user in users | sort %}
	{{ user.name }}
{% endfor %}
```

##slice
`slice`フィルターはリスト、配列、文字列の一部を返します。

```django
{{ ['apple', 'peach', 'pear', 'banana'] | slice(1,3) }}
{# results in: [peach, pear] #}


{{ 'Mitchell' | slice(1,3) }}
{# results in: 'it' #}
```

###引数
* `fromIndex`: 0始まりで指定、指定したインデックスは含む
* `toIndex`: 0始まりで指定、指定したインデックスは含まない

##sort
`sort`フィルターはソートします。`Comparable`が実装されている必要があります。

```django
{% for user in users | sort %}
	{{ user.name }}
{% endfor %}
```

##title
`title`フィルターは各単語をキャタピライズします。

```django
{{ "article title" | title }}
```
上記の例ではこう出力されます。
`Article Title`
[capitalize](capitalize)も参照ください
//TODO

##trim
`trim`フィルターは前後の空白をトリムするのに使います。

```django
{{ "    This text has too much whitespace.    " | trim }}
```
上記の例ではこう出力されます。
`This text has too much whitespace.`

##upper
`upper`フィルターは大文字へ変換します。

```django
{{ "this is a quiet sentence." | upper }}
```
上記の例ではこのように出力されます。
`THIS IS A QUIET SENTENCE.`

##urlencode
`urlencode`フィルターは`application/x-www-form-urlencoded`フォーマットへUTF-8を使用して変換します。

```django
{{ "The string ü@foo-bar" | urlencode }}
```
上記の例ではこのように出力されます。
`The+string+%C3%BC%40foo-bar`

#関数

##block
`block`関数はコンテンツを複数回出力するのに使われます。
ブロックを宣言する`block`タグと混同しないようにしてください。

この例は"post"ブロックを2回出力する例です。
1回目の出力は宣言によるもので、2回目は`block`関数によるものです。

```django
{% block "post" %} content {% endblock %}

{{ block("post") }}
```
上記の例ではこのように出力されます。

```
content

content
```
パフォーマンスの警告

`block`関数はflushタグが動作してくれません。
`block`タグ内でflushするのは問題なくできますが、
`block`関数内では独自のStringWriterを使っているため、flushはされません。

##i18n

`i18n`関数はロケールを指定した`ResourceBundle`からメッセージを取り出します。
`PebbleTemplate`は常に`PebbleEngine`からデフォルトロケールを割り当てます。
テンプレートを評価する毎に、`evaluate`メソッドの引数でロケールを変えることができます。

```java
void evaluate(Writer writer, Map<String, Object> context, Locale locale);
```

`i18n`関数は`ResourceBundle.getBundle(name, locale).getObject(key)`のラッパーです。
第１引数は、bundleの名前、第２引数はbundleのキーです。

```django
{{ i18n("messages","greeting") }}
```
上記の例では`messages.properties`がクラスパスにあり、且つ、
`greeting`がキーにあるある前提です。
もしテンプレートのロケールが`es_US`の場合は、`message_es_US.properties`が検索されます。

さらに、メッセージに変数が使えます。パラメータリストを関数へ渡してあげると`MessageFormat`を使用して置き換えてくれます。

```django
{# greeting.someone=Hello, {0} #}
{{ i18n("messages","greeting", "Jacob") }}

{# output: Hello, Jacob #}
```

###引数
* bundle
* key
* params

##max
`max`関数は最も大きい引数を返します。

```django
{{ max(user.age, 80) }}
```

##min
`min`関数は最も小さい引数を返します。

```django
{{ min(user.age, 80) }}
```

##parent
parent関数は`block`タグ内で使用できる、親テンプレートのblockの内容を出力できるものです。
テンプレートの内容をoverrideはしません。
Javaでいう`super`のようなものです。
"parent.peb"という以下テンプレートを仮定しましょう。

```django:parent.peb
{% block "content" %}
	parent contents
{% endblock %}
```
また違うテンプレート"child.peb"という
"parent.peb"を継承した以下テンプレートを用意します。

```django
{% extends "parent.peb" %}

{% block "content" %}
	child contents
	{{ parent() }}
{% endblock %}
```

すると以下出力されます

```
child contents
parent contents
```

##range
`range`関数は算術型の数列を生成します
補足：Pythonとかみたいだね！

```django
{% for i in range(0, 3) %}
    {{ i }},
{% endfor %}

{# outputs 0, 1, 2, 3, #}
```
第３引数に、増加分を指定できます。デクリメントもできます。

```django
{% for i in range(0, 6, 2) %}
    {{ i }},
{% endfor %}

{# outputs 0, 2, 4, 6, #}
```

Pebbleのビルドインの`..`という演算子は、range関数と+1インクリメントの省略形として記述できます。

```django
{% for i in 0..3 %}
    {{ i }},
{% endfor %}

{# outputs 0, 1, 2, 3, #}
```

#テスト

##empty
`empty`テストは空チェックします。変数がnull、空文字、コレクションが空、Mapが空であることをテストします。

```django
{% if user.email is empty %}
	...
{% endif %}
```
##even
`even`テストは数字が偶数であることをテストします。

```django
{% if 2 is even %}
	...
{% endif %}
```


##map
`map`テストはオブジェクトがMapであることをテストします。

```django
{% if {"apple":"red", "banana":"yellow"} is map %}
	...
{% endif %}
```

##null
`null`テストは変数がnullであることをテストします。

```django
{% if user.email is null %}
	...
{% endif %}
```

##odd
`odd`テストはテストは数字が奇数であることをテストします。

```django
{% if 3 is odd %}
	...
{% endif %}
```

##iterable
`iterable`テストは`java.lang.Iterable`を実装していることをテストします。

```django
{% if users is iterable %}
	{% for user in users %}
		...
	{% endfor %}
{% endif %}
```

#演算子

##比較演算子

Pebbleは`==`, `!=`, `<`, `>`, `<=`, `>=`を用意しています。
`==`を除く、全ての演算子はJavaと同じです。
`==`演算子は`Java.util.Objects.equals(a, b)`を内部で使用します（nullセーフです）。
`equals`は`==`の別名です。

```django
{% if user.name equals "Mitchell" %}
	...
{% endif %}
```

##contains
`contains`演算子はコレクション、マップ、配列に特定の値が含まれているか、テストできます。

```django
{% if ["apple", "pear", "banana"] contains "apple" %}
	...
{% endif %}
```
マップの場合はキーを指定します。

```django
{% if {"apple":"red", "banana":"yellow"} contains "banana" %}
	...
{% endif %}
```
複数のアイテムも指定できます。

```django
{% if ["apple", "pear", "banana", "peach"] contains ["apple", "peach"] %}
	...
{% endif %}
```


##is
`is`演算子はbooleanを返す変数にテストを適用します。

```django
{% if 2 is even %}
	...
{% endif %}
```
否定したい場合は、`not`演算子を使います。

#論理演算子

`and`演算子、`or`演算子はbooleanの式を結合します。

```django
{% if 2 is even and 3 is odd %}
	...
{% endif %}
```

否定したい場合は、`not`演算子を使います。

```django
{% if 3 is not even %}
	...
{% endif %}
```
カッコをつけると式をグループ化して、必要な優先順位をつけれます。

```django
{% if (3 is not even) and (2 is odd or 3 is even) %}
	...
{% endif %}
```

#算術演算子
通常の算術演算もできます。

```django
{{ 2 + 2 / ( 10 % 3 ) * (8 - 1) }}
```


#その他演算子
`|`演算子でフィルターが使えます。

```django
{{ user.name | capitalize }}
```

三項演算子

```django
{{ foo == null ? bar : baz }}
```