LoginSignup
3
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-22

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&lt;br&gt;' #}

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: &lt;div&gt; #}

エスケーピングガイドも参照ください
//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: &lt;DIV&gt; #}

//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"という以下テンプレートを仮定しましょう。

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 }}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1