今日のCPANモジュール(跡地)
このサイトが元になったCPANモジュールガイドという本を書きました。
本書ではTemplateの代わりにText::Xslateを扱いました。
Text::Xslateの項目以外でも、Data::Pageなどテンプレートで使われそうなモジュールについてはText::Xslateテンプレートでの利用例を載せてあります。
2007-02-25
use Template;
これから数回は、仕事でよく使う大型モジュールを中心に紹介して行こうと思います。まずは、Templateです。ディストリビューション名は Template-Toolkit というので、TT と呼ばれます。
CPAN は、こういったトップレベル(モジュール名に :: がつかない)の名前空間はよほどのものでないと使わないようにということになっています。いちおう。その点、Template は「テンプレート」を名乗るにふさわしい、一級のモジュールです。
要するに Perl 版 JSP と言えば早いでしょうか。非常に強力なのでテンプレート側でいろいろできちゃったりしますので使い方はあなた次第、という感じです。が、いざという時(きれいなロジックを書いてるヒマがなかった時とか...)助かります。
コード側は簡単で、以下のように使います。
use Template;
# インスタンスを作成し、
my $tt = new Template;
my $vars = {
name => "とみた",
age => 28,
};
# test.tt テンプレートファイルに $vars をバインドして出力
$tt->process('test.tt', $vars)
or die $tt->error;
process() メソッドだけ覚えておけばよく、1番目がテンプレート、2番目が変数のハッシュです。test.tt ファイルには、例えば以下のように書いておくと、
「[% name %]さん、あと[% 30 - age %]才で30ですよ」
「とみたさん、あと2才で30ですよ」と出力されます。テンプレートファイルの拡張子は何でもいいですが、TT なので .tt とする人が多いです。
1番目の引数にはファイル名以外に、ファイルハンドルやテキストそのものも渡せます。これらを渡すときはリファレンスで渡します。
$tt->process(*DATA, $vars); # DATAファイルハンドル
my $text = "My name is [% name %].";
$tt->process($text, $vars); # テキストのリファレンス
私はこれのようにコネタ系 CGI を書くときDATA ファイルハンドルを使ったりします。一つのファイルに __DATA__ 前後でまとめて入れられるので気に入っています。
process() はデフォルトは標準出力に結果を出しますが、結果を変数、例えば $out に入れたい場合は3番目に格納先変数をリファレンスで渡します。
$tt->process('template-test.tt', $vars, my $out)
or die $tt->error;
シンタックス 基本編
では、そんな TT の強力っぷりについてできるだけ説明してみます。それぞれ簡単にしか説明できないので、詳しくはそれぞれの項目に置いた POD のリンクも参照してください。
TT タグ(POD)
基本は
[%と%]内が TT エリアとなりますが、インスタンスを作るときの TAG_STYLE オプションで<? ?>とか<% %>とか<!-- -->とかに好きに設定できます。my $tt = new Template({ TAG_STYLE => 'html', # <!-- と --> を使う });他にもテンプレート内で指定しちゃうこともできます。途中から変えるのも有り。
[% foo.bar %]... [% TAGS html %] ここからは HTML コメントスタイル <!-- foo.bar --> <!-- TAGS default --> ここから戻る [% foo.bar %]...TT タグ前後の改行(POD)
『こんにちは [% IF name %] 、[% name %]さん [% END # IF %] 。』は
『こんにちは 、とみたさん 。』と出力されます。HTML の場合改行は問題なかったりしますが、以下のように TT タグの内側にハイフンをつけるとその方向の空文字が消え、
『こんにちは、とみたさん。』とつながって出ます。『こんにちは [%- IF name %] 、[% name %]さん [% END # IF -%] 。』変数へのアクセス(POD)
$tt->process('test.tt', { name => 'とみた', hatena => 'tomi-ru', skill => ['迷子', '寝坊'], favorite => { game => 'ドラクエ3', movie => 'インディペンデンスデイ', }, birthday => DateTime->new(year=>'1979', month=>'1', day=>'4'), hello => sub { my $word = shift; return "こんにちは $word。"; }, });というような形で変数があった場合、以下のようにアクセスします。Ruby のように ドットでたどります。TT は HTML で使うことが多いので、
->でなくドットでたどれるのはいい感じです。<dl> <dt>名前:</dt> <dd>[% name %]</dd> <dt>特技:</dt> <dd>[% skill.0 %], [% skill.1 %]</dd> <dt>好きな映画: </dt> <dd>[% favorite.movie %]</dd> <dt>誕生日:</dt> <dd>[% birthday.strftime('%Y年%m月%d日') %]</dd> <dt>ひとこと:</dt> <dd>[% hello("みなさん") %]</dd> <dt>はてな:</dt> <dd>[% "http://d.hatena.ne.jp/" _ hatena _ "/about" %]</dd> </dl>なお、_で始まる変数は変数側では参照できません。
-
+ - * / % and or not ?:は Perl と同じに使えます。文字列の連結だけ.じゃなくて_になります。比較演算子も== != < <= > >= && || ! and or notが Perl と同じに使えます。[% 30 - age <= 0 ? "もう" _ age : "まだ" _ age %] 仮想メソッド(POD)
どんな変数にもくっつけることができるスーパーオブジェクト関数のようなものがたくさん用意されていて、文字列操作が多いテンプレートまわりで役立ちます。よくできた言語になっています。以下は一例です。
[% date = "2008-01-01"; date.remove('^200[0-9]-'); # 01-01 date.replace('-0?', '/'); # 2008/1/1 date.split('-').join('/'); # 2008/01/01 date.search('^2008'); # true(1) m = date.match('(d+)-(d+)-(d+)'); m.0; # 2008 abc = ["z", "a", "a", "b", "c"]; abc.size; # 5 abc.unique.sort.join('/'); # a/b/c/z map = { one => 1, two => 2, three => 3, }; map.keys; # ["one", "two", "three"] ※順不同 map.values.sort.join('-'); # 1-2-3 %]例のように、
[% %]内に複数の文を入れるときは、セミコロンで区切ります。また、# 以降はコメントとなります。フィルタ(POD)
HTMLエスケープなどの最終処理用に、
|を使ったフィルタというシンタックスがあり、デフォルトでよく使うものが複数用意されています。[% title | html %]htmlやuri、改行を<br />に変えるhtml_line_breakなんかを良く使います。HTML エスケープして改行を<br />にする場合は以下のようにつなげます。<p>[% daily.body | html | html_line_break %]</p>IF文など制御構文(POD)
TT の制御構文キーワードは大文字で、Perl とは少し違います。どれも
ENDで終われるので、ネストした時にわかるよう、END # IFとかするとわかりやすいです。-
[% IF a == "A" and (b <= 3 and c > 4) %] foo [% ELSIF not d %] bar [% ELSE %] gee [% END # IF %] -
[% SWITCH x %] [% CASE "A" %] Aの時(breakはいらないです) [% CASE ["B", "C"] %] BかCの時 [% CASE %] 指定しないと、それ以外 [% END # SWITCH %] リストと FOREACH
[% var = ["a", "b", "c", "d", "e"] %] <ul> [% FOREACH v = var; NEXT IF v == "b"; LAST IF v == "e"; %] <li>[% v %]</li> [% END # FOREACH %] </ul>ハッシュと FOREACH
[% unit = { ja => 'yen', us => 'dollar', eu => 'euro', } %] [% FOREACH u IN unit %] * [% u.key %]: [% u.value %] [% END # FOREACH %]ハッシュのリストと FOREACH
[% rank = [ { name => 'usagi', color => 'white' }, { name => 'kame', color => 'green' }, ]; %] [% FOREACH r IN rank %] No.[% loop.count %] [% r.name %] ([% r.color %]) [% END # FOREACH %]※ loop というのは FOREACH 内部で利用できる特別な変数で、count 以外に index などがあります。どの FOREACH 内でも利用できます。
-
[% WHILE (row = rs.next) %] [% row.id %] - [% row.name %] [% END # WHILE %]
-
シンタックス 発展編
WRAPPER(POD)
HTML出力に TT を使う場合、共通ヘッダやフッタ部分などをまとめたいという話が出てくると思います。もちろん普通の INCLUDE のしくみもありますが、WRAPPER という仕組みが便利です。まず、
<html> <head> <title>[% title _ " - " | html IF title %]<title> [% IF jquery %] <script type="text/javascript" src="jquery.js"></script> [% END %] </head> <body> [% content %] </body> </html>という
[% content %]を含むテンプレートを、例えば wrapper.tt という名前で用意しておきます。そして、test.tt を WRAPPER ディレクティブで囲みます。[% WRAPPER wrapper.tt; title = "目次" jquery = 1 %] コンテンツ [% END # WRAPPER %]これで、
$tt->process('test.tt')すると、WRAPPER ディレクティブ内が wrapper.tt の [% contents %] のところに埋め込まれます。コンテンツの上下にヘッダとフッタを2回 INCLUDE するよりわかりやすいです。ラッパーの方でも呼び出し元テンプレートの変数を参照できますので、例のようにタイトルとかを渡すと良いです。ちなみに、FOREACH の中の loop のように、TT が用意する特別な変数があって、template と component というのがあります。
[% template.name %]にはラッパーした側 された側 関係なく$tt->processされた大元のファイル名が入っています。[% component.name %]にはラッパーした側 された側 関係なく、自分のファイル名が入っています。
META で template に簡単な変数を入れることもでき、タイトル名などはここに入れて渡すのを好む人もいるようです。
USE プラグイン(POD)
それでも不足がある場合は、プラグインで強化することができます。たとえば
Template::Plugin::Dumper を使うと TT 内で Dumper できます。プラグインは以下のように USE 構文を使い Template::Plugin:: 以降を指定します。
[% USE Dumper %] [% Dumper.dump(template) %]
Dumper など TT に同梱のプラグインもけっこうありますが、 ほかにもたくさんのプラグインが Template::Plugin:: 名前空間にあります。TT は人気があるので、かゆいところに手がとどくのが揃っているわけです。たとえば、
T::P::Comma は、数値をカンマ区切りするフィルタを追加してくれます。10,000 とかになります。
[% USE Comma %] ¥ [% yen | comma %]T::P::Clickable という、URL ぽいものを
<a>タグでリンクにしてくれるフィルタもあります。[% USE Clickable %] [% entry | html | clickable %]T::P::JapanesePrefectures は、よくある都道府県
<select>を作るのを楽できます。[% USE prefs = JapanesePrefectures('utf-8') %] <select name="pref"> <option value="">選択してください</option> [% FOR p IN prefs.prefectures %] <option value="[% p.name | id %]">[% p.name %]</option> [% END %] </select>

