Template Toolkit は Perl の世界では鉄板のテンプレートエンジンといっても過言ではないでしょう。 私が知る限り、Template Toolkit でやりたいことはほとんど実現できます。それくらい機能が豊富です。 ここでは、Template Toolkit の良く使う、または、便利そうな機能に絞って解説します。
まずは、Template Toolkit の使い方を一通り見てみましょう。 次の内容のテンプレートファイル template.html を用意します。
<h1>会員一覧 ([% year %]年[% month %]月[% day %]日現在)</h1>
[% IF msize %]
<ul>
[% FOREACH member IN mlist %]
<li>[% member.name | html %] ([% member.age %])</li>
[% END %]
</ul>
[% END %]
次に、テンプレートファイル template.html と同じディレクトリ位置に次の Perl スクリプトを用意します。 この Perl スクリプトは、同じディレクトリ位置にあるテンプレートファイル template.html を読み込み、 変数をセットしたうえで STDOUT に出力します。
use utf8;
use Template;
use Encode;
# 変数定義
my @now = localtime;
my $mlist = [ { name => '太郎', age => 24 }, { name => '次郎', age => 21 } ];
my $msize = scalar @{$mlist};
my $vars = {
year => $now[5] + 1900,
month => $now[4] + 1,
day => $now[3],
mlist => $mlist,
msize => $msize
};
# オブジェクト生成
my $template = Template->new( { ENCODING => 'utf8' } );
# 結果をスカラー変数に格納して STDOUT に出力
my $output = '';
$template->process( 'template.html', $vars, \$output );
print Encode::encode( 'UTF-8', $output );
この Perl スクリプトと同じディレクトリ位置をカレントディレクトリとします。 そして、この Perl スクリプトを実行すると、その出力結果は次の通りになります。
<h1>会員一覧 (2023年3月30日現在)</h1>
<ul>
<li>太郎 (24)</li>
<li>次郎 (21)</li>
</ul>
以上のサンプルだけでも、テンプレートの書き方とテンプレートの処理の方法をざっくりと理解できたのではないでしょうか。 以降、この Template Toolkit の詳細を解説していきます。
Template Toolkit を使うには、Perl スクリプトで Template のオブジェクトを生成しなければいけません。 このオブジェクトの生成時に与えるパラメータで Template の挙動が決まります。
my $template = Template->new(
{
ENCODING => 'UTF-8',
...
}
);
以上は冒頭のサンプルコードの抜粋ですが、パラメータは他にも数多く用意されています。 以下に良く使うであろうパラメータを紹介します。
ENCODING
テンプレートファイルのエンコーディングを
ENCODING => 'utf8'
のように指定します。
use utf8
プラグマを使ったスクリプトで日本語を扱うのであれば、
必ず指定するようにしましょう。
このパラメータはファイルの読み取りにのみ適用されます。 ファイルへの書き出しや標準出力には適用されませんので注意してください。
START_TAG
, END_TAG
テンプレートの開始タグと終了タグを指定します。デフォルトは [%
と %]
です。
PRE_CHOMP
, POST_CHOMP
1
を指定すると、ディレクティブの前および後ろのホワイトスペースを削除します。
デフォルトは 0
です。つまり前後のホワイトスペースを削除しません。
TRIM
1
を指定すると、テンプレート出力の最初と最後のホワイトスペースを削除します。
デフォルトは 0
です。つまり前後のホワイトスペースを削除しません。
INCLUDE_PATH
テンプレートファイルを検索するパスを指定できます。
複数のパスを指定する場合は :
で区切ります。
Template オブジェクトの process()
メソッドは、第一引数にテンプレート入力、第二引数に変数を格納した hashref、
第三引数に出力先を指定します。
process($template, \%vars, $output)
第一引数のテンプレート入力には、テンプレートファイルのファイル名、または、テンプレートの内容を格納したスカラー変数を指定します。
一方、第三引数の出力先は、ファイルパス、または、結果を格納するスカラー変数のリファレンスを指定することができます。 もし第三引数に何も指定がないと STDOUT に結果が出力されます。
# オブジェクト生成
my $template = Template->new( { ENCODING => 'utf8' } );
# ファイルに出力
$template->process( 'template.html', $vars, './output.html', binmode => ':utf8' );
# スカラー変数に格納して STDOUT に出力
my $output = '';
$template->process( 'template.html', $vars, \$output );
print Encode::encode( 'UTF-8', $output );
# STDOUT に出力
$template->process( 'template.html', $vars ); # Wide character 警告が出力される
process()
メソッドは第三引数に何も指定が無ければ STDOUT に結果を出力しますが、
内部文字列を外部文字列に変換する仕組みが用意されていません。
日本語を含む場合は Wide character 警告が表示されてしまいますので注意してください。
どうしても process()
メソッドに結果を出力させたいのであれば、
標準出力のデフォルトエンコードを utf8 となるよう、スクリプトの先頭付近に次のコードを入れておきます。
binmode STDOUT, ":utf8";
第一引数のテンプレート入力ですが、ファイルを指定する場合はファイル名のみを指定します。
デフォルトではパスと一緒に指定することはできません。
process()
メソッドは、オブジェクト生成時にパラメータ INCLUDE_PATH
に指定されたパスから該当のファイルを検索します。
もし複数のパスから検索させたい場合は、:
で区切って指定します。
use utf8;
use Template;
use FindBin;
binmode STDOUT, ":utf8";
# 変数定義
my $vars = {...};
# オブジェクト生成
my $template = Template->new(
{
ENCODING => 'utf8',
INCLUDE_PATH => "${FindBin::Bin}:${FindBin::Bin}/templates"
}
);
# STDOUT に出力
$template->process( 'template.html', $vars );
13 行目に注目してください。ここで、テンプレートファイルの検索パスとして、${FindBin::Bin}
と ${FindBin::Bin}/templates
の 2 つを指定しています。
なお、${FindBin::Bin}
は、Perl スクリプトが存在するディレクトリパスです。
このコードでは、Perl スクリプトと同じディレクトリ位置、または、その下部の templates ディレクトリ位置の 2 箇所から template.html を検索することになります。
テンプレートには [% IF msize %]
や [% END %]
といったディレクティブが記述されますが、
これらディレクティブは空白行として出力されてしまいます。
HTML ならさほど問題にはなりませんが、たとえばメール本文などテキストとして出力する場合、
非常に見苦しい結果となります。次の例をご覧ください。
[% FOREACH member IN mlist %]
- [% member.name %] ([% member.age %])
[% END %]
use utf8;
use Template;
binmode STDOUT, ":utf8";
# 変数定義
my $vars =
{ mlist => [ { name => '太郎', age => 24 }, { name => '次郎', age => 21 } ] };
# オブジェクト生成
my $template = Template->new(
{
ENCODING => 'utf8'
}
);
# STDOUT に出力
$template->process( 'template.txt', $vars );
上記スクリプトは次のような結果を出力します。
- 太郎 (24)
- 次郎 (21)
このような無駄となりそうなディレクティブ前後のホワイトスペースを削除するには、
Template オブジェクト生成時にパラメータ PRE_CHOMP
または POST_CHOMP
に 1
をセットすると良いでしょう。
my $template = Template->new(
{
ENCODING => 'utf8',
POST_CHOMP => 1
}
);
この場合は次のように出力されます。
- 太郎 (24)
- 次郎 (21)
ディレクティブだけでなく、テンプレートそのものの最初と最後のホワイトスペースをきれいに切り取りたい場合は、
パラメータ TRIM
に 1
をセットします。
my $template = Template->new(
{
ENCODING => 'utf8',
POST_CHOMP => 1,
TRIM => 1
}
);
テンプレートのディレクティブの開始タグと終了タグはデフォルトでは [%
と %]
です。変数であれば [% varname %]
のように記述するのは前述の通りです。
この開始タグと終了タグは、Template オブジェクト生成時にパラメータ START_TAG
と END_TAG
をセットすることで変更することが可能です。
次の例は開始タグと終了タグを {{
と }}
に変更し、変数なら
{{ varname }}
と記述できるようにします。
my $template = Template->new(
{
ENCODING => 'UTF-8',
START_TAG => '\{\{',
END_TAG => '\}\}'
}
);
START_TAG
と END_TAG
の値を \
でエスケープしている点に注目してください。
開始タグと終了タグは Template モジュールでは正規表現の一部として処理されます。
開始タグと終了タグに正規表現に関係する文字が含まれると正しく動作しません。
そのため、そういった文字の前に \
を入れてエスケープします。
上記のコードによって、変数だけでなく、すべてのディレクティブの開始タグと終了タグが変更されます。
Template モジュールは変数の値を変換するさまざまなフィルターを用意しています。 アルファベット小文字・大文字変換、HTML エスケープ、URL エスケープなど多岐にわたります。 変数にフィルターを適用するには、次のように記述します。
[% varname | html %]
[% varname FILTER html %]
html
の部分がフィルターの種類を表します。
html
は HTML エスケープを表し、たとえば <
は
<
に変換されます。
上記 2 行はどちらも同じ結果になり、どちらの記述方法を利用しても構いません。
以下に便利そうなフィルターのいくつかを紹介します。
html
HTML エスケープします。<
, >
, &
,
"
をそれぞれ <
, >
, &
,
"
に変換します。
xml
上記の html
の変換に加え、'
を '
に変換します。
html_line_break
改行の前に <br />
を挿入します。
uri
URI エスケープします。Hello World
なら Hello%20World
に、
おはよう
なら %E3%81%8A%E3%81%AF%E3%82%88%E3%81%86
に変換されます。
truncate(length,dots)
第一引数に指定した文字数に切り詰めます。第二引数が指定されていれば、切り詰めた後にその文字を追加します。 第二引数が指定されなければ '...' が指定されたものとして扱われます。 なお、第一引数の文字数は第二引数で指定した追加文字も含んだ文字数ですので注意してください。
たとえば、テンプレートに [% varname | truncate(8, '...') %]
と記述したとして、変数 varname に「おやようございます」をセットすると、
「おはようご...」に変換されます。
Template モジュールは上記の他にも様々なフィルターを用意しています。 フィルターの詳細は 公式サイトのマニュアルをご覧ください。
Template モジュールでは、条件分岐のディレクティブに IF
, UNLESS
, ELSIF
,
ELSE
が用意されています。
[% IF success %]
成功
[% ELSE %]
失敗
[% END %]
変数 success の値が真か偽か判定されます。次のように値を指定することもできます。
[% IF status == 1 %]
仮登録
[% ELSIF status == 2 %]
登録済
[% ELSE %]
退会
[% END %]
さらに、値を不等式で評価することも可能です。
[% IF point >= 70 %]
合格
[% ELSE %]
失格
[% END %]
利用可能な比較演算子は ==
, !=
, <
,
<=
, >
, >=
, &&
,
||
, !
です。このように Perl の構文で使われる演算子が利用可能です。
このほか、Python のような or
, and
, not
も利用可能です。
[% IF degc >= 25 and degc <= 28 %]
最適な温度です。
[% END %]
リスト情報を繰り返し処理する場合は FOREACH
を使います。
Perl 構文の foreach
のように柔軟な記述方法が許されています。
arrayref の中の要素を順に出力するなら、次のようになります。
[% FOREACH num IN nlist %]
- [% num %]
[% END %]
$template->process( 'template.txt', { nlist => [ 3, 5, 9 ] } );
もし arrayref の各要素が hashref なら、次のように記述します。
[% FOREACH member IN mlist %]
- [[% member.id %]] [% member.name %]
[% END %]
my $mlist = [
{ id => 'r4-03', name => '太郎' },
{ id => 'r5-12', name => '次郎' },
{ id => 'r6-01', name => '三郎' }
];
$template->process( 'template.txt', { mlist => $mlist } );
このように、リストの各要素が hashref の場合は、ドットシンタックスで各要素内のプロパティ値を表現します。 この点は Perl の構文と異なるため注意が必要です。
hashref を含んだ arrayref を繰り返し処理する場合、テンプレート側では次のような省略表記を認めています。
[% FOREACH mlist %]
- [[% id %]] [% name %]
[% END %]
FOREACH
の中で使われる変数は、FOREACH
の外では利用できないことは当然なのですが、
逆に FOREACH
の外で使われる変数は、FOREACH
の中で利用することができます。
[% FOREACH product IN plist %]
<a href="[% path %]/[% product.code %].html">[% product.name %]</a>
[% END %]
my $vars = {
path => '/products',
plist => [ { name => '商品A', code => 'A' }, { name => '商品B', code => 'B' } ]
};
$template->process( 'template.html', $vars );
このサンプルでは、変数 path は FOREACH
の外で定義されているものですが、
ここでは FOREACH
の中で使われています。
このサンプルの出力結果は次のようになります。
<a href="/products/A.html">商品A</a>
<a href="/products/B.html">商品B</a>
一つのテンプレートの中に別のテンプレートファイウルを組み込んで処理するには INCLUDE
を使います。
まず親のテンプレートに INCLUDE
タグを使って includes/header.html
を読み込んでいます。
パスの先頭に ./
を入れないでください。
[% INCLUDE 'includes/header.html' %]
header.html の内容は次の通りです。
<header>[% title | html %]</header>
Perl スクリプトでは、次のように変数をセットします。
$template->process( 'template.html', { title => 'Perl 初心者講座' } );
これによって、次のような結果が出力されます。
<header>Perl 初心者講座</header>
以上、Template モジュールの必要最低限の使い方を解説しましたが、 Template モジュールには 1 ページでは紹介しきれないほど他にも様々な機能が用意されています。 もしすべての機能を確認したいのでしたら、英語ですが、 公式サイトのドキュメント を見ると良いでしょう。
実はほとんどのプロジェクトで、私自身はここで紹介した機能しか使っていません。
また、これ以上の機能を必要と感じたことはほぼありません。
テンプレートエンジンにどこまでやらせるべきかについては、考え方にも大きく依存するでしょう。
例えば、Template モジュールには FOREACH
で処理するリストをソートする機能もあります。
そのソートはテンプレート側でやるべきなのか、Perl スクリプト側でやるべきなのか、どちらが良いかは
一概に言えません。
私にとってはオーバースペックとも言える Template モジュールですが、それでも重宝しています。 それほどに使い勝手が良いことに加え、いざ、何か追加で機能が必要と感じたときに、 Template モジュールであれば何とかなるだろう、という安心感もあります。
もし、まだ Template モジュールを使ったことがなく、テンプレートエンジンの選定に迷っているなら、ぜひ一度お試しください。 テンプレートエンジンは必要最小限の機能だけで良く、軽量であることを最優先したいのであれば、 HTML::Template も良いかもしれません。