Kwartzユーザーズガイド

Makoto Kuwata <kwa(at)kuwata-lab.com>
last update: $Date: 2005-06-21 19:23:20 +0900 (Tue, 21 Jun 2005) $

はじめに

このドキュメントは、テンプレートシステムKwartz(*1)のユーザーズガイドです。 Kwartzは、『プレゼンテーションロジックとプレゼンテーションデータの分離(Separation of Presentation Logic and Presentation Data, SoPL/PD)』または『プレゼンテーションロジックの独立(Independence of Presentation Logic)』という概念を実現したテンプレートシステムです。

(*1)
Kwartzの開発は、情報処理推進機構(IPA)による平成15年度未踏ソフトウェア創造事業の支援を受けました。

目次



Kwartzについて

Kwartzとは?

Kwartz(*2)とは、 『プレゼンテーションロジックとプレゼンテーションデータの分離(Separation of Presentation Logic and Presentaion Data, SoPL/PD)』または『プレゼンテーションロジックの独立(Independence of Presentation Logic, IoPL)』という概念を実現したテンプレートシステムです。

Kwartz-rubyは、RubyによるKwartzの実装です。 このほか、PHPやJavaによる実装も予定されています。

以降の説明では、「Kwartz」という言葉はテンプレートシステムの仕様を説明するときに使用し、 「Kwartz-ruby」という言葉は特定の実装について説明するときに使用します。

(*2)
Kwartzは'Quartz'と同じように発音してください。

特徴

Kwartzには次のような特徴があります。

プレゼンテーションデータとプレゼンテーションロジックとが分離可能

通常のテンプレートシステムではテンプレートとメインプログラムとを分離します。 Kwartzでは更に、テンプレートをプレゼンテーションデータとプレゼンテーションロジックとに分離します。 これにより、プレゼンテーションロジックがHTMLの中に混じることも、またメインプログラムに紛れ込むこともありません。

高速な動作

Kwartzでは、テンプレート(プレゼンテーションデータとプレゼンテーションロジック)から出力用スクリプトを生成します。 これをあらかじめ行っておくため、実行時には出力用プログラムを呼び出すだけでよく、極めて高速に動作します。 またDOMツリーのような木構造を使わずに済むため、他のテンプレートシステムよりも高速です。

複数のプログラミング言語に対応

Kwartzは内部で独自の中間言語を採用することにより、様々なプログラミング言語から使用できるようになっています。 つまり、ひとつのHTMLテンプレートを様々な言語から使用することができるのです。 また使用する言語を変えたとしても、プレゼンテーション層は何も変更する必要がありません。 現在のところ、Ruby(eRuby)、PHP、JSP(JSTL 1.1&1.0)、Velocityに対応しています。

HTMLテンプレートがSGML形式を崩さない

Kwartzでは、HTMLテンプレートにおけるマーキング(印付け)をid属性で行っています。 そのため、 SmartyJakarta Velocity のようにHTMLテンプレートのデザインを崩してしまうことがありません。

任意のテキストファイルで使用可能

Kwartzでは、専用の属性がついたタグのみを認識し、それ以外のタグはただのプレーンテキストとみなします。 またHTMLパーサーやXMLパーサーを使用せず、独自に解析を行っています。 そのため、Enhydra XMLCamrita のようにXMLやHTMLでしか使用できないということはなく、任意のテキストファイルで使用可能です。

自動サニタイズと部分サニタイズ

Kwartzでは、サニタイズを自動的に行うようにすることができます。 つまり、いちいち「CGI.escapeHTML(var)」や「htmlspecialchars($var)」と書く必要がありません。 またサニタイズ機能はオン/オフすることができます。 さらに、ある部分だけをサニタイズする/しないを細かく指定できます。


簡単な例

Kwartzは、テンプレートをプレゼンテーションデータとプレゼンテーションロジックとに分けて記述します。 ここではその例を示します。

まずプレゼンテーションデータの例です。

プレゼンテーションデータ(example1.html):

<table>
  <tr id="list">
    <td id="item">foo</td>
  </tr>
</table>

次はプレゼンテーションロジックの例です。 プレゼンテーションロジックでは、プレゼンテーションデータにつけた「目印」に対して操作を行います。

プレゼンテーションロジック(example1.plogic):

#item {            // id="item" がついたエレメント
  value: member;      // 内容を変数memberの値で置き換える
  remove: "id";       // id属性を取り除く
}

#list {            // id="list" がついたエレメント
  remove: "id";       // id属性を取り除く
  plogic: {           // プレゼンテーションロジックを定義する
    foreach (member in member_list) {
      @stag;          // タグ(start tag)
      @cont;          // 内容(content)
      @etag;          // 終了タグ(end tag)
    }
  }
}

Kwartzはこの2つから各言語(eRuby, PHP, JSTL1.1&1.0)用の出力用スクリプトを自動生成します。 これをコンパイルといいます。 コンパイルするにはコマンドラインで次のようにします。

### for eRuby
$ kwartz -l eruby  -p example1.plogic example1.html > example1.rhtml

### for PHP
$ kwartz -l php    -p example1.plogic example1.html > example1.php

### for JSTL 1.1
$ kwartz -l jstl11 -p example1.plogic example1.html > example1.jsp

### for JSTL 1.0
$ kwartz -l jstl10 -p example1.plogic example1.html > example1.jsp

### for Velocity
$ kwartz -l velocity -p example1.plogic example1.html > example1.vm

以下は自動生成された出力用スクリプトです。

出力用スクリプト for eRuby (example1.rhtml):

<table>
<% for member in member_list do %>
  <tr>
    <td><%= member %></td>
  </tr>
<% end %>
</table>

出力用スクリプト for PHP (example1.php):

<table>
<?php foreach ($member_list as $member) { ?>
  <tr>
    <td><?php echo $member; ?></td>
  </tr>
<?php } ?>
</table>

出力用スクリプト for JSTL 1.1(*3)(example1.jsp):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<table>
<c:forEach var="member" items="${member_list}">
  <tr>
    <td><c:out value="${member}" escapeXml="false"/></td>
  </tr>
</c:forEach>
</table>

出力用スクリプト for JSTL 1.0(example1.jsp):

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<table>
<c:forEach var="member" items="${member_list}">
  <tr>
    <td><c:out value="${member}" escapeXml="false"/></td>
  </tr>
</c:forEach>
</table>

出力用スクリプト for Velocity(example1.vm):

<table>
#foreach($member in $member_list)
  <tr>
    <td>$!{member}</td>
  </tr>
#end
</table>

またコンパイル時にコマンドオプション -e をつけると、サニタイズされた出力用スクリプトが生成されます。 サニタイズには、eRubyではCGI.escapeHTML()が、PHPではhtmlspecialchars()が、 JSTLではescapeXml="false"なしの<c:out/>が、VelocityではEscapeToolの$esc.html()が使用されます。

これらの出力用スクリプトをメインプログラムから呼び出すと、Webページが出力されます。 呼び出し方は、各プログラミング言語によって異なります。

メインプログラム(Ruby):

## データを用意する
member_list = [ 'Oboro', 'Ominae', 'Jaquemonde' ]

## ERBを使って出力する
require 'erb'
require 'cgi'        # for sanitizing
str = File.open('example1.rhtml') { |f| f.read() }
str.untaint
trim_mode = 1
erb = ERB.new(str, $SAFE, trim_mode)
print erb.result(binding())

## またはERubyを使う方法
require 'eruby'
require 'cgi'        # for sanitizing
ERuby::import('example1.rhtml')

メインプログラム(PHP):

<?php
   // データを用意する
   $member_list = array('Oboro', 'Ominae', 'Jaquemonde');
   
   // 出力する
   include('example1.php');
 ?>

メインプログラム(JSTL):

public void doGet(HttpServletRequest request,
                  HttpServletResponse response)
                  throws ServletException, IOException {
   ...
   // データを用意する
   java.util.List member_list = new java.util.ArrayList();
   member_list.add("Oboro");
   member_list.add("Ominae");
   member_list.add("Jaquemonde");

   // 出力
   RequestDispatcher dispatcher = 
       request.getRequestDispatcher("example1.jsp");
   dispatcher.include(request, response);
   // または dispatcher.forward(request, response);
}

メインプログラム(Velocity):

import org.apache.velocity.app.Velocity;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.MethodInvocationException;
import java.io.OutputStreamWriter;
import java.io.IOException;

public class Main1 {
  public static void main(String[] args) {
    try {
    // 初期化
    Velocity.init();
 
    // データを用意する
    java.util.List member_list = new java.util.ArrayList();
    member_list.add("Oboro");
    member_list.add("Ominae");
    member_list.add("Jaquemonde");
 
    // Contextを作成しデータをセットする
    VelocityContext context = new VelocityContext();
    context.put("member_list", member_list);
 
    // テンプレートを読み込み、Contextとマージする。
    Template template = Velocity.getTemplate("Test.vm", "UTF8");
    OutputStreamWriter writer = new OutputStreamWriter(System.out);
    template.merge(context, writer);

    // 出力する
    writer.flush();
  }
  catch (ResourceNotFoundException ex) {
    // テンプレートが見つからなかった
  }
  catch (ParseErrorException ex) {
    // テンプレート解析時に構文エラーがあった
  }
  catch (MethodInvocationException ex) {
     // メソッド呼び出しができなかった
  }
  catch (IOException ex) {
    // 入出力に問題があった
  }
  catch (Exception ex) {
    // その他のエラー
  }
}

出力用プログラムを呼び出して実行すると、例えば次のようなWebページが生成されます。

<table>
  <tr>
    <td>Oboro</td>
  </tr>
  <tr>
    <td>Ominae</td>
  </tr>
  <tr>
    <td>Jaquemonde</td>
  </tr>
</table>
(*3)
コマンドラインオプションとして「--charset=CHARSET」をつけると、JSTL用出力スクリプトでは「<%@ page contentType="text/html; charset=CHARSET" %>」をつけてくれます。

複雑な例

もう少し複雑な例として、色つきのテーブルを示します。

プレゼンテーションデータは次のようになります。 マーキングは「id="name"」ではなく「id="mark:name"」としています。 こうするとid属性は自動的に削除されますので、プレゼンテーションロジックファイルでいちいち「remove: "id"」と書く必要がなくなります。

プレゼンテーションデータ(example2.html):

<table>
 <tr bgcolor="#CCCCFF" id="mark:list">
  <td id="mark:name">foo</td>
  <td>
   <a href="mailto:foo@mail.com" id="mark:email">foo@mail.com</a>
  </td>
 </tr>
</table>

次はプレゼンテーションロジックです。 プレゼンテーションロジックでは、繰り返しを行いながら、奇数行か偶数行かの判定を行っています。

プレゼンテーションロジック(example2.plogic):

// id="mark:list" がついたエレメント:
// bgcolor属性の値として変数colorを出力する。
// また繰り返しを行い、奇数行と偶数行で変数colorの値を変える。
#list {
  attrs:  "bgcolor" color;
  plogic: {
    i = 0;
    foreach (member in member_list) {
      i += 1;
      color = i % 2 == 0 ? '#FFCCCC' : '#CCCCFF';
      @stag;    // 開始タグ(start tag)
      @cont;    // 内容    (content)
      @etag;    // 終了タグ(end tag)
    }
  }
}

// id="mark:name" がついたエレメント:
// 内容として member['name'] の値を出力する。
#name {
  value: member['name'];
}

// id="mark:email" がついたエレメント:
// 内容として member['email'] の値を出力する。
// またhref属性は "mailto:" .+ member['email'] の値を出力する。
// (「.+」は文字列の連結を行う演算子)
#email {
  value: member['email'];
  attrs: "href" ("mailto:" .+ member['email']);
}

見ておわかりのように、プレゼンテーションロジックにはHTMLタグが一切入らず、またプレゼンテーションデータにはロジックが一切入っていません。 つまり、プレゼンテーションデータとプレゼンテーションロジックの分離が実現できていることになります。

なおインクリメント演算子(++)は使えませんので、ctr++ のように書くことはできません。

コンパイル:

### for eRuby
$ kwartz -l eruby  -p example2.plogic example2.html > example2.rhtml

### for PHP
$ kwartz -l php    -p example2.plogic example2.html > example2.php

### for JSTL 1.1
$ kwartz -l jstl11 -p example2.plogic example2.html > example2.jsp

### for JSTL 1.0
$ kwartz -l jstl10 -p example2.plogic example2.html > example2.jsp

### for Velocity
$ kwartz -l velocity -p example2.plogic example2.html > example2.vm

出力用スクリプト:

### for eRuby
<table>
<% i = 0 %>
<% for member in member_list do %>
<%   i += 1 %>
<%   color = i % 2 == 0 ? "#FFCCCC" : "#CCCCFF" %>
 <tr bgcolor="<%= color %>">
  <td><%= member["name"] %></td>
  <td>
   <a href="mailto:<%= member["email"] %>"><%= member["email"] %></a>
  </td>
 </tr>
<% end %>
</table>

### for PHP
<table>
<?php $i = 0; ?>
<?php foreach ($member_list as $member) { ?>
<?php   $i += 1; ?>
<?php   $color = $i % 2 == 0 ? "#FFCCCC" : "#CCCCFF"; ?>
 <tr bgcolor="<?php echo $color; ?>">
  <td><?php echo $member["name"]; ?></td>
  <td>
   <a href="mailto:<?php echo $member["email"]; ?>"><?php echo $member["email"]; ?></a>
  </td>
 </tr>
<?php } ?>
</table>

### for JSTL
<table>
<c:set var="i" value="0"/>
<c:forEach var="member" items="${member_list}">
  <c:set var="i" value="${i + 1}"/>
  <c:set var="color" value="${i % 2 eq 0 ? '#FFCCCC' : '#CCCCFF'}"/>
 <tr bgcolor="<c:out value="${color}" escapeXml="false"/>">
  <td><c:out value="${member['name']}" escapeXml="false"/></td>
  <td>
   <a href="mailto:<c:out value="${member['email']}" escapeXml="false"/>"><c:out value="${member['email']}" escapeXml="false"/></a>
  </td>
 </tr>
</c:forEach>
</table>

### for Velocity
<table>
#set($i = 0)
#foreach($member in $member_list)
  #set($i = $i + 1)
  #if($i % 2 == 0)
    #set($color = "#FFCCCC")
  #else
    #set($color = "#CCCCFF")
  #end
 <tr bgcolor="$!{color}">
  <td>$!{member["name"]}</td>
  <td>
   <a href="mailto:$!{member["email"]}">$!{member["email"]}</a>
  </td>
 </tr>
#end
</table>

プレゼンテーションロジックの応用例

Kwartzでは、複雑なプレゼンテーションロジックが素直に記述できます。 ここではその例を示します。 なお『プレゼンテーションパターンカタログ』もご覧ください。

ここで重要なのは、プレゼンテーションロジックにはタグ名や属性名が一切出てきていないという点です。 プレゼンテーションデータのほうでどんなにタグを変更したとしても、プレゼンテーションロジックはまったく変更する必要はありません。

つまり、プレゼンテーションデータとプレゼンテーションロジックとが完全に分離されているわけです。



ディレクティブ

Kwartzでは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むこともできます。 つまり、両者を分離することも、一体化することもできるわけです。

プレゼンテーションロジックをプレゼンテーションデータの中に埋め込むには、ディレクティブを用います。 ディレクティブとは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むための命令です。 Kwartzでは、id属性とkw:d属性を用いてディレクティブを記述します。

次はディレクティブを使った例です:

プレゼンテーションデータ(example3.html):

<table>
  <tr id="foreach:member=member_list">
    <td id="value:member['name']">foo</td>
    <td><a href="mailto:@{member['email']}@">
         @{member['email']}@</a></td>
  </tr>
  <tr id="dummy:d1">
    <td>bar</td>
    <td><a href="mailto:bar@mail.org">bar@mail.org</a></td>
  </tr>
  <tr id="dummy:d2">
    <td>baz</td>
    <td><a href="mailto:baz@mail.net">baz@mail.net</a></td>
  </tr>
</table>

コンパイル:

### for eRuby
$ kwartz -l eruby   example3.html > example3.rhtml

### for PHP
$ kwartz -l php     example3.html > example3.php

### for JSTL1.1
$ kwartz -l jstl11  example3.html > example3.jsp

出力用スクリプト:

### for eRuby
<table>
<% for member in member_list do %>
  <tr>
    <td><%= member["name"] %></td>
    <td><a href="mailto:<%= member["email"] %>">
         <%= member["email"] %></a></td>
  </tr>
<% end %>
</table>

### for PHP
<table>
<?php foreach ($member_list as $member) { ?>
  <tr>
    <td><?php echo $member["name"]; ?></td>
    <td><a href="mailto:<?php echo $member["email"]; ?>">
         <?php echo $member["email"]; ?></a></td>
  </tr>
<?php } ?>
</table>

### for JSTL
<table>
<c:forEach var="member" items="${member_list}">
  <tr>
    <td><c:out value="${member['name']}" escapeXml="false"/></td>
    <td><a href="mailto:<c:out value="${member['email']}" escapeXml="false"/>">
         <c:out value="${member['email']}" escapeXml="false"/></a></td>
  </tr>
</c:forEach>
</table>

### for Velocity
<table>
#foreach($member in $member_list)
  <tr>
    <td>$!{member["name"]}</td>
    <td><a href="mailto:$!{member["email"]}">
         $!{member["email"]}</a></td>
  </tr>
#end
</table>

このほか、条件分岐を行うディレクティブなども用意されています。 詳細はリファレンスマニュアルをご覧ください。

(*4)
このパターンは設定ファイルの定数EMBED_PATTERNで変更できます。

サニタイズ

Kwartzでは、自動でサニタイズを行うことができます。 またある部分だけをサニタイズする/しないを選択することもできます。

自動サニタイズ

コマンドラインオプション -e をつけると、出力スクリプトをサニタイズします。 サニタイズでは、eRubyではCGI.escapeHTML()が、ERBではh()が、PHPではhtmlspecialchars()が、JSTLではescapeXml="false"なしの<c:out>が、Velocityでは$!esc.html()がそれぞれ使用されます。

プレゼンテーションデータ:

<tr bgcolor="@{color}@">
  <td id="value:str">foo</td>
</tr>

コンパイル:

### for eRuby
$ kwartz -e -l eruby  sanitize1.html > sanitize1.rhtml
	
### for ERB
$ kwartz -e -l erb    sanitize1.html > sanitize1.rhtml
	
### for PHP
$ kwartz -e -l php    sanitize1.html > sanitize1.php
	
### for JSTL
$ kwartz -e -l jstl   sanitize1.html > sanitize1.jsp

### for Velocity
$ kwartz -e -l velocity sanitize1.html > sanitize1.vm

出力用スクリプト:

### for eRuby
<tr bgcolor="<%= CGI::escapeHTML((color).to_s) %>">
  <td><%= CGI::escapeHTML((str).to_s) %></td>
</tr>

### for ERB
<tr bgcolor="<%=h(color)%>">
  <td><%=h(str)%></td>
</tr>

### for PHP
<tr bgcolor="<?php echo htmlspecialchars($color); ?>">
  <td><?php echo htmlspecialchars($str); ?></td>
</tr>

### for JSTL
<tr bgcolor="<c:out value="${color}"/>">
  <td><c:out value="${str}"/></td>
</tr>

### for Velocity
<tr bgcolor="$!esc.html($color)">
  <td>$!esc.html($str)</td>
</tr>

式が文字列や数値のような定数の場合は、サニタイズされません。 また「flag ? 'checked' : ''」のような条件演算子では、flagの値にかかわらず文字列定数が出力されますので、これもサニタイズされません。


部分サニタイズ

関数E(expr)は、コマンドラインオプションに関わらず式exprをサニタイズします。 また関数X(expr)は、コマンドラインオプションに関わらず式exprをサニタイズしません (*5)

プレゼンテーションデータ:

<table>
 <tr bgcolor="@{X(color)}@">
   <td id="value:E(str)">foo</td>
 </tr>
</table>

コンパイル:

### for eRuby
$ kwartz -e -l eruby  sanitize1.html > sanitize1.rhtml
	
### for ERB
$ kwartz -e -l erb    sanitize1.html > sanitize1.rhtml
	
### for PHP
$ kwartz -e -l php    sanitize1.html > sanitize1.php
	
### for JSTL 1.1
$ kwartz -e -l jstl11 sanitize1.html > sanitize1.jsp

### for Velocity
$ kwartz -e -l velocity sanitize1.html > sanitize1.vm

出力用スクリプト:

### for eRuby
<table>
 <tr bgcolor="<%= color %>">
   <td><%= CGI::escapeHTML((str).to_s) %></td>
 </tr>
</table>

### for ERB
<table>
 <tr bgcolor="<%= color %>">
   <td><%=h(str)%></td>
 </tr>
</table>

### for PHP
<table>
 <tr bgcolor="<?php echo $color; ?>">
   <td><?php echo htmlspecialchars($str); ?></td>
 </tr>
</table>

### for JSTL
<table>
 <tr bgcolor="<c:out value="${color}" escapeXml="false"/>">
   <td><c:out value="${str}"/></td>
 </tr>
</table>

### for Velocity
<table>
 <tr bgcolor="$!{color}">
   <td>$!esc.html($str)</td>
 </tr>
</table>

またコマンドラインオプションに関係なく、「id="Value:expr"」や「id="Attr:name=expr"」は式exprを必ずサニタイズします。 逆に、「id="VALUE:expr"」や「id="ATTR:name=expr"」は式exprを必ずサニタイズしません。 これらはそれぞれ、「id="value:E(expr)"」や「id="attr:name=X(expr)"」と同じです。

(*5)
E()とX()は正確には擬似関数であり、PLにおけるprint文の引数に指定することはできますが、任意の式として使用できるわけではありません。例えば「@{E(...)}@」や「id="value:E(...)"」と書くことはできますが、「id="set:str=E(...)"」のようには書けません。

サニタイズの設定

kwartz/config.rbはKwartz-rubyの動作を設定しているファイルです。 デフォルトでサニタイズを行うように設定するには、この中で定数ESCAPEの値をtrueに変更します。



その他の話題

制限事項

Kwartzでは、HTMLパーサやXMLパーサを用いず、正規表現によるパターンマッチでHTMLファイルを解析しています。 そのため、HTMLではないテキストファイルでも利用できるという利点がありますが、次のような制限事項もあります。


グローバル変数とローカル変数

Kwartzには、プレゼンテーションデータ/ロジックファイルを分析し、変数を調査する機能があります。

Kwartzでは、メインプログラムで設定されて出力用スクリプトに渡される変数を「テンプレートグローバル変数」、 テンプレートの中でだけ使用される変数を「テンプレートローカル変数」と呼んでいます。 Kwartzは、変数がグローバルかローカルかを調べて報告する機能があります。

次の例をご覧ください。

プレゼンテーションデータ(analyze.html):

<span kw:d="value:title">Analyzer Example</span>
<dl id="mark:items">
  <dt kw:d="value:ctr"></dt>
  <dd kw:d="value:item">Foo</dd>
</dl>

プレゼンテーションロジック(analyze.plogic):

#items {
  plogic: {
    @stag;
    ctr = 0;
    foreach (item in list) {
      ctr += 1;
      @cont;
    }
    @etag;
  }
}

この例では4つの変数があります。 このうち、itemctrはテンプレート中でだけ使われるのでテンプレートローカル変数、 titlelistはメインプログラムで設定されて出力用スクリプトに渡されるのでテンプレートグローバル変数です。

kwartzをコマンドランオプション -a analyze をつけて起動すると、グローバル変数とローカル変数を報告してくれます。

実行例:

$ kwartz -a analyze -p analyze.plogic analyze.html
Global: title list
Local:  ctr item

テンプレートグローバル/ローカル変数の名前を変更する

Kwartzでは、テンプレートグローバル変数とテンプレートローカル変数の、両方または一方の名前にプレフィックスをつけることができます。 プレフィックスをつけるには、コマンドラインオプション「--globalvar-prefix=prefix」と「--localvar-prefix=prefix」を使用します。

例えばRubyやPHPでは、メインプログラムで使用している変数名と、テンプレートのローカル変数名とが一致した場合、 テンプレート側の変数を変更することでメインプログラムの動作に影響を与えてしまうという問題があります。 これを避けるには、テンプレートローカル変数名に例えば「_」というプレフィックスをつけます。

前の節で使用したanalyze.htmlとanalyze.plogicを使ってみます。

実行例:

$ kwartz -l eruby  -p analyze.plogic --localvar-prefix='_' analyze.html
$ kwartz -l php    -p analyze.plogic --localvar-prefix='_' analyze.html
$ kwartz -l jstl11 -p analyze.plogic --localvar-prefix='_' analyze.html
$ kwartz -l velocity -p analyze.plogic --localvar-prefix='_' analyze.html

出力用スクリプト:

### for eRuby
<%= title %>
<dl>
<% _ctr = 0 %>
<% for _item in list do %>
<%   _ctr += 1 %>
  <dt><%= _ctr %></dt>
  <dd><%= _item %></dd>
<% end %>
</dl>

### for PHP
<?php echo $title; ?>
<dl>
<?php $_ctr = 0; ?>
<?php foreach ($list as $_item) { ?>
<?php   $_ctr += 1; ?>
  <dt><?php echo $_ctr; ?></dt>
  <dd><?php echo $_item; ?></dd>
<?php } ?>
</dl>

### for JSTL
<c:out value="${title}" escapeXml="false"/>
<dl>
<c:set var="_ctr" value="0"/>
<c:forEach var="_item" items="${list}">
  <c:set var="_ctr" value="${_ctr + 1}"/>
  <dt><c:out value="${_ctr}" escapeXml="false"/></dt>
  <dd><c:out value="${_item}" escapeXml="false"/></dd>
</c:forEach>
</dl>

### for Velocity
$!{title}
<dl>
#set($_ctr = 0)
#foreach($_item in $list)
  #set($_ctr = $_ctr + 1)
  <dt>$!{_ctr}</dt>
  <dd>$!{_item}</dd>
#end
</dl>

なお設定ファイルの定数「GLOBALVAR_PREFIX」と「LOCALVAR_PREFIX」で、 デフォルトのプレフィックスを指定できます。


spanタグの削除

Kwartzでは、ディレクティブしか含まないようなspanタグは、ダミータグとみなされて自動的に削除されます。

プレゼンテーションデータ:

<h1><span id="mark:title">title</span></h1>

Hello <span id="value:user">World</span>!

プレゼンテーションロジック:

#title {
  value: title;
}

出力用スクリプト(for eRuby):

<h1><%= title %></h1>

Hello <%= user %>!

spanタグが他の属性を含んでいた場合は、削除されません。

プレゼンテーションデータ:

<h1><span id="mark:title" class="title">title</span></h1>

Hello <span kw:d="value:user" style="color:black">World</span>!

出力用スクリプト(for eRuby):

<h1><span class="title"><%= title %></span></h1>

Hello <span style="color:black"><%= user %></span>!

開始タグに式の値を追加する

<input type="..." checked>」のようにする場合は、次のようにします。

プレゼンテーションデータ:

<input type="checkbox" name="foo" value="Y" id="foo" />

プレゼンテーションロジック:

#foo {
  append: flag ? ' checked' : '';
}

出力用スクリプト:

### for eRuby
<input type="checkbox" name="foo" value="Y" id="foo"<%= flag ? " checked" : "" %> />

### for PHP
<input type="checkbox" name="foo" value="Y" id="foo"<?php echo $flag ? " checked" : ""; ?> />

### for JSTL 1.1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<input type="checkbox" name="foo" value="Y" id="foo"<c:out value="${flag ? ' checked' : ''}" escapeXml="false"/> />

### for JSTL 1.0
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<c:choose><c:when test="${flag}">
<input type="checkbox" name="foo" value="Y" id="foo" checked />
</c:when><c:otherwise>
<input type="checkbox" name="foo" value="Y" id="foo" />
</c:otherwise></c:choose>

### for Velocity
#if($flag)
<input type="checkbox" name="foo" value="Y" id="foo" checked />
#else
<input type="checkbox" name="foo" value="Y" id="foo" />
#end

ディレクティブ id="append:expr" でも同じことができます。

プレゼンテーションデータ:

<input type="checkbox" name="foo" value="Y" id="append:flag?' checked':''" />

またchecked="checked"selected="selected"を簡単に出力するための関数を用意しています。 「C(expr)」「S(expr)」「D(expr)」は式exprが真だった場合に、 それぞれ「 checked="checked"」「 selected="selected"」「 disabled="disabled"」を出力します。

プレゼンテーションデータ:

<input type="checkbox" name="foo" value="Y" id="foo" />

プレゼンテーションロジック:

#foo {
  append: C(foo == 100);
}

出力用スクリプト:

### for eRuby
<input type="checkbox" name="foo" value="Y" id="foo"<%= foo == 100 ? " checked=\"checked\"" : "" %> />

### for PHP
<input type="checkbox" name="foo" value="Y" id="foo"<?php echo $foo == 100 ? " checked=\"checked\"" : ""; ?> />

### for JSTL 1.1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<input type="checkbox" name="foo" value="Y" id="foo"<c:out value="${foo eq 100 ? ' checked="checked"' : ''}" escapeXml="false"/> />

### for JSTL 1.0
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<c:choose><c:when test="${foo eq 100}">
<input type="checkbox" name="foo" value="Y" id="foo" checked="checked" />
</c:when><c:otherwise>
<input type="checkbox" name="foo" value="Y" id="foo" />
</c:otherwise></c:choose>

### for Velocity
#if($foo == 100)
<input type="checkbox" name="foo" value="Y" id="foo" checked="checked" />
#else
<input type="checkbox" name="foo" value="Y" id="foo" />
#end

プレゼンテーションロジックをターゲット言語で記述する

Kwartzでは、ターゲット言語(RubyやPHPやJavaなど)でプレゼンテーションロジックを記述することができます。

プレゼンテーションロジック(eRuby):

#foo {
  value:  <%= str.empty? ? "null" : str %>;
  plogic: {
    @stag;
    <% ENV.each do |name, value| %>
      @cont;
    <% end %>
    @etag;
  }
}

プレゼンテーションロジック(PHP):

#foo {
  value:  <?= $str === NULL ? "null" : str ?>;
  plogic: {
    @stag;
    <?php foreach ($_ENV as $name => $value) { ?>
      @cont;
    <?php } ?>
    @etag;
  }
}

Kwartzは、「<%= ... %>」や「<% ... %>」で囲まれた部分をそのまま出力するだけです。 中身を一切解析しませんので、文法に間違いがあっても検出されませんし、 テンプレートローカル変数/グローバル変数の解析もできません。

なお「X(<%= expression %>)」とすれば、expressionをサニタイズせずに出力できます。


Ruby on Railsで使用する

Ruby on RailsでKwartzを使うには、コマンドオプションとして「-l erb」と「--globalvar-prefix='@'」をつけます。 Kwartz-rubyでは、これをまとめて行うコマンドオプション「-Rails」を特別に用意しています。

またプレゼンテーションロジックファイルにおいてRuby on Rails特有の関数を使う場合は、 「<% ... %>」や「<%= ... %>」を使います(もちろん、プレゼンテーションデータファイルに直接埋め込んでもよいです)。

プレゼンテーションデータ(list.html):

<html>
 <body>

  <h1>Online Cookbook - All Recipes</h1>
  
  <table border="1">
   <tr>
    <th width="80%">Recipe</th>
    <th width="20%">Date</th>
   </tr>
   <tr id="mark:recipes">
    <td><a href="..." id="mark:recipe_title">Hot Chips</a></td>
    <td id="mark:recipe_date">2004 November 11</td>
   </tr>
   <tr id="dummy:d1">
    <td><a href="...">Ice Water</a></td>
    <td>2004 November 11</td>
   </tr>
  </table>

  <p>
   <a href="..." id="mark:recipe_new">Create new recipe</a>
  </p>

 </body>
</html>

プレゼンテーションロジック(list.plogic):

#recipes {
  plogic: {
    foreach (recipe in recipes) {
      @stag;   // start tag
      @cont;   // content
      @etag;   // end tag
    }
  }
}

#recipe_title {
  value: recipe.title;
  attrs: "href" <%= url_for(:action => "show", :id => recipe.id) %>;
}

// または次のようにしてもよい。
//  #recipe_title {
//    plogic: {
//      print(<%= link_to recipe.title, :action => "show", :id => recipe.id %>);
//    }
//  }

#recipe_date {
  value: recipe.date;
}

#recipe_new {
  attrs: "href" <%= url_for(:action => "new") %>;
}

// または次のようにしてもよい。
//  #recipe_new {
//    plogic: {
//      print(<%= link_to "Create new recipe", :action => "new" %>);
//    }
//  }

コンパイル:

$ kwartz -Rails -p list.plogic list.html > list.rhtml

出力用スクリプト:

<html>
 <body>

  <h1>Online Cookbook - All Recipes</h1>
  
  <table border="1">
   <tr>
    <th width="80%">Recipe</th>
    <th width="20%">Date</th>
   </tr>
<% for recipe in @recipes do %>
   <tr>
    <td><a href="<%= url_for(:action => "show", :id => recipe.id) %>"><%= recipe.title %></a></td>
    <td><%= recipe.date %></td>
   </tr>
<% end %>
  </table>

  <p>
   <a href="<%= url_for(:action => "new") %>">Create new recipe</a>
  </p>

 </body>
</html>

なお「X(<%= expression %>)」とすれば、expressionをサニタイズせずに出力できます。


テンプレートをRubyまたはPHPの関数にコンパイルする

コマンドラインオプション「-a defun」を指定すると、テンプレートをRubyまたはPHPの関数に変換できます。

プレゼンテーションデータ(hoge1.html):

Hello @{user}@ !
<ul id="mark:list">
  <li id="value:item">xxx</li>
</ul>

プレゼンテーションロジック(hoge1.plogic):

#list {
  plogic: {
    @stag;
    foreach (item in list) {
      @cont;
    }
    @stag;
  }
}

コンパイル:

$ kwartz -l eruby -a defun -p hoge1.plogic hoge1.html > hoge1.rb
$ kwartz -l php   -a defun -p hoge1.plogic hoge1.html > hoge1.php

出力用スクリプト:

### for eRuby
  def view_hoge1(__args)
    user = __args[:user]
    list = __args[:list]
    return _view_hoge1(user,list)
  end
  def _view_hoge1(user,list)
    _erbout = ''; _erbout << "Hello "; _erbout <<(( user ).to_s); _erbout << " !\n"
    _erbout << "<ul>\n"
     for item in list do 
    _erbout << "  <li>"; _erbout <<(( item ).to_s); _erbout << "</li>\n"
     end 
    _erbout << "<ul>\n"
    _erbout
  end

### for PHP
<?php
    function view_hoge1($__args) {
        $user = $__args['user']
        $list = $__args['list']
        return _view_hoge1($user, $list)
    }
    function _view_hoge1($user, $list) {
        ob_start();
?>Hello <?php echo $user; ?> !
<ul>
<?php foreach ($list as $item) { ?>
  <li><?php echo $item; ?></li>
<?php } ?>
<ul>
<?php
        $__s = ob_get_contents();
        ob_end_clean();
        return $__s;
    }
?>

関数は2つ定義されます。どちらでもお好きなほうをお使いください。

コマンドラインオプション「-C name」でクラス名またはモジュール名を、 「-F name」で関数名またはメソッド名を、 「-A name」で引数を指定できます。 また設定ファイルの「DEFUN_CLASS」でクラス名またはモジュール名を、 「DEFUN_FUNCTION」で関数名またはメソッド名を指定できます。