Perl でドキュメントテンプレートシステムを利用したい場合,
- 変数の展開を使う(ex. print "Hello, ${world_name}\n";)
- 正規表現の置換を使う
- HTML::Template を使う
- Template (Toolkit) を使う
あたりが選択肢となるかと思います。
3番目の HTML::Template は,内的には2番目の置換を使っているだけなのでわりとシンプルな仕組みです。できることもわりとシンプルで縛りも多いですが,ロジックとビューの分離という点では縛りのせいで逆に明確にわけることができて,結構人気が高いようです。
4番目の Template Toolkit(以下 TT)はとても高機能で,それゆえ一部の層に人気です。ベンチマーク結果 によると(デフォルトの設定では) HTML::Template には勝ててはいないようですが,高機能な割には十分高速だと思います。
TT の高速性は XS を持っているからだ,と誤解されている向きもありますが,実際には「Template-ToolkitはPure Perlでも動く : blog.nomadscafe.jp」のように,Stash の部分で XS を使っているだけであり,ほとんどの部分は Pure Perl で書かれています。TT のキモは,
という点にあります。
つまり,たとえば
This is [% myval %].
というテンプレートがあった場合,これはコンパイルされて,
sub {
"This is " . $myval . ".";
}のような無名サブルーチンに落とし込まれるということです。ですから,パーシングには時間がかかるものの,実際に展開する局面では HTML::Template などの正規表現置換系なみに高速に動作する,はず,です。
今回はこの実際のコンパイルされたテンプレートドキュメントをみてみたいと思います。
まずは,先ほどのサンプルをスクリプトに落とし込んでみます。
#!/usr/bin/perl
use strict;
use Template;
use Template::Context;
my $tc = Template::Context->new;
my $td = $tc->template(\*DATA);
print $tc->process($td, { myval => 'test' });
__END__
This is [% myval %].ドキュメントオブジェクトををとりだしやすいように,Template ではなく Template::Context を使っています。この実行結果は,もちろん,
This is test.
となります。
さて,コンパイルされたドキュメントオブジェクトをみてみましょう。「__END__」の前に,
use Data::Dumper; print Dumper($td);
を付け加えてみましょう。
$VAR1 = bless( {
'_DEFBLOCKS' => {},
'_BLOCK' => sub { "DUMMY" },
'callers' => [],
'modtime' => 1146287604,
'name' => 'input file handle',
'_HOT' => 0
}, 'Template::Document' );インデントは変えてありますが,このとおり,Template::Document というクラスのオブジェクトになっていることがわかります。
_BLOCK の「sub { "DUMMY" }」とあるのは,Data::Dumper がサブルーチンリファレンスをこのように表現しただけなので,本当の中身ではありません。
このサブルーチンの中身を B::Deparse を使って見てみましょう。「_BLOCK」は $td->block() でアクセスできるので,さらにコードを付け加えてみます。
use B::Deparse; my $b = B::Deparse->new(qw(-P -sC)); print $b->coderef2text($td->block);
詳しくは B::Deparse のマニュアルを見てください。この実行結果は,
{
package Template::Document;
use strict 'refs';
my $context = shift @_ || die("template sub called without context\n");
my $stash = $context->stash;
my $output = '';
my $error;
eval {
do {
BLOCK: {
$output .= 'This is ';
$output .= $stash->get('myval');
$output .= ".\n";
}
}
};
if ($@) {
$error = $context->catch($@, \$output);
die $error unless $error->type eq 'return';
}
return $output;
}となります。
ご覧の通り,例外処理の処理でやや冗長になっていますが,テンプレートの内容が Perl コードに落とし込まれていることがわかります。