Template Toolkit 探訪 (1)
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 コードに落とし込まれていることがわかります。