2006-03-26

なるほどテンプレートっていろいろ考えなきゃいけないんだな

最近 ecmascript を(Web 以外の目的で)利用している。で、簡単なテンプレートみたいなのがほしいなと思ったんだけど、当然のように JavaScript で探すと HTML ベースで DOM をいじるテンプレートばっかりで目的に合わない。

じゃー作ったらいいかーと思って別なテンプレートを参考にしようと眺め始めたら、あぁこりゃ面倒くせえと思って早々に挫折。ただ考えたら目的はキーワードを置換できりゃいい程度なので、全然再発明する必要ないじゃんということで一安心。

安心したついでにのんびりテンプレートについて考えてたら、とりあえず今自分が知っている範囲ではテンプレートってのは3パターンくらいあるのかなと思った。

  1. ERB や 素の PHP のように言語とデータの関係を逆にしたもの
  2. amrita のようにテンプレートが対象とするデータの構造、言語の構造の両方に依存するもの
  3. Perl の HTML::Template, PHP の Smarty のように新たな文法を作るもの

順につらつらと書いてみよう。

1. 言語とデータの関係を逆にしたもの

JSP なんかもこういうものっぽい。(よく知らない。)このパターンでは print 文でデータを出力するという従来のプログラミングとは異なり、地の文がそのままデータとして出力され、特殊な記号で挟んだ範囲が言語の文と解釈される。ことが多い。

例えば PHP では

<p>ここは普通に HTML として出力される。</p>
<?php
if ( $cond ) {
?>
  <p>ここは $cond が成り立ったら出力される</p>
<?php
}
?>

てな感じに書く。

ちなみに PHP 界隈ではこれはメンテしにくくなるのであえて封じて 3 のようなテンプレートシステムを利用する派と、テンプレートなんて邪魔くせえ派に二分しているような印象を受ける。Ruby では ERB の受けは悪くはないが、これは binding という考え方と、「その場で即出力されるとは限らない」ことの二つの要因が大きいように思う。PHP の場合は <?php ?> で囲んでいない部分は HTML としてそのまま出力されるという問答無用さが初期の学習コストを大きく下げる役割を果たす一方、凝った処理の抽象化などに利用しにくいという問題も持つので、敬遠されることもあるのだ。

2. 出力に用いる言語、ロジックを書く言語の特性を利用したもの

amrita が分かりやすい例だと思うというか、amrita のことしか考えてないんだけど、

amrita のテンプレートサンプル

<ul>
  <li id=boyaki>
</ul>

amrita のコードサンプル

data = {
  :boyaki => [ '時間がない', '金がない', 'やる気がない' ]
}
tmpl = TemplateFile.new( templatefile )
tmpl.expand( STDOUT, data )

なんて具合。1 のパターンだと、このように同じ構造でいくつもデータを出力する場合に言語が持ってるループの機能を使わなくてはいけない。当然、記述量は多くなる。

しかし amrita の場合は言語の構造と HTML の構造を活かして配列はくり返し処理すればいいじゃん、と解釈して出力してくれる。楽。嬉しい。

ただし amirita は HTML にべったりなのでプレーンテキストには利用できない。(と思う。)例えば自動送信するメールのテンプレートとしては向いていない。(HTML メールにするのも手かな?)

3.新たな文法を作るもの

Web 向きのテンプレートシステムにはこのパターンが多いような気がする。基本的には

  • テンプレートデータの中の特定のパターンの部分に
  • 出力したいデータを展開する

という処理をくり返して最終的な出力データを生成するものである。例えば Perl の HTML::Template では

HTML::Template のテンプレートサンプル1

<TMPL_VAR NAME=dataname>

HTML::Template のコードサンプル1

$tmpl = HTML::Template->new(filename => 'template');
$tmpl->param( 'dataname' => 'REALDATA' );
print $tmpl->output;

という形で、dataname を囲んだタグの部分に REALDATA を出力できる。amirita みたいなもんじゃんと思うかもしれないけど、ループや条件分岐をさせようと思ったらテンプレートにもコードにも手を加えないといけない点が大きく違う。

HTML::Template のテンプレートサンプル2

<ul>
<TMPL_LOOP NAME=loopname>
  <li><TMPL_VAR NAME=dataname></li>
</TMPL_LOOP>
</ul>

HTML::Template のコードサンプル2

$tmpl = HTML::Template->new(filename => 'template' );
foreach ( @REALDATA ) {
  $item{'dataname'} = $_;
  push( @loopdata, \%item );
}
$tmpl->param( 'loopname' => \$loopdata );
print $tmpl->output();

PHP の Template_Sigma だと以下のようになる。

HTML_Template_Sigma テンプレートサンプル

<ul>
  <!-- BEGIN loopname -->
  <li>{dataname}</li>
  <!-- END loopname -->
</ul>

HTML_Template_Sigma コードサンプル

$tmpl = new HTML_Template_Sigma( $tmpldir );
$tmpl->loadTemplateFile( 'template' );
foreach ( $arr as $data ) {
  $tmpl->setVariable( array( 'dataname' => $data ) );
  $tmpl->parse( 'loopname' );
}

ただし、HTML::Template の場合は

HTML::Template のコードサンプル3

$this->param( 'loopname' => [ {'dataname' => REALDATA},
                              {'dataname' => REALDATA} ]
            );

のようにも書けるので、テンプレート前提にデータの形式を整えておけば amrita 的な使い方もできなくはない。

また Template_Sigma の場合でも wrapper を書いて amrita みたいにデータを放り投げるだけで済むようにできるので、そういうものを用意して使った方がいいかもしんない。

どれも一長一短だなぁ

で、いまのところこういう感想を抱いている。

1 は PHP のぐちゃぐちゃなものをいくつも見ていると絶対に採用したくない仕組みで、自分の場合はこの経験が ERB の印象も悪くしている。さらに変数に値がセットされてないとエラーになる(インスタンスの変数かメソッドがねーよと怒られる)など、テンプレートシステムだと思ってしまうと不親切でとっつきにくい。(上の Sigma の話と同じで wrapper 書けばいいんだけど。)しかしごく簡単な定形文への語句の埋め込みしか行わない場合は素早く十分に機能する。

逆に例えば HTML とコードを分離したいという要求には、(当然のことながら生 PHP や)ERB のようなアプローチは合致しない。ただこの「分離」にも何段階かあって、HTML もコードも両方自分で書くけど分かれていた方がメンテが楽ってレベルから、HTML をいじる人は HTML しか知らないしその人の環境では HTML をそのままブラウザで表示するしか確認の方法がない、というレベルまである。中間の段階に入る人は割と自由にテンプレートのような仕組みを選択できるけど、最後のレベルの場合はファイルが HTML として絶対に破綻しないという条件を満たす必要がある。いわゆるきちっとしたテンプレートシステムを使わずに(例えば自作の小さなツールなどで)これを実現するのは意外と難しい。(運用で回避という合わせ技もあるけど。)しかしこれを採用すると逆にコードが膨らんだり実行速度が落ちたりする。1

さて。

結論が何もない。なんか長文書くとこのパターンが多くねえか?

  1. まぁ言語のコードにコンパイルするとかキャッシュするとか工夫されていることが多いので、うまく使えば結構速度低下を抑えられそうな気がするんだけど。 

About

例によって個人のなんちゃらです

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot Rubygems jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Document SVG AsciiDoc Pandoc DocBook Develop Jekyll macOS Node.js Vite Heroku Transformer AI Data Cloud Wasm