さて前回はテンプレートエンジンの前史のようなレベルの話だったのですが、もう少しましなレベルにしましょう。
テンプレートエンジンを使用するということは、二つの側面があります。ひとつはプログラマが楽をすること(=生産性をあげること)、もうひとつは、サーバーの負荷を下げることです。
RubyのテンプレートエンジンのeRBや、PHPのテンプレートエンジンのSmartyのような本格的なテンプレートエンジンは、テンプレートをRubyやPHPのソースコードにコンパイルします。最近のWebサーバーでは、一度使用したプログラムを中間コードの状態で保持しておいて、実行する機能がありますので、よく使われるサービスであれば、サーバーの負荷を下げることができます。
個人的なツールであれば、生産性を上げるメリットのほうが大きくなります。
PHPのSmartyは良くできたテンプレートエンジンなのですが、開発者の生産性を上げるという側面から見ると、いくつか不満もあります。
(1) コントロールを生成する手段が弱い
MVC構造(モデル・ビュー・コントロール)の分離ということを考えるなら、入力部品の記述は本来テンプレート側に記述するべきなのだが、うまく記述できない。結果として、コントロール側のコードに書かざるを得ない。デザインとプログラムを分離できなくなっている。
(2) 拡張が簡単に書けない
一応insert構文というものがあり、指定した名称のPHP側のコードを呼び出してはくれるが、あまり使い勝手は良くない。出来ればクラス化するなどで拡張性のできる仕組みにしたい。
そこで、勉強をかねて自分で作ってみます。
個人で片手間に作るのですから、コード量を減らす必要があります。
Rubyは、レンタルサーバーで使えるところが増えてきていますが、まだまだです。その点、Perlはどこでも使えるというメリットがあります。もっともCPANに山のように、いいテンプレートエンジンがありますが、そこは勉強と割り切りましょう。
さて、作成に関して、いくつかの指針を決めましょう。
(1) 完璧を求めない
完璧を求めると、正規表現でちょこちょこと簡易パーサーを書くというようなことにはいかなります。あくまでも自分が相対的に楽をするためのものと割り切ります。
(2) コード側に極力HTMLの記述をしない
テンプレートエンジンの内部にHTMLコードが存在するのは、仕方が無いことです。一方本来HTML側に記述するようなstyleとかclassなどのHTMLのパラメータを、極力プログラム側で書かない。少なくともコントロールする必要がなければ、コード側で書かない。
(3) 簡単に拡張できるような仕組みを持つ
必要に応じて簡単に拡張できる仕組みをもつ。テンプレートエンジン自身は全体の9割までの必要を満たせばいいと割り切り、コードサイズを小さくします。コードサイズが小さければ、クラスを拡張するときなど、ちょっとソースコードを参照して、追加したり手直ししたりが出来ます。
[セクション]
ベースとなる考え方ですが、HTMLライクなテンプレートを読み込んで、必要な埋め込みを行ってそのまま、出力するという仕組みにします。テンプレート側に、制御構造を持つとプログラムが大変になるので、埋め込みデータで制御をするようにします。
前回の例で示したテンプレートのサブルーチンでは、制御を完全にプログラムで行うようにしていました。これではHTMLの形を変えると、プログラムで制御しないとなりません。たとえば、ループ構造が複数回出てくる形式で、出現順番が変われば、プログラム側も修正しないとなりません。
デザインとプログラムの分離ということでは、不完全すぎる仕様です。
幸いHTML自身は入れ子構造になっている仕組みなので、ブロックの単位も入れ子構造をとれば、都合がいいはずです。
前のテンプレートのサブルーチンでは、セクションは、単に連続して並んでいましたが、ここでは入れ子構造にします。
セクションの開始
セクションの終了
+— section A 開始
| 記述
| +– SECTION B 開始
| | 記述
| +– SECTION B 終了
| 記述
|—- section A 終了
一番外側のセクションですが、無名というもの使いづらいので、MAINというセクション名を割り当てています。
テンプレートエンジンは、読込を行った時点で新しいセクションがあれば、その位置にセクション用の埋め込みタグを埋め込みます。レンダリングするさいは、その記号を目印に置き換えます。
[埋め込み部品]
埋め込み部品とは、HTMLの<INPUT xxx>などのコントロール部品です。これらは、プログラム側で制御が必要な上に、テンプレート側にもclassやstyleなどのパラメータが必要な厄介な代物です。
<#{部品種別} {名称} [{パラメータ}={値}….]#>
名称で示された値をキーに、テンプレート側のパラメータとプログラムから渡されたハッシュ値を元に、コントロールを生成します。このコントロールの生成部分は、クラスメソッドになっており、変更や追加が簡単にできます。
以下のような部品を用意しました。
| input | テキストボックス |
| password | パスワードテキストボックス |
| hidden | 隠しテキスト |
| select | セレクトボックス |
| textarea | テキストエリア |
| check | チェックボックス |
| radio | ラジオボタン |
ファイルアップロードやイメージボタンなどは、まだ作成していません。
[表示部品]
この部分は以前のテンプレートサブルーチンをそのまま踏襲しています。しかし使い勝手を考えると、Smartyのような修飾子が欲しいところです。ここについては、今後の課題にしたいです。
##{表示名称}##
さて、テンプレートを見てみましょう。
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ShiftJIS" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta http-equiv="Content-Style-Type" content="text/css" />
</head>
<body>
<table border=1>
<tr>
<th width=240>商品名</th>
<th width=100>単価</th>
<th width=60>個数</th>
<th width=100>小計</th>
</tr>
<!--#LOOP-->
<tr>
<td align="left">##s_name##</th>
<td align="right">##n_unit##</th>
<td align="right">##n_count##</th>
<td align="right">##n_sum##</th>
</tr>
<!--##-->
</table>
<#input tx_loginid size="10"#>
<#password tx_loginpw size="10"#>
<br>
<#select sel_kind style="width: 200px"#><br>
<#textarea tx_memo cols="40" rows="10"#><br>
<#check b_upload #>同時更新<br>
<#button btnOpen value="開く" style="width: 100px;"#>
<#submit btnSubmit value="送信" style="width: 100px;"#>
</body>
</html>
いたってシンプルな構造です。
#
#
my $stdcodepage = "cp932";
use lib '.';
use strict;
use English;
use Encode;
use encoding "utf8", STDOUT=>"cp932", STDIN=>"cp932";
use utf8;
use HtmlTemplate;
BEGIN {
my $encode = "cp932";
binmode STDERR, ":encoding($encode)"; # 標準エラーもcp932に変更
}
#=======================================================================
#
sub showMain()
{
my $objTpl = HtmlTemplate::open( "test.tpl", "cp932" );
my %hash = (
"#LOOP" => [
{ "s_name" => "りんご", "n_unit" => 100, "n_count" => 5, "n_sum" => 500 },
{ "s_name" => "みかん", "n_unit" => 70, "n_count" => 4, "n_sum" => 280 },
{ "s_name" => "梨", "n_unit" => 120, "n_count" => 3, "n_sum" => 360 },
],
"b_upload" => {
"value" => "123",
},
"tx_memo" => {
"value" => "これが現実というものだよ。ヤマトの諸君。\\nガミラスで会おう去らばだ"
},
"sel_kind" => {
"mode" => 1,
"list" => [ 1, "Perl", 2, "Ruby", 3, "Java", 4, "C++" ],
"value" => 2,
},
"tx_loginid" => {
"value" => "bitop10",
},
"tx_loginpw" => {
"value" => "******",
},
);
print $objTpl->render( \%hash );
}
コードのほうもデータの構造はちょっと複雑ですが、プログラム的にはいたってシンプルな構造になっています。
#LOOPというキーに紐付いたハッシュの参照があります。この参照先が偽であれば、LOOPで宣言されたセクションは展開しません。スカラー値で真であれば、空のハッシュで展開します。ハッシュへのリファレンスであれば、ハッシュ値を渡して展開します。配列への参照であれば、その配列の個数分だけ繰り返し展開を行います。
テンプレートエンジンは、こちらを参照してください。
renderをデータのパラメータを渡して展開します。renderLoopとrenderHashで実際のレンダリングをしています。
callFuncは、埋め込み部品を生成する部分の大本の関数です。ここで、各埋め込み部品の生成関数を呼び出しています。
html_xxxxxシリーズの関数は、埋め込み部品の関数です。
現在のところ300行程度です。エラーチェックなどが最低限なので、本格的な開発にはちょっと不安ですが、とりあえず勉強用ベースとしては十分でないでしょうか
結果はこんな感じになります。
yna