TwigとかDjangoっぽい構文のJavaのテンプレートエンジンPebbleのドキュメントです。
結構カスタムできるみたいです。
Twig/Django使いの人も参考になる???
導入編的なのはこちらに書きました↓
テンプレートエンジンPebble - ガイド編 - Qiita
この記事は以下ドキュメントを意訳・補足追加して作成しております。
http://www.mitchellbosecke.com/pebble/documentation
記載内容間違ってても責任取りませんが、教えていただけるとありがたいです。
#タグ
##autoescape
autoescapeタグを使うと一時的に有効/無効にできます。
あと、テンプレートの一部のエスケープを変更できます。
{{ danger }} {# デフォルト(HTML)のエスケープでエスケープします #}
{% autoescape false %}
{{ danger }} {# エスケープされません #}
{% endautoescape %}
{{ danger }} {# htmlのエスケープでエスケープします #}
{% autoescape "js" %}
{{ danger }} {# jsのエスケープでエスケープします #}
{% endautoescape %}
詳しくはガイド編のescapingのところに書いてあります
##block
blockタグは2つの関数を実行します。
親となるテンプレートに使用した場合、指定したセクションが、子となるテンプレートでOverrideできます。
子テンプレートで使用した場合、親で宣言したコンテンツをOverrideします。
テンプレートの継承を実装する方法はextendsタグを参照してください。
blockタグの中のコンテンツは子テンプレートでOverrideしない場合に使用されます。
プレースホルダとして空のブロックを定義して、プレースホルダーとして、子テンプレートへ提供する使い方が有用です。
block
と記述した後に、ブロック名を記述します。この名前で子テンプレートで使えます。
endblockタグの直後のブロック名は省けますが、可読性向上のため、ブロック名を書いてもよいです。
補足:{% endblock %}と書いてもよい
例です。
{% block header %}
<h1> Introduction </h1>
{% endblock header %}
子テンプレートはblockの外側はコンテンツは記述できません。
子テンプレートは親テンプレートのblockタグをOverrideすることしかできません。
補足:オブジェクト指向言語の継承と一緒ですね。
##cache
cacheはテンプレートの一部をキャッシュします。
cacheの名前は式かStringのリテラル値のみ使用できます。
キャッシュ名かロケールをキャッシュのキーとして使用できます。
補足:Pebbleのキャッシュにはソースを見るとテンプレートキャッシュとタグキャッシュがあるようです。
デフォルトだとどちらも200件までキャッシュするようです。
{% cache 'menu' %}
{% for item in items %}
{{ item.text }}
....
{% endfor %}
{% endcache %}
PebbleEngine.Builderを使うことで、cacheの実装・上書きできます。
return new PebbleEngine.Builder()
.loader(this.templateLoader())
.tagCache(CacheBuilder.newBuilder().maximumSize(200).build())
.build();
##extends
extendsタグを使って親テンプレートを指定します。
子テンプレートは最初にこのタグを使用しないといけません。
それと親テンプレートは1つしか指定できません。
例を見てもらったほうが早いでしょう。
<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を継承する例です。
{% extends "base" %}
{% block title %} Home {% endblock %}
{% block content %}
Home page content.
{% endblock %}
最終的にはこのように'Home'と出力されます。
<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タグは式を許容しているので、実行時に継承元を判定してうまいこと動作します。
{% extends ajax ? 'ajax' : 'base' %}
##filter
filterタグはテンプレートにフィルター機能を付与します。
{% filter upper %}
hello
{% endfilter %}}
{# output: 'HELLO' #}
複数のフィルターも繋げて書けます。
{% filter upper | escape %}
hello<br>
{% endfilter %}}
{# output: 'HELLO<br>' #}
##flush
flushタグはWriterをflushします。
補足:用途不明…スミマセン。
{{ headerText }}
{% flush %}
{{ content }}
##for
forタグは配列かjava.lang.Iterable
を実装したリスト、Mapオブジェクトに使用できます。
{% for user in users %}
{{ user.name }} lives in {{ user.city }}.
{% endfor %}
ループの内部では特別な変数が使えます。
-
loop.index
- ゼロ始まりのループカウンタ -
loop.length
- ループに使うオブジェクトのサイズ
補足:公式ドキュメントには記載がないですが、ソースコードを見る限り以下も使えるようです。 -
loop.first
- 最初の要素 -
loop.last
- 最後の要素 -
loop.revindex
- 今の要素を最後の要素から数えてのインデックス(ゼロ始まり)
{% for user in users %}
{{ loop.index }} - {{ user.id }}
{% endfor %}
また、forタグはiterableオブジェクトが空かどうかをチェックできる便利なタグも用意しています。
{% for user in users %}
{{ loop.index }} - {{ user.id }}
{% else %}
There are no users to display.
{% endfor %}
mapオブジェクトは以下のようにしてループさせることができます。
{% for entry in map %}
{{ entry.key }} - {{ entry.value }}
{% endfor %}
##if
ifタグは式の結果に応じて指定したコンテンツの内容を出力できます。
{% 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演算子が用いられます。
##import
import
タグは他テンプレートで定義されたマクロを使用できます。
form_util
というテンプレートのなかにあるinput
というマクロがあると仮定します。
そうすると、以下のようにimportできます。
{% import "form_util" %}
{{ input("text", "name", "Mitchell") }}
###動的インポート
実行時にインポートする式を決定します。
{% import modern ? 'ajax_form_util' : 'simple_form_util' %}
{{ input("text", "name", "Mitchell") }}
##include
includeタグは他のテンプレートから挿入します。
挿入されたテンプレートは現在のテンプレートと同じ変数にアクセスできます。
Top Content
{% include "advertisement" %}
Bottom Content
{% include "footer" %}
挿入されたテンプレートに付加的な変数をcontextへ追加できます。
with
キーワードの後に連想配列を記述します。
挿入されたテンプレートは現在のテンプレートと同じ変数、1つのWith
キーワードの後ろの連想配列
にアクセスできます。
{% include "advertisement" with {"foo":"bar"} %}
###動的include
実行時にincludeする式を決定します。
{% include admin ? 'adminFooter' : 'defaultFooter' %}
##macro
macro
タグは再利用可能なコンテンツの部品を作れます。
マクロは複数回呼び出すこともできますし、
import
タグを使用しての他テンプレートから呼び出すこともできます。
マクロはどこに定義しても問題ありません。
マクロを呼び出す前や後でもどちらでもマクロ定義ができます。
{% macro input(type="text", name, value) %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}" />
{% endmacro %}
マクロは引数を省略できます。
{{ input(name="country") }}
{# will output: <input type="text" name="country" value="" /> #}
他テンプレートからマクロを使いたい場合は、import
タグを最初に記述してください。
{% import "form_util" %}
{{ input("text", "country", "Canada") }}
マクロは残りのテンプレートがアクセス可能な変数へはアクセスできません。
マクロはマクロ内の作業用変数、仮引数にしかアクセスできません。
//TODO ほんやくをもっとましにすること。残りのテンプレートとは
##parallel
このタグは新しいスレッドでレンダリングします。
PebbleEngineに設定したExecutorServiceで実行します。
{{ upperContent }}
{% parallel %}
{{ calculation.slowCalculation }}
{% endparallel %}
{{ lowerContent }}
##set
現在のcontextに変数をセットできます。変数がすでに存在するかどうかは関係なく使えます。
{% set header = "Test Page" %}
{{ header }}
##verbatim
verbatimタグを使うとPebbleの構文解析をせず、そのまま出力されます。
{% verbatim %}
{% for user in users %}
{{ user.name }}
{% endfor %}}
{% endverbatim %}
#フィルター
##abbreviate
abbreviateフィルターは省略記号(...)を使って省略します。
1つの引数をとります。省略記号を含む指定された桁数に収めて出力します。
{{ "this is a long sentence." | abbreviate(7) }}
この例はthis...
と出力されます。
- 引数は
length
です
##abs
absフィルターは絶対値を出力します。
{{ -7 | abs }}
{# output: 7 #}
##capitalize
capitalizeフィルターは最初の文字を大文字にします。
{{ "article title" | capitalize }}
この例ではArticle title
と出力します。
// TODO titleも参照ください
##date
dateフィルターはjava.util.Date
オブジェクトのフォーマットに使われます。
このフィルターはjava.text.SimpleDateFormat
のインスタンスを生成し、
それでフィルターへ渡されたDate
オブジェクトをフォーマットします。
{{ user.birthday | date("yyyy-MM-dd") }}
もう1つのこのフィルターの使い道ですが、2つの引数を使います。
1つ目の引数には出力される際の出力フォーマットです、
2つ目の引数には入力の文字列をjava.util.Date
へ変換する際のフォーマットです。
{{ "July 24, 2001" | date("yyyy-MM-dd", existingFormat="MMMM dd, yyyy") }}
この例では2001-07-24
と出力されます。
###引数
- format
- existingFormat
##default
defaultフィルターはフィルターされる値が空のときに、デフォルト値をレンダリングします。
空と判定される値はnull、空文字、空コレクション、空マップです。
{{ user.phoneNumber | default("No phone number") }}
この例では、foo,bar,bazのどれかが空の場合でも、defaultフィルターとして完璧に動作します。
{{ foo.bar.baz | default("No baz") }}
通常、strictVariables
がtrueのときにAttributeNotFoundException
例外がスローされますが、
このフィルターを使うとその例外を抑制します。
###引数
- default
##escape
escapeフィルターはXSS脆弱性を避けるために、特殊文字を安全な文字へ変換します。
このフィルターは一般的にはautoescapingをOFFにした場合に使います。
{{ "<div>" | escape }}
{# output: <div> #}
エスケーピングガイドも参照ください
//TODO
###引数
- strategy
##first
first
フィルターはコレクションの先頭の文字列、または文字列の先頭をレンダリングします。
{{ users | first }}
{# will output the first item in the collection named 'user' #}
{{ 'Mitch' | first }}
{# will output 'M' #}
##join
join
フィルターはコレクションの要素を連結します。
任意で引数に要素と要素の間の文字列を指定できます。
{#
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Joe");
names.add("Bob");
#}
{{ names | join(',') }}
{# will output: Alex,Joe,Bob #}
###引数
- separator
##last
lsft
フィルターはコレクションの最後の文字列、または文字列の最後をレンダリングします。
{{ users | last }}
{# will output the last item in the collection named 'user' #}
{{ 'Mitch' | last }}
{# will output 'h' #}
##length
length
フィルターはコレクション、マップまたは文字列の長さを返します。
{% if users|length > 10 %}
...
{% endif %}
##lower
lower
フィルターは小文字へ変換します。
{{ "THIS IS A LOUD SENTENCE" | lower }}
上記の例ではこのように出力されます。
this is a loud sentence
##numberformat
numberformat
フィルターは数字のフォーマットに使います。
java.text.DecimalFormat
を使用してフォーマットしています。
{{ 3.141592653 | numberformat("#.##") }}
上記の例ではこのように出力されます。
3.14
###引数
- format
##raw
raw
フィルターはオートエスケープによるエスケープを抑制します。
raw
フィルターは各演算子の中で必ず最後に実行されるようにしてください。
そうしないと、オートエスケープが作動し安全でないとみなしてエスケープしてしまいます。
{% set danger = "<div>" %}
{{ danger | upper | raw }}
{# ouptut: <DIV> #}
raw
フィルターを最後に使わなかった場合の例です。
{% set danger = "<div>" %}
{{ danger | raw | upper }}
{# output: <DIV> #}
//TODO エスケーピングガイドを参照すると、エスケープの動作が詳しく載っています。
##rsort
rsort
フィルターは逆順にソートします。Comparable
が実装されている必要があります。
{% for user in users | sort %}
{{ user.name }}
{% endfor %}
##slice
slice
フィルターはリスト、配列、文字列の一部を返します。
{{ ['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
が実装されている必要があります。
{% for user in users | sort %}
{{ user.name }}
{% endfor %}
##title
title
フィルターは各単語をキャタピライズします。
{{ "article title" | title }}
上記の例ではこう出力されます。
Article Title
capitalizeも参照ください
//TODO
##trim
trim
フィルターは前後の空白をトリムするのに使います。
{{ " This text has too much whitespace. " | trim }}
上記の例ではこう出力されます。
This text has too much whitespace.
##upper
upper
フィルターは大文字へ変換します。
{{ "this is a quiet sentence." | upper }}
上記の例ではこのように出力されます。
THIS IS A QUIET SENTENCE.
##urlencode
urlencode
フィルターはapplication/x-www-form-urlencoded
フォーマットへUTF-8を使用して変換します。
{{ "The string ü@foo-bar" | urlencode }}
上記の例ではこのように出力されます。
The+string+%C3%BC%40foo-bar
#関数
##block
block
関数はコンテンツを複数回出力するのに使われます。
ブロックを宣言するblock
タグと混同しないようにしてください。
この例は"post"ブロックを2回出力する例です。
1回目の出力は宣言によるもので、2回目はblock
関数によるものです。
{% block "post" %} content {% endblock %}
{{ block("post") }}
上記の例ではこのように出力されます。
content
content
パフォーマンスの警告
block
関数はflushタグが動作してくれません。
block
タグ内でflushするのは問題なくできますが、
block
関数内では独自のStringWriterを使っているため、flushはされません。
##i18n
i18n
関数はロケールを指定したResourceBundle
からメッセージを取り出します。
PebbleTemplate
は常にPebbleEngine
からデフォルトロケールを割り当てます。
テンプレートを評価する毎に、evaluate
メソッドの引数でロケールを変えることができます。
void evaluate(Writer writer, Map<String, Object> context, Locale locale);
i18n
関数はResourceBundle.getBundle(name, locale).getObject(key)
のラッパーです。
第1引数は、bundleの名前、第2引数はbundleのキーです。
{{ i18n("messages","greeting") }}
上記の例ではmessages.properties
がクラスパスにあり、且つ、
greeting
がキーにあるある前提です。
もしテンプレートのロケールがes_US
の場合は、message_es_US.properties
が検索されます。
さらに、メッセージに変数が使えます。パラメータリストを関数へ渡してあげるとMessageFormat
を使用して置き換えてくれます。
{# greeting.someone=Hello, {0} #}
{{ i18n("messages","greeting", "Jacob") }}
{# output: Hello, Jacob #}
###引数
- bundle
- key
- params
##max
max
関数は最も大きい引数を返します。
{{ max(user.age, 80) }}
##min
min
関数は最も小さい引数を返します。
{{ min(user.age, 80) }}
##parent
parent関数はblock
タグ内で使用できる、親テンプレートのblockの内容を出力できるものです。
テンプレートの内容をoverrideはしません。
Javaでいうsuper
のようなものです。
"parent.peb"という以下テンプレートを仮定しましょう。
{% block "content" %}
parent contents
{% endblock %}
また違うテンプレート"child.peb"という
"parent.peb"を継承した以下テンプレートを用意します。
{% extends "parent.peb" %}
{% block "content" %}
child contents
{{ parent() }}
{% endblock %}
すると以下出力されます
child contents
parent contents
##range
range
関数は算術型の数列を生成します
補足:Pythonとかみたいだね!
{% for i in range(0, 3) %}
{{ i }},
{% endfor %}
{# outputs 0, 1, 2, 3, #}
第3引数に、増加分を指定できます。デクリメントもできます。
{% for i in range(0, 6, 2) %}
{{ i }},
{% endfor %}
{# outputs 0, 2, 4, 6, #}
Pebbleのビルドインの..
という演算子は、range関数と+1インクリメントの省略形として記述できます。
{% for i in 0..3 %}
{{ i }},
{% endfor %}
{# outputs 0, 1, 2, 3, #}
#テスト
##empty
empty
テストは空チェックします。変数がnull、空文字、コレクションが空、Mapが空であることをテストします。
{% if user.email is empty %}
...
{% endif %}
##even
even
テストは数字が偶数であることをテストします。
{% if 2 is even %}
...
{% endif %}
##map
map
テストはオブジェクトがMapであることをテストします。
{% if {"apple":"red", "banana":"yellow"} is map %}
...
{% endif %}
##null
null
テストは変数がnullであることをテストします。
{% if user.email is null %}
...
{% endif %}
##odd
odd
テストはテストは数字が奇数であることをテストします。
{% if 3 is odd %}
...
{% endif %}
##iterable
iterable
テストはjava.lang.Iterable
を実装していることをテストします。
{% if users is iterable %}
{% for user in users %}
...
{% endfor %}
{% endif %}
#演算子
##比較演算子
Pebbleは==
, !=
, <
, >
, <=
, >=
を用意しています。
==
を除く、全ての演算子はJavaと同じです。
==
演算子はJava.util.Objects.equals(a, b)
を内部で使用します(nullセーフです)。
equals
は==
の別名です。
{% if user.name equals "Mitchell" %}
...
{% endif %}
##contains
contains
演算子はコレクション、マップ、配列に特定の値が含まれているか、テストできます。
{% if ["apple", "pear", "banana"] contains "apple" %}
...
{% endif %}
マップの場合はキーを指定します。
{% if {"apple":"red", "banana":"yellow"} contains "banana" %}
...
{% endif %}
複数のアイテムも指定できます。
{% if ["apple", "pear", "banana", "peach"] contains ["apple", "peach"] %}
...
{% endif %}
##is
is
演算子はbooleanを返す変数にテストを適用します。
{% if 2 is even %}
...
{% endif %}
否定したい場合は、not
演算子を使います。
#論理演算子
and
演算子、or
演算子はbooleanの式を結合します。
{% if 2 is even and 3 is odd %}
...
{% endif %}
否定したい場合は、not
演算子を使います。
{% if 3 is not even %}
...
{% endif %}
カッコをつけると式をグループ化して、必要な優先順位をつけれます。
{% if (3 is not even) and (2 is odd or 3 is even) %}
...
{% endif %}
#算術演算子
通常の算術演算もできます。
{{ 2 + 2 / ( 10 % 3 ) * (8 - 1) }}
#その他演算子
|
演算子でフィルターが使えます。
{{ user.name | capitalize }}
三項演算子
{{ foo == null ? bar : baz }}