モダンPerlの世界へようこそ

第40回Text::MicroTemplate:得意分野なんだからPerlを使えばいいじゃない、という方に

テキストの整形はPerlの基本

Perlは「Practical Extraction and Report Language」とも呼ばれるくらいで、正規表現などによる情報抽出機能と並んで、レポートの形を整えて出力する機能はPerlの根幹をなす部分といえます。もちろんそのもっとも原始的な形は二重引用符でくくられた文字列のなかにそのまま変数を埋め込むものです。

print "This report is created by $author.";

もう少しこったことをしたければ、Cから受け継いだprintf系の構文を使えばよいでしょう。

printf "This report is created on %04d/%02d/%02d.", $year, $month, $day;

優先順位の都合でうまく埋め込めなかったり、改行などを含む長い文字列を出力する場合は、適当なところで区切って、ドットで結合したりカンマでつなげたり(念のため、単に出力するだけならカンマで区切るほうがかなり速くなります⁠⁠、

use Time::Piece;
print "This report is created by $author",
      "on ".Time::Piece->new->ymd.".\n",

あるいはシェルから受け継いだヒアドキュメントを使う手もあるのでした。

printf <<"END", $year, $month, $day;
This report is created by $author,
on %04d-%02d-%02d.
END

また、連載第31回でも紹介したように、いまとなっては多くの問題を抱えているものの、古くからのPerlユーザのなかにはいまなおFORTRANから借りてきたformatを愛用している人もいるようです。

format STDOUT = 
This report is created by @*,
$author
on @*-@*-@*.
$year, $month, $day
.

write;

ただし、これらの組み込み関数を利用した整形・出力は、いずれもあまり再利用性は高くありません。また、見栄えの調整をするにもプログラミングの知識が必要になるため、書式を複数のスクリプトで使い回したり、プログラミングが専門ではない人に作業に委託するような分野では、もう少し融通の利くテンプレート処理用のモジュールを利用するのがふつうです。

例によって多様性を重視するPerlの世界では有名どころだけでも片手では数えられないだけのテンプレートモジュールがありますが、代表的なものを大別すると、1)生のPerlコードをテンプレート内に埋め込めるようになっているものと、2)独自のタグ言語が用意されているものに二分できます。また、後者については、2-a)テンプレート内でオブジェクトのメソッドなどを呼び出すことはできないものと、2-b)メソッド呼び出しが可能なものにわけることもできます。また、厳密にはテンプレートエンジンとはいえないものの、3)特定のタグを出力するヘルパー関数を多用してテンプレート(もどき)を構成するものも同列に扱ってもよいかもしれません。これらはそれぞれ得意な分野も異なりますし、モジュールの選択によってはコードの書き方ががらりと変わってしまうこともありえるので、できればそれぞれの特徴をおさえた上で自分の用途にあったものを選択したいものです。

今回からは何度かにわけてテンプレートまわりのモジュールについてまとめてみます。初回の今回は、テンプレートに生のPerlコードを埋め込めるタイプのモジュールを取り上げます。

Text::Template

この系統のモジュールとしてはまずHigher Order Perlなどの著作で知られるマーク・ジェイソン・ドミナス(Mark Jason Dominus)氏が1995年12月にリリースしたText::Templateがあげられます。

これはCPANに登録されているPerlのテンプレート関連モジュールとしては最古のものなので、いまとなってはさすがに古めかしさを禁じ得ませんが、そのドキュメントに書いてある「独自のテンプレートタグは言語としては不完全なものだから、作者が想定していないことをしようとしたときに困る」とか「Perlがあるのに、どうしてわざわざほかのミニ言語を学ぶ必要があるのだろう」という問題提起はいまでも十分通用するものです。

Text::Templateの機能自体はヒアドキュメントに毛が生えたようなもので、テンプレート内部で一部の要素を条件文やループでくくるような記述はできません。ただし、ヒアドキュメントが@{[...]}などの熟語を使うことで任意の式を埋め込めるように、Text::Templateも、最終的に埋め込む値を返しさえすれば、ブロックの中に任意の文を埋め込むことはできます。サンプルには、比較のため、ヒアドキュメントの例もいっしょに並べてみました。ヒアドキュメントの@{[...]}の中にはふつうセミコロンを含む文は入れられませんが、ここではdo { } でくくることで対応しています。

ヒアドキュメントの場合
use strict;
use warnings;

my $author = 'me';
my @items = (1, 2, 3);

print <<"END";
This report is created by ${author}.
@{[ do {
  my $list = '';
  for (@items) { $list .= "item $_\n" }
  $list; # 最終的にこの値が埋め込まれます
}]}
END
Text::Templateの場合
use strict;
use warnings;
use Text::Template;

my $author = 'me';
my @items = (1, 2, 3);

my $template = <<'END';  # ここの部分は外部のファイルに切り出せます
This report is created by {$author}.
{
  my $list = '';
  for (@items) { $list .= "item $_\n" }
  $list; # 最終的にこの値が埋め込まれます
}
END

print Text::Template->new(TYPE => 'STRING', SOURCE => $template)
        ->fill_in(HASH => { items => \@items, author => $author });

この例ではText::Templateに渡すテンプレートもヒアドキュメントで作成しているため違いが見えづらくなっていますが(⁠⁠END」をくくるクォーテーションの違いがポイントです⁠⁠、実際には外部ファイルに切り出したテンプレートでもヒアドキュメントとほとんど同じ書き方ができるのがText::Templateの長所といえます。また、弱点としては、ヒアドキュメントのレベルでは対応がむずかしいループの処理やエスケープ処理などを自前で実装しなければならないことなどがあげられます。

HTML::Mason

この系統の第二陣に属するものとしては、1997年にリリースされたHTML::Embperlや1998年リリースのHTML::Masonなどがあげられます。

これらはPHPやASPの黎明期[1]に開発が進められたという時代背景も手伝って、単なるテンプレートエンジンというより、ウェブアプリケーションフレームワークとしての要素が濃くなっていますが(両者ともmod_perl環境下で使うことが大前提となっています⁠⁠、その気になれば単独でテンプレートエンジンとして使うこともできます。

ここではAmazon.com[2]や、CPANのバグトラッカーとして有名なRTなどでの採用実績があるHTML::Masonの例のみ紹介します。

HTML::Masonではテンプレートはファイルに保存することが前提となっているため、まずはカレントディレクトリにtemplate.tmplというテンプレートを用意します。初期化や引数の受け渡しなどはふつう専用のブロックを利用して行います。

<%args>
  $author
  @items
</%args>
This report is created by <% $author %>.
% for (@items) {
item <% $_ %>
% }

続いて、呼び出し側のコードを用意します。HTML::Masonを単体で使う場合は、mod_perlハンドラなどが内部的に利用しているHTML::Mason::Interpというモジュールを使うのが定石ですが、必要に応じてさらに内側にあるHTML::Mason::Compilerを利用してもよいでしょう。

use strict;
use warnings;
use HTML::Mason;
use File::Spec;

my $interp = HTML::Mason::Interp->new(
  comp_root  => File::Spec->rel2abs('.'),
  out_method => \my $out,

  # 自動でHTMLエスケープしたい場合は下記も
  # default_escape_flags => ['h'], 
);

$interp->exec('/template.tmpl',
  author => 'me',
  items  => [1, 2, 3],
);
print $out;

HTML::Masonにはほかにもさまざまな機能が用意されています。詳しくはHTML::Masonのサイトにドキュメントがまとまっているほか、O'Reilly社から刊行されたEmbedding Perl in HTML with Masonという本の全文がオンラインでも公開されています[3]⁠。

なお、HTML::Masonではインストールの敷居が高いという人は、Text::MicroMasonという選択肢もあります。こちらはMason形式のテンプレートだけでなく、設定に応じてEmbperlやText::Template、あるいはApache::ASP風のテンプレートも利用できるので、そのまま使う場合はもとより、MasonやEmbperlで書かれた古いアプリケーションをPlackなどの環境に移行する場合にも便利かもしれません。

use strict;
use warnings;
use Text::MicroMason;

my $template =<<'END';
This report is created by <% $author %>.
% for (@items) {
item <% $_ %>
% }
<%args>
  $author
  @items
</%args>
END

my $mason = Text::MicroMason->new(
  -Filters,
  default_filters => 'h',
);

print $mason->execute(text=>$template,
  author => 'me <[email protected]>',
  items  => [1, 2, 3],
);

また、PSGI/Plackの登場によってmod_perlと密接に結びついている必要性が薄まったこともあって、2011年2月にはmod_perlとの連携部分を外してMoose化された新しいバージョンがMasonという名前でリリースされました。こちらはまだ評価の対象とするには新しすぎますが、興味のある方は追いかけてみてください。

Text::MicroTemplate

三番目のグループに属するのは、MojoliciousのテンプレートエンジンであるMojo::Templateと、そこから派生したText::MicroTemplate(いずれも2008年)の一族です。

Mojoliciousについては第22回でも取り上げましたし、Text::MicroTemplateの方はtypesterこと村瀬大輔氏のArkや、宮川達彦氏のTatsumakiなどでも標準のテンプレートになっているため、利用したことのある方も少なからずおられることでしょう。

両者の大きな違いは、単体で使えるかどうかと、埋め込む文字列が標準でHTMLエスケープされるかどうかにあります(Text::MicroTemplateはウェブでの利用が前提となっているため、基本的にはすべてがHTMLエスケープされるようになっていますが、Mojo::TemplateはもともとMojoのひな形を作成するなど、ウェブ以外の用途も視野に入れてつくられているため、デフォルトでは不要なエスケープはしないようになっています⁠⁠。ただし、最近ではMojo::Templateにもデフォルトでエスケープを有効にできるようなオプションが用意されていますし、逆に、最近のText::MicroTemplateには埋め込み時にHTMLエスケープしないという表記がなくなったため、生のHTMLを埋め込みたいときは一工夫する必要があります。また、Text::MicroTemplateのフォークはかなり早い時期に行われたため、Mojo::Templateにあとから追加されたいくつかのメソッドには(いまのところ)対応していません。

それぞれ簡単なサンプルを載せておきます。

Mojo::Templateの場合
use strict;
use warnings;
use Mojo::Template;

my $mt = Mojo::Template->new(auto_escape => 1);
print $mt->render(<<'END', 'me <[email protected]>', [1, 2, 3]);
% my ($author, $items) = @_;
This report is created by <%== $author %>.
% for (@$items) {
item <%= $_ %>
% }
END
Text::MicroTemplateの場合
use strict;
use warnings;
use Text::MicroTemplate 'encoded_string';

my $renderer = Text::MicroTemplate->new(template => <<'END')->build;
? my ($author, $items) = @_;
This report is created by <?= encoded_string($author) ?>.
? for (@$items) {
item <?= $_ ?>
? }
END

print $renderer->('me <[email protected]>', [1, 2, 3]);

なお、ここではあえて一手間かける例を紹介していますが、オプションを指定する必要がなかったり、一回こっきりの処理の場合は、build_mt、render_mtという関数を利用するともう少し簡潔に書くこともできます。

Text::MicroTemplateはテンプレートを文字列として渡すようになっていますが、ファイルからテンプレートを読み込みたい場合は、同梱されているText::MicroTemplate::Fileを使うと自前でファイルの読み込みを書く手間を省けます。

use strict;
use warnings;
use Text::MicroTemplate::File;

our $mt = Text::MicroTemplate::File->new;
print $mt->render_file('template.mt', 'me <[email protected]>', [1, 2, 3]);

ここであえて$mtをourでグローバル化しているのは、テンプレートの中から別のテンプレートを呼び出すときにinclude_pathなどの情報が保存されているText::MicroTemplate::Fileのオブジェクトを再利用できるようにするため。いささか場当たり的なやり方ですが、たとえばカレントディレクトリにtemplate.mtとして次のようなテンプレートを用意し、

? my ($author, $items) = @_
This report is created by <?= encoded_string($author) ?>.
? for (@$items) {
item <?= $_ ?>
? }
?= $main::mt->render_file('footer.mt', $author);

footer.mtとしてこのようなテンプレートを用意すると、期待通りにfooter.mtの内容が埋め込まれた結果が表示されます(footer.mtの内容が二重にエスケープされるようなことはありません⁠⁠。

? my $author = shift;
<footer><?= $author ?></footer>

ここでは前からの例にならってあえて配列で引数を渡していますが、統一的に処理したければ、コンテキストオブジェクトなりハッシュリファレンスなりを使い回すようにしておけば、テンプレート間で引数の違いに悩まされることもなくなります。

use strict;
use warnings;
use Text::MicroTemplate::File;

my $context = {
  author => 'me <[email protected]>',
  items  => [1, 2, 3],
};

our $mt = Text::MicroTemplate::File->new;
print $mt->render_file('template.mt', $context);

# あとはすべてのテンプレートの先頭に「?= my $context = shift;」を書くだけ

また、コンテキストからText::MicroTemplate::Fileのインスタンス(や、それを含むビュークラスのインスタンス)にアクセスできるようにしておけば、$mtをグローバルにする必要もなくなります。

use strict;
use warnings;
use Text::MicroTemplate::File;

my $mt = Text::MicroTemplate::File->new;

my $context = {
  author => 'me <[email protected]>',
  items  => [1, 2, 3],
  mt     => $mt,
};

print $mt->render_file('template.mt', $context);

# template.mtでは ?= $context->{mt}->render_file(...)のようにします

なお、サンプルは掲載しませんが、村瀬氏作のText::MicroTemplate::Extendedを使うと、テンプレートの一部分を継承して再利用することもできます。また、Mojo::Templateの方はテンプレート間の継承だけでなく、テンプレート内での再利用もできるようになっています(詳しくはMojo::TemplateやMojolicious::Guides::Renderingをご覧ください⁠⁠。

これらのテンプレートは、Perlを解するプログラマにとっては非常に便利なものです。一方で、自由度が高すぎるためにさまざまな問題を起こす可能性もはらんでいます。次回はその問題と、別のアプローチからの対策についてまとめていきます。

おすすめ記事

記事・ニュース一覧