PostScript風なテンプレート・プロセッサの続き(その3)

その2で属性記述を HAML 風にすることでフォームを書きやすくなりました。でも、まだ textarea のテキスト部分が冗長です。

<div id="preview">[% var /entry.body get! %]</div>

<form [% action= entry.postlink method= /post %]>
<input [% name= /token type= /hidden value= session.token %] />
<textarea [% name= /body %]>[% var /entry.body htmlall_get %]</textara>
<input type="submit" value=" 投稿 " />
</form>

要素の種類に応じて escape 関数を切り替えねばならず、テンプレート・インタープリタが、要素を認識できないといけません。

これは要素生成オペレータで片がつきます。

[%
{ id= /preview } %div!= entry.body br

{ action= entry.postlink method= /post } %form { br
    { name= /token type= /hidden value= session.token } %input/ br
    { name= /body } %textarea= entry.body br
    { type= /submit value= { %] 投稿 [% } } %input/ br
} br
%]
  1. 要素生成オペレータはパーセント記号で始めます。
  2. 属性リストをブロックにして、要素生成オペレータの直前に置きます。属性がない要素を生成する際、あいまさがないときは属性ブロックを省略してもかまいません。
  3. 要素生成オペレータの直後のブロックは、入れ子要素の生成に利用できます。
  4. ただし、要素生成オペレータが等号記号で終わるときは、ブロックであってもオペランド扱いになり、テキストを生成します。オペランドの扱い方は属性オペレータと同じです。
    1. 等号記号だけのときは、escape_html します。textarea は例外的に escape_htmlall します。
    2. 感嘆符付き等号記号のときは、escape_raw します。

なお、最後の input 要素の value 属性には空白が混じっているため、現状のテンプレート・コンパイラでは扱えません。そのため、ブロック内に HTML リテラルとして記述しています。br は改行文字をデータ・スタックへ積むオペレータです。

実装してみます。_apply_block 手続きの while ループへの追加部分は次のとおりです。

+        if ($op =~ m{\A%([$ALNUM:_-]+)(?:(/)|(!?=))?\z}mosx) {
+            my($tag, $empty, $expr) = ($1, $2, $3);
+            $pc = _element($data, $block, $param, $pc, $tag, $empty, $expr);
+            next;
+        }
+        if ($op eq 'br') {
+            push @{$data}, "\n";
+            next;
+        }

要素生成手続きを書きます。

my %EMPTY_ELEMENT = map { $_ => 1 } qw(
    meta img link br hr input area param col base
);

sub _element {
    my($data, $block, $param, $pc, $tag, $empty, $expr) = @_;
    if (! defined $empty) {
        $empty = $EMPTY_ELEMENT{$tag};
    }
    my $attr = [];
    if ($pc >= 2 && ref $block->[$pc - 2]) {
        $attr = _apply_block([], $block->[$pc - 2], $param);
    }
    my $attrstr = join q(), @{$attr};
    if ($empty) {
        my $br = $tag eq 'br' || $tag eq 'hr' ? "\n" : q();
        push @{$data}, "<$tag$attrstr />$br";
        return $pc;
    }
    push @{$data}, qq(<$tag$attrstr>);
    my $opland = $block->[$pc++];
    if (ref $opland && ! $expr) {
        _apply_block($data, $opland, $param);
    }
    else {
        $expr ||= q(=);
        my $value = _opland($opland, $param);
        push @{$data},
              $expr eq q(!=) ? escape_raw($value)
            : $tag eq 'textarea' ? escape_htmlall($value)
            : escape_html($value);
    }
    push @{$data}, qq(</$tag>);
    return $pc;
}

ここまでの追加機能を使って「PostScript風なテンプレート・プロセッサの続き」に載せた YukiWiki-2.1.3 のブラウズ・ページ・ビューを書き直してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>[%
    entry {
      mypage
      subject length { %] - [% subject } if
    } with
%]</title>
</head>
<body>

[%  entry { %]
<h1>[%
      is_fixed
        { mypage }
        { links { { href= searchthis } %a= mypage } with }
      ifelse
      subject length { %] - [% subject } if
%]</h1>

<nav>
<div class="tools">
[%    links {
        is_fixed not {
          { href= admineditform } %a= /AdminEdit %] | [% br
          is_frozen not {
            { href= editform } %a= /Edit %] | [% br
          } if
          { href= diff } %a= /Diff %] | [% br
        } if
        { href= frontpage } %a= /FrontPage %] | [% br
        { href= indexpage } %a= /IndexPage br
      } with %]
</div>
</nav>

[% { id= /entry-body } %div!= texttohtml br %]

<div>Tag: [%
      tags {
        i { %],[% br } if
        { href= link } %a= name
      } forall
%]</div>
[%  } with %]

<aside>
<h1>Recent Changes</h1>
<ol>
[%  recents {
      offset limit -1 add 1 index add 1 exch {
        values exch get dup { {
          %li { { href= link } %a= name } br
        } with } if
      } for
    } with %]
</ol>
</aside>

<footer>derived from YukiWiki 2.1.3</footer>
</body>
</html>

PostScript風なテンプレート・プロセッサの続き(その4)