HTML::Template(3) TMPL_LOOP

引き続き HTML::Template のテンプレートタグを勉強します。今回は <TMPL_LOOP>。
<TMPL_LOOP> タグは、表示するページの一部分を繰り返したい時に使います。例えば「商品のカタログ」を表示するページを作っている場合、複数の商品の情報(名前や値段など)をそれぞれ表示する為に、商品の数だけ一部分(表の列など)を繰り返す必要がありますよね。


<TMPL_LOOP> タグに利用できる属性は NAME だけです。TMPL_VAR と同じく、TMPL_LOOP が利用するデータを NAME 属性で指定するのですが、TMPL_VAR とは違い、その内容が直接置き換わるわけではなく、<TMPL_LOOP> という開始タグと、</TMPL_LOOP> という終了タグで繰り返す部分を挟み込みます。

<TMPL_LOOP NAME="DATASET">
  この部分が繰り返される
</TMPL_LOOP>

この場合、DATASET の中にあるデータの数だけ、「この部分が繰り返される」という文章が表示される事になります。


TMPL_VAR の時と同じように、TMPL_LOOP に与えるデータも HTML::Template オブジェクトの param メソッドで与えるのですが、次のようなデータ構造で渡します。

$tmpl->param( FRUITS => [
                { NAME => "Apple",  COLOR => "Red" },
                { NAME => "Banana", COLOR => "Yellow" },
                { NAME => "Peach",  COLOR => "Pink" },
              ] );

ここで復習。

  • A => B というのは "A", B と同じ意味(シンタックスシュガー)
  • [ ] でリストを囲んだ場合、無名配列が作られてそのリファレンスが得られる
  • { } でリストを囲んだ場合、無名ハッシュが作られてそのリファレンスが得られる

つまり、param に渡しているのは「"FRUITS" という文字列」と、「無名配列へのリファレンス」です。その無名配列の中には果物のデータが3つ入っています。果物のデータは「無名ハッシュへのリファレンス」で、中には "NAME" と "COLOR" がそれぞれキーとなっている2つの要素が入っています。
ちょっと複雑な構造ですが、基礎を勉強したお陰かすんなり理解できました。「配列はスカラー値の集まり」「リファレンスはスカラー値の一種」という事を覚えていれば、どうしてこのようにリファレンスを多用しているのかもわかります。Perl での2次元配列はリファレンスを使って表現するのでしたね。(^_^)


このようにデータを渡すと、以下のようなテンプレートで使う事ができます。

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>

「FRUITS」についての TMPL_LOOP の中で、「NAME」の内容と「COLOR」の内容を TMPL_VAR で出力しています。このテンプレートを、先ほど与えた果物のデータを使って表示させると、以下のように置き換えられます。

<dl>
    
    <dt>Apple</dt>
    <dd>Red</dd>
    
    <dt>Banana</dt>
    <dd>Yellow</dd>
    
    <dt>Peach</dt>
    <dd>Pink</dd>
    
</dl>

「FRUITS」として与えた配列(のリファレンス)が順番に出力されているのがわかりますね。そして TMPL_VAR は、配列の中のハッシュに則って置換されているのがわかります。素直でわかりやすい動作ですね。


ここで、以下のような疑問を持ちました。
「TMPL_LOOP の外で使う為に与えたデータを、TMPL_LOOP の中で使おうとするとどうなるのでしょう」
どういう事かと言うと、まず param メソッドで以下のような値を与えます。

$tmpl->param( NAME => "Palmo", COLOR => "White" );

この値は <TMPL_VAR NAME="NAME"> や <TMPL_VAR NAME="COLOR"> で表示できるのでしたね。でも、ちょっと待ってください。先ほど書いたテンプレートの中の TMPL_LOOP 内でも、全く同じように TMPL_VAR を使って表示させていました。

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>

この場合、「Palmo」と「White」が優先されて、FRUITS の中の要素数分表示されるのでしょうか。それとも FRUITS の中身がそれぞれちゃんと表示されるのでしょうか。


実験してみると、以下のようなエラーが出てしまいました。

HTML::Template : Attempt to set nonexistent parameter 'name' - this parameter na
me doesn't match any declarations in the template file : (die_on_bad_params => 1
) at tmplloop.cgi line 7

つまり、「NAME」として与えた "Palmo" がテンプレートで使われていない、というエラーのようです。与えたデータはテンプレートの中で漏れなく使わなくてはいけないのでしょうか。ちょっと不便ですね。(^_^;)
エラーがでましたが、結果的に「"Palmo" や "White" が使われていない」=「FRUITS の中身がちゃんと表示される」という事がわかりました。試しに、TMPL_LOOP の外で <TMPL_VAR NAME="NAME"> と <TMPL_VAR NAME="COLOR"> を書いて

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>
<p><TMPL_VAR NAME="NAME"> is <TMPL_VAR NAME="COLOR">.</p>

表示させてみると、想像通り、TMPL_LOOP の中と外で TMPL_VAR の NAME が区別されて表示されました。

<dl>
    
    <dt>Apple</dt>
    <dd>Red</dd>
    
    <dt>Banana</dt>
    <dd>Yellow</dd>
    
    <dt>Peach</dt>
    <dd>Pink</dd>
    
</dl>
<p>Palmo is White.</p>


perldoc HTML::Template の TMPL_LOOP の項をよく読むと、この事について書かれていました。

Inside a <TMPL_LOOP>, the only variables that are usable are the ones
from the <TMPL_LOOP>. The variables in the outer blocks are not visible
within a template loop. For the computer-science geeks among you, a
<TMPL_LOOP> introduces a new scope much like a perl subroutine call. If
you want your variables to be global you can use 'global_vars' option to
new() described below.

以下、palmo の意訳です。

<TMPL_LOOP> の中では <TMPL_LOOP> で指定された変数しか使用できません。TMPL_LOOP ブロックの外の変数は、ブロックの中に入ると見えなくなります。geek にわかりやすく言えば、<TMPL_LOOP> は Perl のサブルーチン呼び出しのように新しいスコープを生成します。もし、変数をグローバルにしたい場合は、new() の際に 'global_vars' オプションを利用してください。

なるほど。これなら、TMPL_LOOP の中と外で TMPL_VAR の置換対象が異なるのもよくわかります。TMPL_LOOP は新しいスコープを生成するので、スコープの外の変数とは明確に区別されるわけですね。(^_^)
もし、ループの外の変数を、ループの中で参照したい場合は

my $tmpl = HTML::Template->new(filename => 'tmplloop.tmpl',
                               global_vars => 1);

のように、「global_vars」オプションを有効にすればいいとの事です。


今回の勉強に利用した CGI スクリプトは以下の通りです。

#!/usr/local/bin/perl
use strict;
use HTML::Template;

my $tmpl = HTML::Template->new(filename => 'tmplloop.tmpl');

$tmpl->param( NAME => "Palmo", COLOR => "White");

$tmpl->param( FRUITS => [
                { NAME => "Apple",  COLOR => "Red" },
                { NAME => "Banana", COLOR => "Yellow" },
                { NAME => "Peach",  COLOR => "Pink" },
              ] );

print "Content-Type: text/html\n\n", $tmpl->output;

また、利用したテンプレート「tmplloop.tmpl」は、以下の通りです。

<html>
<head><title>TMPL_LOOP Fruits</title></head>
<body>
<h1>TMPL_LOOP Fruits</h1>

<h2>果物の色</h2>
<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="MSG"><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>
<p><TMPL_VAR NAME="NAME"> is <TMPL_VAR NAME="COLOR">.</p>

</body>
</html>

実行結果は以下から見る事ができます。
tmplloop.cgi 実行結果


制御構造を覚えると、表現の幅がとても広がりますね。(^_^)
HTML::Template では「繰り返し」の他に「分岐」も利用できるので、プログラムのようにテンプレートを作る事ができます。分岐については、また別のエントリで勉強したいと思います。