Perl Hackers Hub

第5回Xslate 次世代テンプレートエンジン(2)

Xslateのインストール

それではいよいよXslateの解説に入ります。まず、お好みのCPANクライアント[3]でText::Xslateをインストールしてください。インストールの際にCコンパイラがあれば高速なXSのバックエンドが使われますが、pure Perlのバックエンドも存在するため、Cコンパイラのない環境でも使用することはできます。

Xslateの基本的な使い方

基本的な使い方は、すでに紹介したとおり、Text::Xslateクラスのインスタンスを作成し、render()ないしrender_string()メソッドでレンダリングするだけです。必要であればコンストラクタにオプションを渡してください。有効なオプションは表1のとおりです。リスト8では、pathにハッシュリファレンスを渡しています。また、headerオプションとfooterオプションを使ってヘッダとフッタを自動的に結合しています。

表1 Text::Xslate->new()の主なオプション
名前デフォルト値説明
cache10:キャッシュ[1]を行わない
1:キャッシュが古くなければ使用する
2:可能な限りキャッシュを使用する
cache_dir※2参照キャッシュを置くディレクトリ
typehtmlHTMLでないテキストを生成するときは、"text"にして自動エスケープを抑制する
footer[ ]自動的に追加されるフッタのリスト
function{ }テンプレート内にエクスポートする関数
header[ ]自動的に追加されるヘッダのリスト
input_layer:utf8openに渡すオプション:encoding(cp932)など
line_start:1行テンプレートコードの開始文字列[3]
module[ ]関数をインポートするモジュール
path["."]テンプレートファイルを検索する場所。ディレクトリ名ないしハッシュリファレンスのリスト
syntaxKolonテンプレートの構文
tag_start<:テンプレートタグの開始文字列[3]
tag_end:>テンプレートタグの終端文字列[3]
verbose10:警告をなるべく発しない
1:明らかな誤りのみ警告を発する
2:誤りの可能性をすべて警告する

※1)Xslateではテンプレートのコンパイル結果をキャッシュできる

※2)デフォルトは$ENV{HOME}/.xslate_cacheだが、$ENV{HOME}が書き込み可能でなければほかのディレクトリも試す

※3) テンプレートの構文によってデフォルトは変わる。これらはデフォルト構文Kolonのデフォルト値

リスト8 オプションを指定する(hello_xslate2.pl)
use strict;
use Text::Xslate;
my %vpath = (
    header => "- - - - - - - - - - -\n",
    footer => "- - - - - - - - - - -\n",
    hello  => "Hello, Xslate world!\n",
);
my $tx = Text::Xslate->new(
    path   => \%vpath,
    header => ['header'],
    footer => ['footer'],
);
print $tx->render('hello');
# =>
# - - - - - - - - - - -
# Hello, Xslate world!
# - - - - - - - - - - -

以降でそれぞれの構文について説明していきますが、テンプレート式については構文にかかわらずほぼ共通です。したがってテンプレート式については、特に断りのない限りすべての構文に当てはまります。

Kolon Perl 6ライクな独自構文

Xslateは複数の構文をサポートしており、デフォルトのテンプレート構文はKolonというPerl 6ライクな独自言語です。これはすでにいくつかコードを出していますが、タグ<: ... :>で囲ったインラインコードと、:で始まる1行コードを埋め込むことができます。コメントはPerlと同じく#から行末までです。

テンプレート式

テンプレート式はPerl 5をベースにTT2やPerl 6からいくつかの機能を取り込んだ形になっています。

単純な変数の参照は、Perlのスカラ値と同じくシジル[4]$と識別子を連結したものです。ただしPerlと異なり、シジルとして@%は使いませんリスト9 (1)⁠。

リスト9 テンプレート式
(1) 変数の参照
<: $foo :>

(2) フィールドの参照
<: $obj.bar     # Perl: $obj->bar() :>
<: $hashref.bar # Perl: $hashref->{bar} :>
<: $arrayref.0  # Perl: $arrayref->[0] :>

(3) フィールドの参照(添え字の使用)
<: $value["baz"] # $value.bazと同じ :>
<: $value[0]     # $value.0と同じ :>

(4) メソッドの呼び出し
<: $obj.method(foo => 'bar') :>
<: $obj.method({ foo => [42] }) :>

(5) メソッドの呼び出し(組み込みメソッド)
<: $arrayref.sort(-> $a, $b { $a <=> $b }).join(" ") :>
<: $hashref.keys().join(", ") :>

(6) フィールドの参照と組み込みメソッドの呼び出しの区別
<: $hashref.keys   # "keys"フィールドを参照する :>
<: $hashref.keys() # ハッシュキーのリストを返す :>

(7) Perl 5由来の演算子
<: defined $a
    ? "a is defined"
    : "a is undefined" :>
<: $a == "foo"     # $aが"foo"と等しいかどうか :>
<: $a == nil       # $aが未定義かどうか(defined $aと同じ):>
<: $v != ($i + 10) # 複雑な演算も可能 :>

(8) Perl 6由来の演算子
<: "foo" ~ "bar" # 文字列連結 :>
<: 10 min 20     # 小さいほう: 10 :>
<: [10, 20, 30].reduce(-> $a, $b { $a max $b }) # 30 :>

(9) defined-or演算子
:# $a定義がされていれば$a、未定義ならば"default"
<: $a // "default" :>

(10) フィルタの適用
<: $a | dump  # dump($a) と同じ :>
<: $x | f | g # g(f($x)) と同じ :>

フィールドアクセスはTT2に似ており、ドット演算子を使います。このとき、参照する値がオブジェクトであればアクセサメソッドを呼び出し、ハッシュや配列のリファレンスであれば要素を参照します(2)⁠。

フィールドアクセスに任意の式を使いたいときは、[]演算子を使います。Perlと異なり、ハッシュに対するアクセスでも{}は使いません(3)⁠。

オブジェクトに対しては任意のメソッドを呼び出すことができます。このとき、Perlと同様にハッシュリファレンスや配列リファレンスのリテラルを書くこともできます(4)⁠。

オブジェクトでない値に対してメソッドを起動するとオートボックス[5]が働きます(5)⁠。組み込みメソッドについてはperldoc Text::Xslate::Manual::Builtinに一覧があります。なお、フィールドの参照とあいまいになることを避けるため、オートボックスによるメソッド呼び出しには括弧を必須にしています(6)⁠。

テンプレート式では演算も行えます。演算子はPerl 5のものがほぼそのまま使えます(7)⁠。また、Perl 6由来の演算子もいくつかあります(8)⁠。機能や優先順位は元になった演算子とほぼ同じです。ただし比較演算子==は少し特殊で、nil[6]を特別扱いし、文字列にも適用できます。

Perl 5.10から使用可能となったdefined-or演算子//は、たとえXslateがPerl 5.8上で動いているとしても使用できます(9)⁠。

TT2由来のフィルタ演算子|も使えます。これは関数呼び出しのシンタックスシュガー(糖衣構文)です(10)⁠。

制御文

制御文はif、given-when、for、whileをサポートしています。これらはPerl 6と同様に括弧を使わなくて済むようになっています。また、for、given、whileは、リスト10のようにポインティ構文-> $varでトピック変数[7]を指定できますが、これもPerl 6から拝借したものです。

リスト10 制御文(statements.tx)
:# ifの括弧は不要
:if $count >= 10 {
    $countは10以上です。
: }

:# $itを使わないならトピック変数は省略できる
:given $a -> $it {
:   when "foo" {
        $aは"foo"です。
:    }
:    when $it == "bar" or $it == "baz" {
        $aは"bar"ないし"baz"です。
:    }
:    default {
        $aは"foo"でも"bar"でも"baz"でもありません。
:    }
:}

:# whileはイテレータに対して使用可能
:# 以下はwhile(defined(my $row = $dbh.fetch())) と同じ
: while defined $dbh.fetch() -> $row {
    <: $row.join(" ") :>
: }

forループもPerl 6風です。また、これはKolonの独自仕様ですが、リスト11のようにループコンテキストにアクセスするための特殊な記法を用意しています。なお、forループで使用できるのは配列リファレンスだけなので、配列以外の値は配列リファレンスに変換する必要があります。ハッシュリファレンスであれば、keys()やvalues()などの組み込みメソッドが配列のリファレンスを返すのでこの用途に使えます。

リスト11 forループ(for.tx)
:# forではトピック変数の省略はできない
: for $arrayref -> $item {
    <: $item # 一つ一つの要素 :>
    <: $~item # ループカウンタ(0 origin):>
    <: $~item.count # ループカウンタ(1 origin):>
    <: $~item.max_index # Perlの $#{$arrayref} と同じ :>
    <: $~item.is_first # $~item == 0 :>
    <: $~item.is_last # $~item == $~item.max_index :>
    <: $~item.cycle("even", "odd")
                           # even -> odd -> even -> ... :>
: }

テンプレートカスケーディング

Kolonはテンプレートカスケーディング(以下カスケード)という機能を持っています。カスケードは、複数のテンプレートファイルを組み合わせて1つの出力を得る機能です。これはちょうど、カスケーディングスタイルシート(CSS)のように、ベースとなるテンプレートにコンポーネントを重ね合わせて表現を追加していくことから名付けました[8]⁠。

TT2のユーザであればWRAPPERという機能をご存じかもしれません。これは、ベースとなるテンプレートで基本的な枠組みを作り、サブテンプレートでその枠組みの内容を埋めるというやり方でテンプレートを構築する機能です。これは基本的にヘッダ・フッタとページの内容を分離するための機能です。ヘッダとフッタを同じファイルに入れることができるため、HTMLタグの整合性を保つことができ、ほかのテンプレートエンジンでよく見られるincludeで分割するやり方よりもメンテナンスしやすいと言われています。

カスケードはTT2のWRAPPERのスーパーセットであり、WRAPPERで実現できることはカスケードでも実現できます[9]⁠。

Template Toolkit 2のWRAPPER機能

それではまず、TT2でWRAPPERを使ってみましょう。

WRAPPERを使うには基本となるベーステンプレートを用意しますリスト12⁠。このファイルから見ると、contentキーワードにWRAPPERの呼び出し元(サブテンプレート)のブロックが入ります。そのほかのテンプレート変数は通常どおり参照できます。

リスト12 TT2のベーステンプレート(base.tt)
<!doctype html>
<html>
<head><title>[% title %]</title></head>
<body>
[% content -%]
</body>
</html>

次に、ページ内容を入れたサブテンプレートを用意し、WRAPPERディレクティブを使いますリスト13⁠。このファイルから見ると、WRAPPERディレクティブの行がヘッダに、ENDキーワードの行がフッタに置き換わる、と見ることができます。また、WRAPPERと共にWITHキーワードで新たなテンプレート変数を導入できます[10]⁠。

リスト13 TT2のサブテンプレート(content.tt)
[% WRAPPER "base.tt" WITH title = "Welcome to my site!" -%]
<p>Hello, world!</p>
[% END -%]

これを処理すると、出力はリスト14のようになります。

リスト14 WRAPPERの出力例(output.html)
<!doctype html>
<html>
<head><title>Welcome to my site!</title></head>
<body>
<p>Hello, world!</p>
</body>
</html>

Xslateのテンプレートカスケーディング

それでは、このWRAPPER機能をカスケードで実装してみます。

まずベーステンプレートでは、content変数を参照する代わりに、blockキーワードで置き換え可能なブロックを定義しますリスト15⁠。WRAPPERと異なり、ブロックの名前や数には制限がありません。リスト15ではTT2に合わせてtitleをテンプレート変数で表現していますが、これもブロックにすることができます。

リスト15 Xslateのベーステンプレート(base.tx)
<!doctype html>
<html>
<head><title><: $title :></title></head>
<body>
: block content -> { "default content" }
</body>
</html>

そしてサブテンプレートでは、ブロック修飾子キーワードを使ってベーステンプレートのブロックを修飾しますリスト16⁠。ブロック修飾子はoverride、around、before、afterがあり、overrideとaroundは元のブロックを置き換えるために[11]⁠、beforeとafterは元のブロックの前後に内容を追加するために使います。

リスト16 Xslateのサブテンプレート(content.tx)
: cascade base { title => 'Welcome to my site!' };

: override content -> {
<p>Hello, world!</p>
: }

出力は、WRAPPER版のリスト14とまったく同じです。

カスケードがWRAPPERより優れている点は、置換ブロックが1つに制限されないこと、置換ブロックにデフォルト値を設定できること、ベーステンプレートの置き換えブロックをサブテンプレートから参照できることです。

おすすめ記事

記事・ニュース一覧