Kwartzユーザーズガイド
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属性で行っています。 そのため、 Smarty や Jakarta Velocity のようにHTMLテンプレートのデザインを崩してしまうことがありません。
- 任意のテキストファイルで使用可能
-
Kwartzでは、専用の属性がついたタグのみを認識し、それ以外のタグはただのプレーンテキストとみなします。 またHTMLパーサーやXMLパーサーを使用せず、独自に解析を行っています。 そのため、Enhydra XMLC や amrita のようにXMLやHTMLでしか使用できないということはなく、任意のテキストファイルで使用可能です。
- 自動サニタイズと部分サニタイズ
-
Kwartzでは、サニタイズを自動的に行うようにすることができます。 つまり、いちいち「CGI.escapeHTML(var)」や「
htmlspecialchars($var)」と書く必要がありません。 またサニタイズ機能はオン/オフすることができます。 さらに、ある部分だけをサニタイズする/しないを細かく指定できます。
簡単な例
Kwartzは、テンプレートをプレゼンテーションデータとプレゼンテーションロジックとに分けて記述します。 ここではその例を示します。
まずプレゼンテーションデータの例です。
- 「
id="list"」はそのエレメントにvaluesという名前で「目印」をつけること、つまり操作対象とすることを表します。Kwartzでは、エレメントに目印をつけることを「マーキング(Marking)」といいます。 - 同様に、「
id="item"」は<td>エレメントにitemという名前で「目印」をつけています。
プレゼンテーションデータ(example1.html):
<table>
<tr id="list">
<td id="item">foo</td>
</tr>
</table>
次はプレゼンテーションロジックの例です。 プレゼンテーションロジックでは、プレゼンテーションデータにつけた「目印」に対して操作を行います。
- 「
#item { ... }」は目印「item」がつけられたエレメント(Element, ここでは<td>から<t/d>まで)を表します。- 「
value: expression;」は、エレメントの内容を式expressionの値(ここでは変数memberの値)で置き換えることを表します。 - 「
remove: "id";」は、エレメントからid属性を取り除くことを表します。 なお「id="name"」のかわりに「id="mark:name"」を使用するとid属性は自動的に削除され、「remove: "id";」をいちいち記述する必要はなくなります。
- 「
- 「
#list { ... }」は目印「list」がつけられたエレメント(Element, ここでは<tr>から</tr>まで)を表します。- 「
plogic: { ... }」で、そのエレメントのプレゼンテーションロジックを定義します。 - 「
@stag」は開始タグ(Start tag, ここでは<tr>)を表します。 - 「
@cont」は内容(Content, ここでは<td id="item">foo</td>)を表します。 - 「
@etag」は終了タグ(End tag, ここでは</tr>)を表します。 - 「
foreach (member in member_list) { ... }」は繰り返しを表します。
- 「
プレゼンテーションロジック(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では、複雑なプレゼンテーションロジックが素直に記述できます。 ここではその例を示します。 なお『プレゼンテーションパターンカタログ』もご覧ください。
- エレメント全体を繰り返すことができます。
#list { plogic: { foreach (member in member_list) { @stag; @cont; @etag; } } }
- 内容だけを繰り返すことができます。<dl></dl>で使うのに向いています。
#list { plogic: { @stag; foreach (member in member_list) { @cont; } @etag; } }
- 内容のかわりに、変数や式などの値を出力することができます。
#list { plogic: { @stag; print("Hello!"); @etag; } }これは次のように書くこともできます。#list { value: "Hello!"; }
- エレメントのかわりに、変数や式などの値を出力することができます。
#list { plogic: { print("<b>Hello!</b>"); } }
- 内容だけを残し、開始タグと終了タグを消すことができます。
#list { plogic: { @cont; } }
- エレメント全体を消すことができます。これは、ダミーデータを消すときに便利です。
#list { plogic: { } }
- エレメント全体を、別のエレメントで置き換えることができます。
#list { plogic: { @element(foo); // 「foo」とマーキングされたエレメントを展開する } }
- エレメントの内容を、別のエレメントで置き換えることができます。
#list { plogic: { @stag; @element(foo); // 「foo」とマーキングされたエレメントを展開する @etag; } }
- 複雑なプレゼンテーションロジックを含めることができます。
#list { plogic: { i = 0; foreach (member in member_list) { i += 1; // 注意:i++ や ++i は使えません if (i % 2 == 0) { color = 'red'; } else { color = 'blue'; } @stag; @cont; @etag; } }
ここで重要なのは、プレゼンテーションロジックにはタグ名や属性名が一切出てきていないという点です。 プレゼンテーションデータのほうでどんなにタグを変更したとしても、プレゼンテーションロジックはまったく変更する必要はありません。
つまり、プレゼンテーションデータとプレゼンテーションロジックとが完全に分離されているわけです。
ディレクティブ
Kwartzでは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むこともできます。 つまり、両者を分離することも、一体化することもできるわけです。
プレゼンテーションロジックをプレゼンテーションデータの中に埋め込むには、ディレクティブを用います。 ディレクティブとは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むための命令です。 Kwartzでは、id属性とkw:d属性を用いてディレクティブを記述します。
次はディレクティブを使った例です:
- 「
id="foreach:var=list"」は、繰り返しを表します。 - 「
id="value:expression"」は、内容のかわりに式の値を出力することを表します。 - 「
id="dummy:str"」は、そのエレメントがダミーであることを表します(つまり出力されません)。 ここでstrはid属性の値が同じにならないようにするためのものであり、特に意味はありません。 - 「
@{expression}@」(*4)は、式の値を出力するためのディレクティブです。
プレゼンテーションデータ(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ではないテキストファイルでも利用できるという利点がありますが、次のような制限事項もあります。
- id属性がついたタグは、終了タグを省略できません。
<!-- </li> が指定されていないので解析できない --> <ul> <li id="foo">foo </ul>
また内容を持たない場合は、<foo id="..."/>のような空タグとしてください。
- ただし、
<input>と<br>と<meta>と<img>と<hr>は、終了タグを省略できます。 また空タグにする必要もありません。<!-- </input> が省略されているが、正しく解析される --> <input type="text" name="user" id="user">
なおこれらのタグ名はkwartz/config.rbの中で定数NOENDに設定されています。 また大文字と小文字は区別されます。
- 属性値は必ず「
"」で囲ってください。また「'」で囲っても認識されません。<!-- 属性値が「"」で囲まれていないので、解析できない --> <h1 id="value:title" class=title>title</h1>
グローバル変数とローカル変数
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つの変数があります。
このうち、itemとctrはテンプレート中でだけ使われるのでテンプレートローカル変数、
titleとlistはメインプログラムで設定されて出力用スクリプトに渡されるのでテンプレートグローバル変数です。
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など)でプレゼンテーションロジックを記述することができます。
- 「
<%= expression %>」または「<?= expression ?>」は、 ターゲット言語で式を記述します。 - 「
<% statement %>」または「<? statement ?>」は、 ターゲット言語で文を記述します。「%>」や「?>」のあとに「;」はつけません。
プレゼンテーションロジック(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つ定義されます。どちらでもお好きなほうをお使いください。
- 引数をハッシュで与える関数
view_filename() - 引数を明示的に指定する関数
_view_filename()
コマンドラインオプション「-C name」でクラス名またはモジュール名を、
「-F name」で関数名またはメソッド名を、
「-A name」で引数を指定できます。
また設定ファイルの「DEFUN_CLASS」でクラス名またはモジュール名を、
「DEFUN_FUNCTION」で関数名またはメソッド名を指定できます。