« 関数のデバッグトレース | メイン | ドラッグアンドドロップで並べ替え(Rails + Ajax) »

テンプレートの記述もPerlで - Template::Declareを使うはてなブックマークに追加 livedoorクリップに追加 Yahoo!ブックマークに追加 del.icio.usに追加 イザ!ブックマーク ニフティクリップに追加

こんにちは、Perl担当の西山です。

これまでJifty::DBI、Template::WWW::DeclareとWebフレームワークJiftyの周辺モジュールについて書きましたが、今回はViewに関わる部分であるTemplate::Declareというモジュールを取り上げようと思います。

特徴

Template::Declare(以下TD)は、HTML::MASONやTemplate::Toolkitなどに並ぶテンプレートエンジンですが、他のものには無い特徴を持っています。
  1. すべてのテンプレートは100% Pure Perlで記述する
  2. 宣言的なシンタックス
  3. Mixinや継承などのオブジェクト指向の機能を利用可能(Perlなので)
  4. HTML/XUL/RDF/XMLなどの形式に対応(その他の形式も拡張可能)

簡単な例でPerlではデファクトのテンプレートエンジンであるTemplate::Toolkit(以下TT)と比較してみましょう。

配列データを元にHTMLでリストを出力するような場合、
TTでは以下のように書くと思います。

<ul>
[% FOREACH a IN array %]
    <li>[% a %]</li>
[% END %]
</ul>

TTに限らず、HTMLなどのデザインを主としてそれに対してロジックを組み込むようなタイプのテンプレートエンジンでは、記法の多少の違いはあれど大体上記のような書き方になるのではないでしょうか。

一方TDでは以下のようになります。

template list => sub {
    ul {
        for my $a (@array) {
            li { $a }
        }
    }
};

使い方は・・・と思いましたが、結構見たままですよね。
"list"という名前でHTMLのリスト構造をテンプレートとして定義していて、
その後は以下のようにshow関数を使って名前でテンプレートを呼び出すことができます。

show 'list'

HTMLのタグはすべて同名の関数として用意されていて、通常のPerlコードと同じように使えます。
※Perlにもともとtrという名前の演算子が用意されている関係でtr/tdタグのみそれぞれrow/cellという別の名称の関数にマッピングされています。

TTなど他のテンプレートエンジンはロジック部分にそれぞれ(しかも結構独特な)文法を用意していて、メインの言語とは別に覚えないといけないですが、
TDならば変数への値の格納やループ処理はそのままPerlの文法で書けます。
外部モジュールを使う場合もいちいちテンプレート用のプラグインを作る必要も無いです。


雑感

デザインとロジックは分離すべきという一般的な原則からするとギョッとするような見た目ですが、 個人的な感覚でいうと意外に書いていてすっきり書ける印象がありました。

HTMLのコンテキストの中に[% ... %]や<% ... %>などの記号で分断されてソースコードが挿入されるのはやはり少々気持ち悪いものがありますが、その点TDでは一貫してPerlのコンテキストで書けますからね:)

通常のPerlプログラムと同様の扱いで構造化・再利用することができるようになるのも魅力だと思います。

また、レイアウト情報を部品化してテンプレートにする感覚はXSLにも通じるところがあると思います。
ただXSLは無理やり感のある冗長なタグによる記述になるので、マークアップ言語でそんなにがんばるならいっそPerlで・・・という気にもなってきます。


実際のところデザイナー/HTMLコーダーとどう協業していくかという課題はありますが・・・、以下のようなケースでTDは使い道があるように思いました。

  1. AHAHのように部品単位でHTMLを出力する場合
  2. 部品の集合で画面が構成されている場合
  3. Webアプリケーションで表示上のロジックが複雑になる場合
  4. 少人数で開発していて、クライアント/サーバ両サイドについて一貫した設計が行える場合

具体例

最後に具体例としてもうすこしまとまったサンプルスクリプトを書いてみました。 Tkrb::Template::Baseモジュールでページ共通の構造やHTMLのヘッダーなどを定義しています。 それを拡張する形でTkrb::Template::IndexでTOPページに当たるHTMLの定義を、 Tkrb::Template::ProfileでプロフィールページのHTMLの定義を記述してます。

最終的に整形されたHTMLを取得するには、mainパッケージにあるように
Template::Declareのinitメソッドにテンプレートを記述したモジュールを指定して初期化した上で
showメソッドでテンプレート名を指定すると文字列でHTMLが返却されます。

#!/usr/bin/env perl
use strict;
use warnings;

package Tkrb::Template::Base;
use Template::Declare::Tags;
use base 'Template::Declare';

private template base => sub {
    my ($self, $center) = @_;

    html {
        head { show 'head' }
        body {
            div { attr { class => 'left' } }
            div { attr { class => 'center' }
                show $center
            }
            div { attr { class => 'right' }
                show 'sidemenu'
            }
        }
    };
};

private template head => sub {
    meta { attr {
            content => 'text/html; charset=utf-8',
            'http-equiv' => 'Content-Type',
        }}
    title { 't*k*r*b' }
};

private template sidemenu => sub {
    ul {
        li { a { attr { href => '/' } 'Top' } }
        li { a { attr { href => '/profile' } 'Profile' } }
    }
};

package Tkrb::Template::Index;
use Template::Declare::Tags;
use base 'Tkrb::Template::Base';

template index => sub { show 'base', 'index/center' };
private template 'index/center' => sub {
    h1 { "TOP PAGE" };
    p { b { "contents..."} }
};


package Tkrb::Template::Profile;
use Template::Declare::Tags;
use base 'Tkrb::Template::Base';

my %data = (
    name => 'つくるぶ太郎',
    email => 'taro@tkrb.jp',
    tel => '090-xxxx-xxxx',
);

template profile => sub { show 'base', 'profile/center' };
private template 'profile/center' => sub {
    h1 { "$data{name} さんのプロフィール" }
    table {
        for my $key (keys %data) {
            row {
                cell { $key }
                cell { $data{$key} }
            }
        }
    }
};

package main;
use Template::Declare;

Template::Declare->init(
    roots => [qw(Tkrb::Template::Index Tkrb::Template::Profile)],
);
print "### Top.\n";
print Template::Declare->show('index') . "\n\n";

print "### Profile.\n";
print Template::Declare->show('profile') . "\n\n";

まとめ

すべてをPerlコードで記述できるテンプレートエンジンであるTemplate::Declareモジュールを取り上げました。 Perlプログラムと同様の扱いでHTMLを構造化・再利用することができるようになるのが魅力だと思います。

さて、なんだか外堀を埋めるような感じでJiftyの構成部品であるJifty::DBI、Template::Declareを扱いましたが、
当然ながらこれらはJiftyとして使われることを念頭に開発されている為、個別に使っていても利用できない相乗機能があるようです。
次回はJifty本体について取り上げてみたいと思います。