遅い→起動時

http://d.hatena.ne.jp/pmint/

データ駆動型テンプレートエンジンの提案

配列やハッシュを渡すと中身を全部HTMLテンプレートに埋め込んでくれるテンプレートエンジンは知ってる。
でも埋め込むためのデータはHTMLマークアップ済みでなければならなく、それをどうやるかというとアプリケーション開発者が自分で…つまりサポートしてない。


そこで任意のデータ構造をデータ型別にマークアップする方法を考えてみた。
Perlなのでデータ型はARRAY、HASH、SCALARみたいなの、くらい。blessしてあればクラス名で区別できるけど、データ構造の各要素にすべてオブジェクト化するのは面倒 → テンプレートエンジンの意味がなくなる。
型の区別がざっくりなので(アプリケーションで期待するくらいの)型別処理はできない。

そこで位置に着目。モデルクラス->ハッシュ->ハッシュ->配列の要素ならこのマークアップ、というように。クラス名、ハッシュキー、配列のインデックス番号も併用すればさらに正確に要素を特定できる。

GitHub - pmint/DataDrivenTemplateEngine: Data-Driven HTML Template engine suggestion (zip)


ddt.plがサンプル起動用。
DDT.pmが本体。
他(Models.pm, Views.pm)はどうでもいいサンプルモデルとサンプルビュー。


アプリケーションで使うときはDDT.pmを継承してカスタマイズ版DDTを作ってもらう仕様。


説明

マークアップ

マークアップは文字列化+タグ付け。これをそれぞれの位置に対応するコードで。画一化した処理を用意、というわけではない。

[DDT.pm]
    sub __


'__' + (Perl組み込みの)refの戻り値という命名規則にしているのでリファレンスでない型(文字列化、マークアップされる要素)を処理するのは __ になった。
これを継承して、こんな風に上書き。

[ddt.pl]

package DDT::_entries;
use base qw/DDT/;

……

    sub __
    {

……

        # Markup
        if ($path =~ / Model::_entry HASH->{title}$/s){
            ### [markup Title]
            $ret .= ……
        }
        elsif ($path =~ / Model::_entry HASH->{body}$/s){
            ### [markup Body]
            $ret .= ……
        }
        elsif ($path =~ / Model::_entry HASH->{tags} ARRAY->\[\d+\]$/s){
            ### [markup Tag]
            $ret .= ……
        }
        else {
            $ret .= ……
        }

……

(※以上、かなり省略)


型ごとのサブルーチン、その内部で位置ごとのコードブロック。という構成。
マークアップ内容は全て __ に集まることになる。


位置とマークアップの対応付け

位置とマークアップコードの対応付けは型のパス表現で。

Model::_entries ARRAY->[0] Model::_entry HASH->{tags} ARRAY->[0]

…みたいな文字列形式。


これは…

  1. Model::_entries ARRAY->[0]
    Model::_entries(実体はblessされたARRAY)のインデックス0
  2. Model::_entry HASH->{tags}
    …に入っているModel::_entry(実体はblessされたHASH)のハッシュ要素「tags」
  3. ARRAY->[0]
    …に入っているARRAYのインデックス0

…を示す。


文字列だと正規表現で判別しやすい。ARRAY->[\d+]のような表現で、1つの配列に同じコードを対応付けられる。
判別も特に画一化してないので、インデックス番号が奇数か偶数かで別のマークアップをして背景色を切り替えることもできる、はず。


データ構造の展開はテンプレートエンジンで

ハッシュや配列のネスト構造を掘り下げるのはテンプレートエンジン側のコードで。

[DDT.pm]
    sub __HASH
    sub __ARRAY


要素の処理順序は別途決められるので、ここは継承してもらっても意味がない。


今のところ掘り下げ対象はHASH、ARRAYだけ。アプリケーション側で定義されてるクラスは扱い方がわからないのでそっちで掘り下げてもらう仕様。
とは言ってもHASH、ARRAYとして扱える属性値を与えてもらうだけ。それを__HASHや__ARRAYが画一的処理。


ハッシュ要素の順序

ハッシュを掘り下げるときは出力順を決めないと思った通りにはならない。
そのためアプリケーションにハッシュキーを並べてもらう。どうでもいいならスーパークラス任せ。
これも型パスで要素指定、マークアップと同様に判別。

[DDT.pm]
    sub __HASH_keys_order
    sub __ARRAY_indexes_order


これらを継承してもらって、アプリケーション定義の順序を決めてもらう。
配列用の__ARRAY_indexes_orderも用意してあるけど、テンプレートエンジンの中で並べ替えることってあるだろうか。


まとめ

まとまってない点。


HASH、ARRAY以外の独自クラスについては掘り下げ方を個別に定義しないといけないけど、これはこれでいいのかな。


型名パスの記述は…

Model::_entry HASH->{tags}
Model::_entries ARRAY->[0]

…よりも…

Model::_entry->{tags}
Model::_entries->[0]

…がいいかも?


でもblessされてないハッシュだと…

HASH->{tags}
ARRAY->[0]

だから変えないほうが統一感はあるかな?
blessだけでパスが1単語増えるのは違和感?