HTML::Template

テンプレートを使用したHTMLページの動的生成

長所
・埋め込み変数にループする要素を出力したいとき(テーブルに不定個数の行を出力したい、等)に専用のタグを使えばよく、容易。
短所
・テンプレート内の埋め込み変数にタグ(<TMPL VAR>)を使うので、タグ内の要素を生成とようとするとタグがネストする形になってレイアウトがくずれる場合がある。

上記の長所・短所は、CGI::FastTemplate の特徴と逆。"レイアウトが崩れる対策"を使うと、短所をカバーできるのでHTML::Templateの方が使いやすいかも。

テンプレートの形式:

<TMPL_VAR NAME="FOO">
HTMLファイルの動的生成を行いたい場所に、このタグを記述する。変数名は[A-Za-z0-9_]にマッチする文字で構成する。下記のパラメタを指定できる。
<TMPL VAR NAME="FOO" ESCAPE=HTML>:HTMLエスケープする。
<TMPL VAR NAME="FOO" ESCAPE=URL>:URLエンコードする。

ユーザが入力した文字列を埋め込む場合、クロスサイトスクリプティングを防止するため、意図的にタグを出力したい場合を除いて必ずESCAPE=を指定するべきである。クロスサイトスクリプティングに利用される可能性のある " < > & ' は、ESCAPE=HTMLで全てエスケープしてくれる。タグを出力したい場合は、ESCAPE指定なしにして、アプリ側で適切なエスケープ処理をする。HTML::Templateにエスケープ処理させるのか、アプリ側でエスケープ処理するのか明確にしないと二重にエスケープされてしまうので注意。

ESCAPE=URLの実装は、ちょっと変。普通は、URLとして使用できない文字(スペース = & % + 非ASCII)だけをエンコードするのだが、/ とか : も エンコードしてしまい、http://~を渡すと、http%3A%2F%2F に変換されてしまう。普通のブラウザは、この表記をデコードしてくれな い。従って、? パラメタの後ろだけをエンコードするとかの用途にしか使えない。

<TMPL_LOOP NAME="FOO">~</TMPL_LOOP>
不定個数の要素を出力したい箇所に使う。
例えば:
<table>
<TMPL_LOOP NAME="ROWS">
<tr>
<td><TMPL_VAR NAME="COL1"></td>
<td><TMPL_VAR NAME="COL2"></td>
</tr>
</TMPL_LOOP>
</table>

<TMPL_IF NAME="FOO1">~<TMPL_ELSE>~</TMPL_IF>
変数の真偽によってブロックの出力を制御する。

<TMPL_UNLESS NAME="FOO1">~</TMPL_UNLESS>
変数の真偽によってブロックの出力を制御する。

使い方:

use HTML::Template;
// テンプレートファイルを読み込んでインスタンスを生成する
my $tpl = HTML::Template->new(filename => $template,die_on_bad_params => 0);
// テンプレートを変数から読み込んでインスタンスを生成する
my $html = <<HTML;
<html>
:
</html>
HTML
my $tpl = HTML::Template->new(scalarref => \$html);
// 変数に値を代入する
$tpl->param(FOO => $val);
// ハッシュを使って複数の変数に値を代入する
my %foo;
$foo{'FOO1'} = $val1;
$foo{'FOO2'} = $val2;
$tpl->param(%foo);
// ループ変数に値を代入する
my @rows;
for (my i=0; i<3; ++i) {
	my %foo;
	$foo{'FOO1'} = $val1;
	$foo{'FOO2'} = $val2;
	push(@rows,\%foo);
}
$tpl->param(FOO => \@foo);
// 出力する
print $cgi->header(status => 200);
print $tpl->output();

レイアウトが崩れる対策:

1. TMPLタグの形式は、柔軟に記述できる。
下記の記述は、いずれも同じ意味に解釈される。TMPL_VAR以外のタグも同様な記述が可能。
HTMLエディタによるが、2番目以降のこれらの表記を使う事によって、"TMPLタグを埋め込むとレイアウトが崩れる"現象を回避できる場合がある。
<TMPL_VAR NAME="FOO1">
<TMPL_VAR NAME='FOO1'>
<TMPL_VAR NAME=FOO1>
<TMPL_VAR FOO1>
<!-- TMPL_VAR NAME="FOO1" -->
<!-- TMPL_VAR NAME='FOO1' -->
<!-- TMPL_VAR NAME=FOO1 -->
<!-- TMPL_VAR FOO1 -->

2. newメソッドでフィルタを使う
例えば、テンプレートには上記のTMPL_VARタグを${変数名}に置き換えて記述しておき、filterパラメタを追加してnewメソッドを使う。
HTML::Templateは、newメソッドにfilterパラメタが指定されると、テンプレートの解析を始める前に、テンプレートへの参照を引数として&$filterを呼び出す。
&$filter内で${変数名}を<TMPL_VAR 変数名>に置換する。

my $filter = sub {
	my $ref = shift;
	$$ref =~ s/\${([A-Za-z0-9_]+)}//g;
};
my $tpl = HTML::Template->new(filename => $template , filter => $filter);

下記のような簡単な派生クラスを作成して、派生クラスを使うようにすると、フィルター用の無名サブルーチンを毎回書く手間が省ける。

この派生クラスは、${変数名}が " " で囲われていた場合のみ、<TMPL_NAME 変数名>に置換する。

package FlexTemplate;

use strict;
use warnings;
use base qw(HTML::Template);

sub new {
	my $param = shift;
	my $class = ref($param) || $param;
	my $self = $class->HTML::Template::new(@_ ,filter => \&_flex_filter);
	$self;
}

sub _flex_filter {
	my $ref = shift;
	$$ref =~ s/"\s*\${([A-Za-z0-9_]+)}\s*"/"<TMPL_VAR $1>"/g;
}

1;