Kwartzユーザーズガイド

Makoto Kuwata <kwa(at)kuwata-lab.com>
last update: $Date: 2004/05/09 14:25:20 $

はじめに

このドキュメントでは、テンプレートシステムKwartz(*1)について説明します。

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

目次

目次:

更新履歴

2004-05-09
  • "..." is also string literal, in addition to '...'.
  • auto-detect "\n" or "\r\n".
  • command line option "--delete_idattr" which delete id attribute from presentation data.
2004-04-23
  • new language ruby2 and php2
  • updated about 'mkmethod'
2004-04-04
  • utility script 'mkmethod'
  • tips to get result as string
  • fixed 'import()' => 'include()' in PHP
2004-04-01
  • 'include' directive.
  • 'load' directive and :load() statement.
  • function E() and X()
2004-03-21
  • delete only <span> tag and leave <div> tag
2004-03-20
  • special macro BEGIN and END
  • Ruby sanitizing
2004-03-19
  • new command option '--enable_eruby=true'
  • new keyword 'empty'
  • new action 'analyze'
  • new directive notation 'id="foreach:item:list"'
2004-03-13
  • use id attribute instead of kd attribute.
  • add description about new features (id="replace:name" and :value(name=expr))
  • add new samples (navilink, breadcrumbs, error messages, calendar)
2004-03-08
  • subsection 'Compare with null explicitly' added.
  • some eratta fixed
2004-02-27
  • 'Notes' of directives and presentaion language updated
  • description about sanitizing updated
2004-02-24
  • some eratta fixed
2004-02-12
  • public release


Kwartzについて

Kwartzとは?

Kwartz(*2)とはRubyで実装されたテンプレートシステムです。 次のような特徴を持ちます。

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

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

高速な動作

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

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

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

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

Kwartzでは、HTMLテンプレートにおけるマーキング(印付け)をSGMLのタグ形式で行っています。 そのため、Jakarta VelocityTemplate-Toolkit のようにHTMLテンプレートのデザインを崩してしまうことがありません。

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

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

自動サニタイズ機能をサポート

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

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

簡単なサンプル

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

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

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

<table>
  <tr id="xxx">
    <td>#{var}#</td>
  </tr>
</table>

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

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

## 繰り返しを行うようにエレメントを定義しなおす。
:elem(xxx)		## element
  :foreach(var=list)
    @stag		## start tag
    @cont		## content
    @etag		## end tag
  :end
:end

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

### for Ruby
$ kwartz -l ruby  -p example.plogic example.html > example.rb
or
$ kwartz -l ruby2 -p example.plogic example.html > example.rb2

### for PHP
$ kwartz -l php   -p example.plogic example.html > example.php
or
$ kwartz -l php2  -p example.plogic example.html > example.php2

### for JSP
$ kwartz -l jsp   -p example.plogic example.html > example.jsp

### for eRuby
$ kwartz -l eruby -p example.plogic example.html > example.rthml

### for ERB
$ kwartz -l erb   -p example.plogic example.html > example.erb

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

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

Ruby用(example.rb):

print "<table>\n"
for var in list do
  print "  <tr id=\"xxx\">\n"
  print "    <td>", var, "</td>\n"
  print "  </tr>\n"
end
print "</table>\n"

Ruby用(example.rb2) -- 出力を文字列として取り出す:

_s << "<table>\n"
for var in list do
  _s << "  <tr id=\"xxx\">\n"
  _s << "    <td>" << (var).to_s << "</td>\n"
  _s << "  </tr>\n"
end
_s << "</table>\n"

PHP用(example.php):

<table>
<?php foreach ($list as $var) { ?>
  <tr id="xxx">
    <td><?php echo $var; ?></td>
  </tr>
<?php } ?>
</table>

PHP用(example.php2) -- 出力を文字列として取り出す:

<?php  ob_start(); ?>
<table>
<?php foreach ($list as $var) { ?>
  <tr id="xxx">
    <td><?php echo $var; ?></td>
  </tr>
<?php } ?>
</table>
<?php  $_s = ob_get_contents();  ob_end_clean(); ?>

JSP用(*3)(example.jsp):

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

eRuby用(example.rhtml):

<table>
<% for var in list do
 %>  <tr id="xxx">
    <td><%= var %></td>
  </tr>
<% end
 %></table>

ERB用(example.erb):

<table>
% for var in list do
  <tr id="xxx">
    <td><%= var %></td>
  </tr>
% end
</table>

Velocity用(example.vm):

<table>
#foreach ($var in $list)
  <tr id="xxx">
    <td>$!{var}</td>
  </tr>
#end
</table>

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

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

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

require 'cgi'        # for sanitizing
s = File.open('example.rb') { |f| f.read }
s.untaint            # for CGI program
eval s

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

require 'cgi'        # for sanitizing
s = File.open('example.rb2') { |f| f.read }
s.untaint            # for CGI program
_s = ''
eval s
print _s

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

<?php
   include('example.php');
 ?>

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

<?php
   include('example.php2');
   echo $_s;
 ?>

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

public void doGet(HttpServletRequest request,
                  HttpServletResponse response)
                  throws ServletException, IOException {
   ...
   RequestDispatcher dispatcher = 
       request.getRequestDispatcher('example.jsp');
   dispatcher.include(request, response);
   // または dispatcher.forward(request, response);
}

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

require 'eruby'
require 'cgi'        # for sanitizing
ERuby::import('example.rhtml')

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

require 'erb'
include ERB::Util    # for sanitizing
s = File.open('example.erb') { |f| f.read }
s.untaint            # for CGI program
erb = ERB.new(s, $SAFE, '%')
erb.run(binding())   # or print erb.result(binding())

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

Velocity.init();
VelocityContext context = new VelocityContext();
Template template = Velocity.getTemplate("example.vm");
OutputStreamWriter writer = new OutputStreamWriter(System.out);
template.merge(context, writer);
writer.flush();

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

<table>
  <tr id="xxx">
    <td>apple</td>
  </tr>
  <tr id="xxx">
    <td>orange</td>
  </tr>
  <tr id="xxx">
    <td>banana</td>
  </tr>
</table>

この出力結果をみると、属性id="xxx"が残っているのが気になります。 これを消す場合は、id="mark:xxx"と記述するか、コマンドラインオプション--delete_idattr=trueをつけてください。 id属性が出力されないようになります。

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

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

次はプレゼンテーションロジックをプレゼンテーションデータ中に埋め込んだ例です。 このサンプルからは、先ほどのと同じ出力用プログラムが自動生成されます。 .

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


動作詳細

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

Kwartzは次のようなアプローチで、プレゼンテーションデータとプレゼンテーションロジックとを分離しています。

この、プレゼンテーションデータに対して「目印」をつけることをマーキングと呼んでいます。 Kwartzでは、マーキングはid属性(またはkd属性(*4))を用いて「id=name」のように記述します。

マーキングの例:

<table>
 <tr id="list">
  <td>#{item}#</td>
 </tr>
</table>

Kwartzでは、マーキングされたエレメントだけが認識され、マーキングされていないエレメントは通常のプレーンテキストとして扱われます。 そのため、プレゼンテーションデータはHTMLやXMLである必要はなく、どんなテキストでも扱うことができます。 例えば次のような、well-formedでないファイルも問題なく扱えます。

well-formedではないファイルの例:

<span id="test">
 aaa <bbb> ccc </ddd>
</span>
(*4)
名前は'Kwartz Directive'に由来します。属性名はコマンドラインオプション --attr_name を使って変更できます。

中間コード

Kwartzは、プレゼンテーションデータをいったん中間コードへ変換します。これをコンバート(Convert)と呼んでいます。

このとき、Kwartzはマーキングされたエレメントに対し、次のような4つのマクロを自動生成します。

例えば次のようなプレゼンテーションデータがあるとします。

<table>
 <tr id="list">
  <td>#{item}#</td>
 </tr>
</table>

このプレゼンテーションデータは、Kwartzによって次のような中間コードにコンバートされます。 4つのマクロが作成されていること、またそれを利用してプレゼンテーションデータが表現されていることがわかります。

:macro(stag_list)          ## 開始タグのマクロ
  :print(" <tr id=\"list\">\n")
:end

:macro(cont_list)          ## 内容のマクロ
  :print("  <td>", item, "</td>\n")
:end

:macro(etag_list)          ## 終了タグのマクロ
  :print(" </tr>\n")
:end

:macro(elem_list)          ## エレメントのマクロ
  :expand(stag_list)            ## マクロを展開
  :expand(cont_list)            ## マクロを展開
  :expand(etag_list)            ## マクロを展開
:end

:print("<table>\n")        ## プレゼンテーションデータ
:expand(elem_list)              ## マクロを展開
:print("</table>\n")

この例ではマーキングとして「id="list"」を用いたので、id属性が残ったままになっています。 これを「id="mark:list"」とするか、コマンドラインオプションに --delete_idattr=trueをつけると、中間コード変換時にid属性が取り除かれます。

なお、この中間言語は「PL(Presentation Language)」という名前がついています。


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

プレゼンテーションロジックは次のようにして記述します。

次がプレゼンテーションロジックの例です。 マクロelem_listを上書きし、開始タグから終了タグまでを繰り返しています。

:macro(elem_list)
  :foreach(item=itemlist)    ## foreach文による繰り返し
    :expand(stag_item)       ## start tag
    :expand(cont_item)       ## content
    :expand(etag_item)       ## end tag
  :end
:end

なお、このプレゼンテーションロジックは、次のように書くこともできます。 このプレゼンテーションロジックは、Kwartzパーサーによって、上のものと同じように扱われます。

:elem(list)                  ## :macro(elem_item) と同じ
  :foreach(item=itemlist)
    @stag                    ## :expand(stag_item) と同じ
    @cont                    ## :expand(cont_item) と同じ
    @etag                    ## :eppand(etag_item) と同じ
  :end
:end

出力用プログラム

Kwartzは、(プレゼンテーションデータから自動生成した)中間コードと、プレゼンテーションロジックとをマージし、マクロを展開することで、出力用プログラムを生成します。 これをトランスレート(Translate)と呼んでいます。

トランスレートの例を示します。

   
   :print("<table>\n")
   :expand(elem_list)
   :print("</table>\n")

      |
      | マクロを展開
      V

   :print("<table>\n")
   :foreach(item=itemlist)
     :expand(stag_list)
     :expand(cont_list)
     :expand(etag_list)
   :end
   :print("</table>\n")

      |
      | マクロを展開
      V

   :print("<table>\n")
   :foreach(item=itemlist)
     :print(" <tr id=\"list\">\n")
     :print("  <td>", item, "</td>\n")
     :print(" </tr>\n")
   :end
   :print("</table>\n")

      |
      | 出力用プログラムを生成
      V

   ### for Ruby
   print "<table>\n"
   for item in itemlist do
     print " <tr id=\"list\">\n"
     print "  <td>", item, "</td>\n"
     print " </tr>\n"
   end
   print "</table>\n"
 
   ### for PHP
   <table>
   <?php foreach($itemlist as $item) { ?>
    <tr id="list">
     <td><?php echo $item; ?></td>
    </tr>
   <?php } ?>
   </table>

   ### for JSP
   <table>
   <c:forEach var="item" items="${itemlist}">
    <tr id="list">
     <td><c:out value="${item}"/></td>
    </tr>
   </c:forEach>
   </table>

   ### for eRuby
   <table>
   <% for item in itemlist do
    %> <tr id="list">
     <td><%= item %></td>
    </tr>
   <% end
    %></table>

   ### for ERB
   <table>
   % for item in itemlist do
    <tr id="list">
     <td><%= item %></td>
    </tr>
   % end
   </table>

   ### for Velocity
   <table>
   #foreach ($item in $itemlist)
    <tr id="list">
     <td>${item}</td>
    </tr>
   #end
   </table>

ここまでくれば、あとはメインプログラムからこの出力用プログラムを読み込むなり実行するなりすれば、Webページが生成されます。

以上がKwartzの動作になります。


出力用プログラムの呼び出し方

メインプログラムから出力用プログラムを呼び出す方法は、各プログラム言語ごとに異なります。 詳しくは他の文献などを参照してください。

Rubyの場合:

ファイルを読み込み、evalしてください。サニタイズを行う場合はcgi.rbを読み込んでください。

require 'cgi'         # for sanitizing
str = File.open('example.rb') { |f| f.read }
str.untaint           # for CGI program
eval str

またコマンドラインで -l ruby2 と指定した場合は次のようにします。

require 'cgi'         # for sanitizing
str = File.open('example.rb') { |f| f.read }
str.untaint           # for CGI program
_s = ''
eval str
print _s
PHPの場合:

include()関数を使ってください。

<?php include('example.phtml'); ?>

またコマンドラインで -l php2 と指定した場合は次のようにします。

<?php include('example.phtml'); echo $_s; ?>
JSP(Servlet)の場合:

ServletからJSPを呼び出す場合は、RequestDispatcherを用いてください。

RequestDispatcher dispatcher = 
    request.getRequestDispatcher("example.jsp");
dispatcher.forward(request, response);
eRubyの場合:

ERuby::import()で読み込んでください。サニタイズを行う場合はcgi.rbを読み込んでください。

require 'eruby'
require 'cgi'         # for sanitizing
ERuby::import('example.rhtml')
ERBの場合:

ERBオブジェクトを生成し、run()メソッドを実行してください。 またtrimパラメータとして '%' を指定してください。 サニタイズを行う場合はERB::Utilをimportしてください。

require 'erb'
include ERB::Util     # for sanitizing
str = File.open('example.erb') { |f| f.read }
str.untaint
erb = ERB.new(str, $SAFE, '%')
erb.run(binding())   # or print erb.result(binding())
Velocityの場合:

org.apache.velocity.context.Contextやorg.apache.velocity.Templateを使います。 詳しくはVelocityのマニュアルをご覧ください。

Velocity.init();
VelocityContext context = new VelocityContext();
Template template = Velocity.getTemplate("example.vm");
OutputStreamWriter writer = new OutputStreamWriter(System.out);
template.merge(context, writer);
writer.flush();

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

例で示したプレゼンテーションロジックは、開始タグから終了タグまでを繰り返すものでした。 ここでは他の例を紹介します。

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

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


まとめ

kwartsの内部動作です。 名前の終わりに「*」がついているものは自動的に生成されるもの、それ以外は開発者が手動で作成するものです。

  Presentation Data           Presentation Logic
    (HTML)                    (intermediate language)
       |                            |
       | convert                    |
       |                            |
       V                            |
Intermediate Code*                  |
(intermediate language)             |
       |                            |
       |                            |
       +-------------+--------------+
                     | translate
                     |
                     V              call/import
              Output Program*    <============ Main Program
           (Ruby/PHP/JSP/eRuby)             (Ruby/PHP/JSP/eRuby)
                     |
                     | output
                     |
                     V
                 Web Page*
                  (HTML)

開発者が手動で作成するもの:

自動生成されるもの:

用語:

コンバート(Convert)
テンプレートファイルを中間コードに変換すること。
トランスレート(Translate)
中間コードとプレゼンテーションロジックとをマージして出力用プログラムに変換すること。
コンパイル(Compile)
コンバートしてトランスレートすること。 つまりプレゼンテーションデータとプレゼンテーションロジックから出力用ファイルを生成すること。


ディレクティブ

ディレクティブとは?

Kwartzでは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むこともできます。 そのための命令を「ディレクティブ(Directive)」といいます。

ディレクティブとは、プレゼンテーションデータの中に埋め込む命令であり、プレゼンテーションロジックを表します。 今まで見てきたマーキング「id="name"」や「id="mark:name"」も、ディレクティブのひとつです。 他に、繰り返しや条件分岐のためのディレクティブが用意されています。

Kwartzでは、ディレクティブはid属性またはkd属性を使用します。 通常はid属性を使用しますが、id属性を他の用途で使用した場合にはkd属性を使用します。また両方を同時に使用することもできます。 id属性とkd属性の使い分けについては、「id属性とkd属性について」をご覧ください。

Kwartzではせっかくプレゼンテーションデータとプレゼンテーションロジックとを分離することができるのに、なぜ両者を一体化するような機能も用意されているのでしょう?

それは、開発者の好みに応じてどちらも選べるようにするため、つまり選択肢を増やすためです。 Kwartzは、開発者にどちらか一方を押しつけるようなことはしません。 分離するほうが望ましいのであれば分離すればよいし、一体化するほうが好みであれば一体化すればよいのです。

また両者を一体化すると、他のテンプレートと差別化できないと思われるかもしれません。 しかし一体化してもなお、Kwartzは他のテンプレートと比べて次のような利点があります。


マーキング

id="name"」または「id="mark:name"」とすることで、マーキングを行います。 マーキングとは、エレメントに対してその名前で目印をつけることです。 マーキングされたエレメントは、中間コードにおいてマクロで表現されます。

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

<li id="item">foo</li>

中間コード:

:macro(stag_item)
  :print("<li id=\"item\">")
:end

:macro(cont_item)
  :print("foo")
:end

:macro(etag_item)
  :print("</li>\n")
:end

:macro(elem_item)
  :expand(stag_item)
  :expand(cont_item)
  :expand(etag_item)
:end

:expand(elem_item)

出力用プログラム:

### Ruby
print "<li id=\"item\">"
print "foo"
print "</li>\n"

### PHP
<li id="item">foo</li>

### JSP + JSTL
<li id="item">foo</li>

### eRuby
<li id="item">foo</li>

### ERB
<li id="item">foo</li>

### Velocity
<li id="item">foo</li>

id="name"」と「id="mark:name"」との違いは、中間コードにコンバートされるときに前者ではid属性が残るのに対し後者は取り除かれるという点です。

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

<li id="mark:item">foo</li>

中間コード:

:macro(stag_item)
  :print("<li>")
:end

:macro(cont_item)
  :print("foo")
:end

:macro(etag_item)
  :print("</li>\n")
:end

:macro(elem_item)
  :expand(stag_item)
  :expand(cont_item)
  :expand(etag_item)
:end

:expand(elem_item)

値の出力

#{expression}#」は、式の値を出力するコマンドです。 Rubyなら「print expression」に、PHPなら「<?php echo expression; ?>」に相当します。

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

Hello #{user}#!

中間コード:

:print("Hello ", user, "!\n")

出力用プログラム:

### Ruby
print "Hello ", user, "!\n"

### PHP
Hello <?php echo $user; ?>!

### JSP + JSTL
Hello <c:out value="${user}" escapeXml="false"/>!

### eRuby
Hello <%= user %>!

### ERB
Hello <%= user %>!

### Velocity
Hello $!{user}!

コンパイル時に、コマンドオプション-s(または--escape=true)を指定した場合は、式をサニタイズして出力します。 このとき、文字列や数値はサニタイズされず、式のみがサニタイズされることに注意してください。

出力用プログラム(コマンドオプション-sをつけた場合):

### Ruby
print "Hello ", CGI.escapeHTML((user).to_s), "!\n"

### PHP
Hello <?php echo htmlspecialchars($user); ?>!

### JSP + JSTL
Hello <c:out value="${user}"/>!

### eRuby
Hello <%= CGI.escapeHTML((user).to_s) %>!

### ERB
Hello <%= html_escape(user) %>!

### Velocity
Hello $!{user}!

コマンドオプションの有無に関わらずサニタイズをする/しないを指定するには、関数E()とX()を使用します。 E(expr)は式exprをサニタイズし、X(expr)はサニタイズしません。

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

With sanitizing:    #{E(expr)}#!
Without sanitizing: #{X(expr)}#!

中間コード:

:print("With sanitizing:    ", E(expr), "\n")
:print("Without sanitizing: ", X(expr), "\n")

出力用プログラム:

### Ruby
print "With sanitizing:    ", CGI.escapeHTML((expr).to_s), "!\n"
print "Without sanitizing: ", expr, "!\n"

### PHP
With sanitizing:    <?php echo htmlspecialchars($expr); ?>!
Without sanitizing: <?php echo $expr; ?>!

### JSP + JSTL
With sanitizing:    <c:out value="${expr}"/>!
Without sanitizing: <c:out value="${expr}" escapeXml="false"/>!

### eRuby
With sanitizing:    <%= CGI.escapeHTML((expr).to_s) %>!
Without sanitizing: <%= expr %>!

### ERB
With sanitizing:    <%= html_escape(expr) %>!
Without sanitizing: <%= expr %>!

### Velocity
With sanitizing:    $!{expr}!
Without sanitizing: $!{expr}!

値の出力2

id="value:expression"」とすることで、内容のかわりに式の値を出力します。 ダミーデータを利用できるため、「#{expression}#」と比べてHTMLデザインがより崩れません。

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

<li id="value:hash['name']">foo</li>

中間コード:

:print("<li>", hash['name'], "</li>\n")

出力用プログラム:

### Ruby
print "<li>", hash["name"], "</li>\n"

### PHP
<li><?php echo $hash['name']; ?></li>

### JSP + JSTL
<li><c:out value="${hash['name']}" escapeXml="false"/></li>

### eRuby
<li><%= hash['name'] %></li>

### ERB
<li><%= hash['name'] %></li>

### Velocity
<li>$!{hash.get('name')}</li>

また「id="Value:expr」でサニタイズすることを、「id="VALUE:expr」でサニタイズしないことを指定できます。 これらはそれぞれ「id="value:E(expr)」や「id="value:X(expr)」と同じです。


属性値の設定

id="attr:name=value"」(または「id="attr:name:value"」)とすることで、タグの属性値を設定できます。 これを用いると、「#{...}#」を使わずに属性値を設定でき、またダミーの属性値を設定できます。

次の例では、属性classにダミーの属性値 "odd" を設定していますが、実際には変数 klass の値を用いるようにしています。

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

<tr class="odd" id="attr:class=klass">
  <td>foo</td>
</tr>

中間コード:

:print("<tr class=\"", klass, "\">\n")
:print("  <td>foo</td>\n")
:print("</tr>\n")

出力用プログラム:

### Ruby
print "<tr class=\"", klass, "\">\n"
print "  <td>foo</td>\n"
print "</tr>\n"

### PHP
<tr class="<?php echo $klass; ?>">
  <td>foo</td>
</tr>

### JSP + JSTL
<tr class="<c:out value="${klass}" escapeXml="false"/>">
  <td>foo</td>
</tr>

### eRuby
<tr class="<%= klass %>">
  <td>foo</td>
</tr>

### ERB
<tr class="<%= klass %>">
  <td>foo</td>
</tr>

### Velocity
<tr class="$!{klass}">
  <td>foo</td>
</tr>

また「id="Attr:name=value」でサニタイズすることを、「id="ATTR:name=value」でサニタイズしないことを指定できます。 これらはそれぞれ「id="attr:name=E(value)」や「id="attr:name=X(value)」と同じです。

なお「;」で区切ることにより、「attr:name=value」を複数設定したり他のテンプレートコマンドと一緒に設定したりできます。

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

<font id="if:message;attr:class=klass;attr:bgcolor=color">
 #{message}#
</font>

中間コード:

:if(message)
  :print("<font class=\"", klass, "\" bgcolor=\"", color, "\">\n")
  :print(" ", message, "\n")
  :print("</font>\n")
:end

値の設定

id="set:var=value」(または「id="set:var:value」)は、変数varに値valueを設定します。 「=」だけでなく「+=」「-=」「*=」「/=」「.+=」が使用できます (「.+=」は文字列の連結を表します)。

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

<dt id="set:var=value">foo</dt>
<dd id="set:count+=1">123</dd>

中間コード:

:set(var=value)
:print("<dt>")
:print("foo")
:print("</dt>\n")
:set(count+=1)
:print("<dd>")
:print("123")
:print("</dd>\n")

出力用プログラム:

### Ruby
var = value
print "<dt>"
print "foo"
print "</dt>\n"
count += 1
print "<dd>"
print "123"
print "</dd>\n"

### PHP
<?php $var = $value; ?>
<dt>foo</dt>
<?php $count += 1; ?>
<dd>123</dd>

### JSP + JSTL
<c:set var="var" value="${value}"/>
<dt>foo</dt>
<c:set var="count" value="${count + 1}"/>
<dd>123</dd>

### eRuby
<% var = value
 %><dt>foo</dt>
<% count += 1
 %><dd>123</dd>

### ERB
% var = value
<dt>foo</dt>
% count += 1
<dd>123</dd>

### Velocity
#set ($var = $value)
<dt>foo</dt>
#set ($count = $count + 1)
<dd>123</dd>

条件分岐

id="if:expression"」とすることで、条件分岐を行います。elseやelse ifは表現できません。 elseやelse ifを使いたい場合は、プレゼンテーションロジックを別ファイルに用意してください。

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

<font color="red" id="if:flag_error">
 Error!!
</font>

中間コード:

:if(flag_error)
  :print("<font color=\"red\">\n")
  :print(" Error!!\n")
  :print("</font>\n")
:end

出力用プログラム:

### Ruby
if flag_error then
  print "<font color=\"red\">\n"
  print " Error!!\n"
  print "</font>\n"
end

### PHP
<?php if ($flag_error) { ?>
<font color="red">
 Error!!
</font>
<?php } ?>

### JSP + JSTL
<c:choose>
<c:when test="${flag_error}">
<font color="red">
 Error!!
</font>
</c:when>
</c:choose>

### eRuby
<% if flag_error then
 %><font color="red">
 Error!!
</font>
<% end
 %>

### ERB
% if flag_error then
<font color="red">
 Error!!
</font>
% end

### Velocity
#if ($flag_error)
<font color="red">
 Error!!
</font>
#end

繰り返し(foreach)

id="foreach:var=list"」(または「id="foreach:var:list"」)とすることで、リストlistの要素をひとつずつ変数varに代入しながら、繰り返しを行います。

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

<tr id="foreach:user=user_list">
  <td>#{user}#</td>
</tr>

中間コード:

:foreach(user = user_list)
  :print("<tr>\n")
  :print("  <td>", user, "</td>\n")
  :print("</tr>\n")
:end

出力用プログラム:

### Ruby
for user in user_list do
  print "<tr>\n"
  print "  <td>", user, "</td>\n"
  print "</tr>\n"
end

### PHP
<?php foreach ($user_list as $user) { ?>
<tr>
  <td><?php echo $user; ?></td>
</tr>
<?php } ?>

### JSP + JSTL
<c:forEach var="user" items="${user_list}">
<tr>
  <td><c:out value="${user}" escapeXml="false"/></td>
</tr>
</c:forEach>

### eRuby
<% for user in user_list do
 %><tr>
  <td><%= user %></td>
</tr>
<% end
 %>

### ERB
% for user in user_list do
<tr>
  <td><%= user %></td>
</tr>
% end

### Velocity
#foreach ($user in $user_list)
<tr>
  <td>$!{user}</td>
</tr>
#end

繰り返し(list)

id="list:var=list"」(または「id="list:var:list"」)とすることで、リストlistの要素をひとつずつ変数varに代入しながら繰り返しを行います。 「id="foreach:var=list"」とよく似ていますが、開始タグと終了タグを含めず、内容だけ繰り返す点が異なります。

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

<tr id="list:user=user_list">
  <td>#{user}#</td>
</tr>

中間コード:

:print("<tr>\n")
:foreach(user = user_list)
  :print("  <td>", user, "</td>\n")
:end
:print("</tr>\n")

出力用プログラム:

### Ruby
print "<tr>\n"
for user in user_list do
  print "  <td>", user, "</td>\n"
end
print "</tr>\n"

### PHP
<tr>
<?php foreach ($user_list as $user) { ?>
  <td><?php echo $user; ?></td>
<?php } ?>
</tr>

### JSP + JSTL
<tr>
<c:forEach var="user" items="${user_list}">
  <td><c:out value="${user}" escapeXml="false"/></td>
</c:forEach>
</tr>

### eRuby
<tr>
<% for user in user_list do
 %>  <td><%= user %></td>
<% end
 %></tr>

### ERB
<tr>
% for user in user_list do
  <td><%= user %></td>
% end
</tr>

### Velocity
<tr>
#foreach ($user in $user_list)
  <td>$!{user}</td>
#end
</tr>

カウンタつき繰り返し(Foreach,List)

id="Foreach:var=list"」または「id="List:var=list"」を使うと、カウンタを増やしながら繰り返しを行います。 カウンタは1から始まり、変数名はvar_ctrです。

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

<tr id="Foreach:item=item_list">
  <td id="value:item_ctr">1</td>
  <td id="value:item">foo</td>
</tr>

中間コード:

:set(item_ctr = 0)
:foreach(item = item_list)
  :set(item_ctr += 1)
  :print("<tr>\n")
  :print("  <td>", item_ctr, "</td>\n")
  :print("  <td>", item, "</td>\n")
  :print("</tr>\n")
:end

出力用プログラム:

### Ruby
item_ctr = 0
for item in item_list do
  item_ctr += 1
  print "<tr>\n"
  print "  <td>", item_ctr, "</td>\n"
  print "  <td>", item, "</td>\n"
  print "</tr>\n"
end

### PHP
<?php $item_ctr = 0; ?>
<?php foreach ($item_list as $item) { ?>
  <?php $item_ctr += 1; ?>
<tr>
  <td><?php echo $item_ctr; ?></td>
  <td><?php echo $item; ?></td>
</tr>
<?php } ?>

### JSP + JSTL
<c:set var="item_ctr" value="0"/>
<c:forEach var="item" items="${item_list}">
 <c:set var="item_ctr" value="${item_ctr + 1}"/>
<tr>
  <td><c:out value="${item_ctr}" escapeXml="false"/></td>
  <td><c:out value="${item}" escapeXml="false"/></td>
</tr>
</c:forEach>

### eRuby
<% item_ctr = 0
 %><% for item in item_list do
 %><% item_ctr += 1
 %><tr>
  <td><%= item_ctr %></td>
  <td><%= item %></td>
</tr>
<% end
 %>

### ERB
% item_ctr = 0
% for item in item_list do
%   item_ctr += 1
<tr>
  <td><%= item_ctr %></td>
  <td><%= item %></td>
</tr>
% end

### Velocity
#set ($item_ctr = 0)
#foreach ($item in $item_list)
  #set ($item_ctr = $item_ctr + 1)
<tr>
  <td>$!{item_ctr}</td>
  <td>$!{item}</td>
</tr>
#end

トグルつき繰り返し(FOREACH,LIST)

id="FOREACH:var=list"」または「id="LIST:var=list"」とすると、トグルつきの繰り返しを行うことができます。 トグルは、繰り返し回数が奇数回か偶数回かによって、値が入れ替わります。 トグル変数の名前はvar_tglです。 トグルの値はデフォルトで「'odd'」と「'even'」が使われますが、コマンドオプション --odd_value と --even_value で指定できます。

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

<table>
  <tbody id="LIST:item=item_list">
    <tr class="#{item_tgl}#">
      <td id="value:item">foo</td>
    </tr>
  </tbody>
</table>

中間コード:

:print("<table>\n")
:print("  <tbody>\n")
:set(item_ctr = 0)
:foreach(item = item_list)
  :set(item_ctr += 1)
  :set(item_tgl = item_ctr % 2 == 0 ? 'even' : 'odd')
  :print("    <tr class=\"", item_tgl, "\">\n")
  :print("      <td>", item, "</td>\n")
  :print("    </tr>\n")
:end
:print("  </tbody>\n")
:print("</table>\n")

出力用プログラム:

### Ruby
print "<table>\n"
print "  <tbody>\n"
item_ctr = 0
for item in item_list do
  item_ctr += 1
  item_tgl = (item_ctr % 2 == 0 ? "even" : "odd")
  print "    <tr class=\"", item_tgl, "\">\n"
  print "      <td>", item, "</td>\n"
  print "    </tr>\n"
end
print "  </tbody>\n"
print "</table>\n"

### PHP
<table>
  <tbody>
<?php $item_ctr = 0; ?>
<?php foreach ($item_list as $item) { ?>
  <?php $item_ctr += 1; ?>
  <?php $item_tgl = ($item_ctr % 2 == 0 ? 'even' : 'odd'); ?>
    <tr class="<?php echo $item_tgl; ?>">
      <td><?php echo $item; ?></td>
    </tr>
<?php } ?>
  </tbody>
</table>

### JSP + JSTL
<table>
  <tbody>
<c:set var="item_ctr" value="0"/>
<c:forEach var="item" items="${item_list}">
 <c:set var="item_ctr" value="${item_ctr + 1}"/>
 <c:choose>
<c:when test="${item_ctr % 2 == 0}">
  <c:set var="item_tgl" value="even"/>
 </c:when>
 <c:otherwise>
  <c:set var="item_tgl" value="odd"/>
 </c:otherwise>
 </c:choose>
    <tr class="<c:out value="${item_tgl}" escapeXml="false"/>">
      <td><c:out value="${item}" escapeXml="false"/></td>
    </tr>
</c:forEach>
  </tbody>
</table>

### eRuby
<table>
  <tbody>
<% item_ctr = 0
 %><% for item in item_list do
 %><% item_ctr += 1
 %><% item_tgl = (item_ctr % 2 == 0 ? 'even' : 'odd')
 %>    <tr class="<%= item_tgl %>">
      <td><%= item %></td>
    </tr>
<% end
 %>  </tbody>
</table>

### ERB
<table>
  <tbody>
% item_ctr = 0
% for item in item_list do
%   item_ctr += 1
%   item_tgl = (item_ctr % 2 == 0 ? 'even' : 'odd')
    <tr class="<%= item_tgl %>">
      <td><%= item %></td>
    </tr>
% end
  </tbody>
</table>

### Velocity
<table>
  <tbody>
#set ($item_ctr = 0)
#foreach ($item in $item_list)
  #set ($item_ctr = $item_ctr + 1)
  #if ($item_ctr % 2 == 0)
    #set ($item_tgl = 'even')
  #else
    #set ($item_tgl = 'odd')
  #end
    <tr class="$!{item_tgl}">
      <td>$!{item}</td>
    </tr>
#end
  </tbody>
</table>

繰り返し(while)

id="while:expression"」とすることで、繰り返しを行うことができます。 なおKwartzでは、代入文は式(expression)ではありませんが、whileの条件式に限っては式として代入文を書くことができます。

またJSTLやVelocityでは、whileを変換しようとするとエラーになります。 これは、whilteに対応するカスタムタグやVelocityディレクティブがないからです。

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

<tr id="while:row=sth.fetch">
  <td>#{row[0]}#</td>
</tr>

中間コード:

:while(row=sth.fetch)
  :print("<tr>\n")
  :print("  <td>", row[0], "</td>\n")
  :print("</tr>\n")
:end

出力用プログラム:

### Ruby
while row = sth.fetch do
  print "<tr>\n"
  print "  <td>", row[0], "</td>\n"
  print "</tr>\n"
end

### PHP
<?php while ($row = $sth->fetch) { ?>
<tr>
  <td><?php echo $row[0]; ?></td>
</tr>
<?php } ?>

### eRuby
<% while row = sth.fetch do
 %><tr>
  <td><%= row[0] %></td>
</tr>
<% end
 %>

### ERB
% while row = sth.fetch do
<tr>
  <td><%= row[0] %></td>
</tr>
% end

ダミーデータ

id="dummy:name"」とすることで、そのノードをダミーデータとみなして読み飛ばします。 ここでnameには、重複しないような適当な文字列(*5)を入れてください。 ここでnameは、ひとつのXML文書の中に同じid属性値が使われないようにするためのものです。 重複しないような適当な文字列を入れてください。

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

<tr id="dummy:d1">
  <td>bar</td>
</tr>
<tr id="dummy:d2">
  <td>baz</td>
</tr>

中間コード:

                          ## 何も出力されない

出力用プログラム:

                          ## 何も出力されない
(*5)
この文字列自体には、特に意味はありません。

エレメントの置換

id="replace:name"」とすることで、そのエレメントを別のエレメントに置き換えることができます。

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

<html>
  <body>
    <!-- ナビゲーション -->
    <span id="breadclumbs">
      <a href="/">Home</a>
      &gt; <a href="/ruby">Ruby</a>
      &gt; <a href="/ruby/kwartz">Kwartz</a>
    </span>

   ....

    <span id="replace:breadclumbs">
      Home &gt; Ruby &gt; Kwartz
    </span>
  </body>
</html

中間コード:

:macro(stag_breadclumbs)
  :print("    <span id=\"breadclumbs\">\n")
:end

:macro(cont_breadclumbs)
  :print("      <a href=\"/\">Home</a>\n")
  :print("      &gt; <a href=\"/ruby\">Ruby</a>\n")
  :print("      &gt; <a href=\"/ruby/kwartz\">Kwartz</a>\n")
:end

:macro(etag_breadclumbs)
  :print("    </span>\n")
:end

:macro(elem_breadclumbs)
  :expand(stag_breadclumbs)
  :expand(cont_breadclumbs)
  :expand(etag_breadclumbs)
:end

:print("<html>\n")
:print("  <body>\n")
:print("    <!-- ナビゲーション -->\n")
:expand(elem_breadclumbs)

   ....

:expand(elem_breadclumbs)
:print("  </body>\n")
:print("</html\n")

プレゼンテーションデータファイルの読み込み

id="include:'filename'"」で他のプレゼンテーションデータファイル(HTMLファイル)を読み込むことができます。 通常は<span/>タグを使用して「<span id="include:'filename'"/>」のようにします(Kwartzでは、ディレクティブしか含まない<span>タグは自動的に削除されます)。

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

<span style="border-width:1 1 0 1; border-style:solid; padding:1 5 1 5">
  <a href="#{tab['url']}#" id="value:tab['name']" style="text-decoration:none">TabName</a>
</span>

プレゼンテーションデータ(tab-main.html):

<div id="foreach:tab:tablist">
  <span id="include:'tab.html'"/>
</div>

中間コード:

:foreach(tab = tablist)
  :print("<div>\n")
  ## :print("  <span>")
  :print("<span style=\"border-width:1 1 0 1; border-style:solid; padding:1 5 1 5\">\n")
  :print("  <a href=\"", tab['url'], "\" style=\"text-decoration:none\">", tab['name'], "</a>\n")
  :print("</span>\n")
  ## :print("</span>\n")
  :print("</div>\n")
:end

includeディレクティブで読み込むファイルは、別ディレクトリに置くことができます。 ファイルが置いてあるディレクトリは、コマンドラインオプション --include_path=dir1,dir2,... で指定できます。

$ kwartz --include_path=dir1,dir2 tab-main.html

プレゼンテーションロジックファイルの読み込み

id="load:'filename'"」で他のプレゼンテーションロジックファイルを読み込むことができます。 通常は<span/>タグを使用して「<span id="load:'filename'"/>」のようにします(Kwartzでは、ディレクティブしか含まない<span>タグは自動的に削除されます)。

通常、「どのプレゼンテーションロジックファイルを使用するか?」はコマンドオプション-pまたは-Pで指定します。 しかしloadディレクティブを用いると、それをプレゼンテーションデータの中で指定することができます。 またloadディレクティブとコマンドオプションとを同時に使用することもできます。

loadディレクティブはプレゼンテーションデータファイルの最後に書いてください。 さもないと、読み込んだプレゼンテーションロジックがプレゼンテーションデータファイルによって上書きされることがあります。

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

:elem(item)
  :foreach(item=itemlist)
    @stag
    @cont
    @etag
  :end
:end

:macro(BEGIN)
  :print("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n")
  :print("<html lang=\"ja\">\n")
  :print(" <body bgcolor=\"#FFFFFF\">\n")
:end

:macro(END)
  :print(" </body>\n")
  :print("<html>\n")
:end

プレゼンテーションデータ(load-main.html):

<ul id="mark:itemlist">
  <li id="value:item">foobar</li>
</ul>
<span id="load:'load-lib.plogic'"/>

中間コード:

:macro(stag_itemlist)
  :print("<ul>\n")
:end

:macro(cont_itemlist)
  :print("  <li>", item, "</li>\n")
:end

:macro(etag_itemlist)
  :print("</ul>\n")
:end

:macro(elem_itemlist)
  :expand(stag_itemlist)
  :expand(cont_itemlist)
  :expand(etag_itemlist)
:end

:expand(elem_itemlist)
## :print("<span>")
:load('load-lib.plogic')
## :print("</span>\n")

出力用プログラム:

### Ruby
print "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
print "<html lang=\"ja\">\n"
print " <body bgcolor=\"\#FFFFFF\">\n"
print "<ul>\n"
print "  <li>", item, "</li>\n"
print "</ul>\n"
print " </body>\n"
print "<html>\n"

### PHP
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
 <body bgcolor="#FFFFFF">
<ul>
  <li><?php echo $item; ?></li>
</ul>
 </body>
<html>

### JSP + JSTL
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
 <body bgcolor="#FFFFFF">
<ul>
  <li><c:out value="${item}" escapeXml="false"/></li>
</ul>
 </body>
<html>

### eRuby
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
 <body bgcolor="#FFFFFF">
<ul>
  <li><%= item %></li>
</ul>
 </body>
<html>

### ERB
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
 <body bgcolor="#FFFFFF">
<ul>
  <li><%= item %></li>
</ul>
 </body>
<html>

### Velocity
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
 <body bgcolor="#FFFFFF">
<ul>
  <li>$!{item}</li>
</ul>
 </body>
<html>

loadディレクティブで読み込むファイルは、別ディレクトリに置くことができます。 ファイルが置いてあるディレクトリは、コマンドラインオプション --load_path=dir1,dir2,... で指定できます。

$ kwartz --load_path=dir1,dir2 load-main.html

id属性とkd属性について

Kwartzでは、ディレクティブを記述するのにid属性とkd属性が使用できます。 ここでは、両者の使い分けについて説明します。


注意事項

ディレクティブを使用する際の注意事項です。



プレゼンテーション用言語「PL」

Kwartzが使用している中間言語を、PL(Presentation Language)といいます。 PLは、次の2つの用途で使用されます。

つまり、KwartzではプレゼンテーションデータもプレゼンテーションロジックもPLで表現されるわけです。

この章では、PLの書き方について説明します。

コメント

#」から改行まではコメントになります。 ただし、将来は「#*」や「#@」などを特別扱いする可能性があります。 そのため、できればコメントは「##」にしてください。

## ここはコメント

文字列

文字列は「"..."」または「'...'」で表します。 前者には改行(Line Feed)「\n」と復改(Carriage Returen)「\r」とタブ(Tab)「\t」を含めることができます。

'foo bar'		## 文字列
"foo bar\n"		## 改行を含む文字列

真偽値、null

式の中で truefalsenull が使えます。

:set(flag = obj == null? true : false)

truefalsenullは各言語において次のように変換されます。

truefalsenullの変換
変換先言語 true false null
Ruby,eRuby,ERB true false nil
PHP TRUE FALSE NULL
JSP(JSTL) true false null
Velocity true false -

なおVelocityではnullやそれに類するキーワードがありません。 そのため、nullを含むPLプログラムをVelocityに変換するとエラーになります。 ただしnullとの比較を行う論理式は、nullを使わない式に変換されます。

PLプログラム:

:set(flag = obj == null? true : false)

:if (expr != null)
  :print("expr is not null.\n")
:else
  :print("epxr is null.\n")
:end

出力用スクリプト:

### Velocity
#if (! $obj)
  #set ($flag = true)
#else
  #set ($flag = false)
#end
#if ($expr)
expr is not null.
#else
epxr is null.
#end

変数

変数はJavaやRubyと同じように、アルファベットと数字と「_」が連続したものです。 ただし先頭の文字はアルファベットまたは「_」でなくてはいけません。

また変数には型がなく、変数宣言もする必要がありません(*6)

(*6)
この性質から、変数に型のある言語や変数宣言が必要な言語にKwartzを対応させるのは困難です。

演算子

演算子には次のものが使用できます。基本的には、文字列用と数値用とで演算子を分けてはいません(*7)

比較演算子 == != < <= > >=
論理演算子 && || !
算術演算子 + - * / %
文字列の連結 .+
条件演算子 ?:

条件演算子(3項演算子)は、RubyやPHPでは使用できますが、JSP(厳密にいうとJSTLの式言語)やVelocityでは使用できません。 そのため、条件演算子を含む式は、JSPやVelocityではif文(やそれに相当する<c:choose>)に変換されます。

PLプログラム:

:set(color = ctr%2==0 ? '#CCCCFF' : '#FFCCCC')

出力用スクリプト:

### Ruby
color = (ctr % 2 == 0 ? "\#CCCCFF" : "\#FFCCCC")

### PHP
<?php $color = ($ctr % 2 == 0 ? '#CCCCFF' : '#FFCCCC'); ?>

### JSP + JSTL
<c:choose>
<c:when test="${ctr % 2 == 0}">
 <c:set var="color" value="#CCCCFF"/>
</c:when>
<c:otherwise>
 <c:set var="color" value="#FFCCCC"/>
</c:otherwise>
</c:choose>

### eRuby
<% color = (ctr % 2 == 0 ? '#CCCCFF' : '#FFCCCC')
 %>

### ERB
% color = (ctr % 2 == 0 ? '#CCCCFF' : '#FFCCCC')

### Velocity
#if ($ctr % 2 == 0)
  #set ($color = '#CCCCFF')
#else
  #set ($color = '#FFCCCC')
#end
(*7)
Perlのように文字列用と数値用とで演算子が異なる言語に対しては、Kwartzを対応させるのは困難です。

出力

出力は「:print(...)」で表します。この中には任意の式を書くことができます。

PLプログラム:

:print('foo', bar, "baz\n")   ## 文字列と変数を出力

出力用スクリプト:

### Ruby
print "foo", bar, "baz\n"

### PHP
foo<?php echo $bar; ?>baz

### JSP + JSTL
foo<c:out value="${bar}" escapeXml="false"/>baz

### eRuby
foo<%= bar %>baz

### ERB
foo<%= bar %>baz

### Velocity
foo$!{bar}baz

自動サニタイズ機能を使用した場合は、Ruby、PHP、eRuby、ERBでは次のような出力になります。

### Ruby
print "foo", CGI.escapeHTML((bar).to_s), "baz\n"

### PHP
foo<?php echo htmlspecialchars($bar); ?>baz

### eRuby
foo<%= CGI.escapeHTML((bar).to_s) %>baz

### ERB
foo<%= html_escape(bar) %>baz

代入

代入は「:set(var=value)」です。 「(」や「=」の前後に空白を入れても構いません。

また、「+=」「-=」「*=」「/=」「%=」「.+=」も使用できます。 最後の「.+=」は文字列の連結を表します。

PLプログラム:

:set(name = 'Foo')      ## 変数に文字列'Foo'を代入
:set(count += 1)        ## 変数の値を1増やす
:set(str .+= '.txt')    ## 文字列の末尾に追加

出力用スクリプト:

### Ruby
name = "Foo"
count += 1
str << ".txt"

### PHP
<?php $name = 'Foo'; ?>
<?php $count += 1; ?>
<?php $str .= '.txt'; ?>

### JSP + JSTL
<c:set var="name" value="Foo"/>
<c:set var="count" value="${count + 1}"/>
<c:set var="str" value="${str}${'.txt'}"/>

### eRuby
<% name = 'Foo'
 %><% count += 1
 %><% str << '.txt'
 %>

### ERB
% name = 'Foo'
% count += 1
% str << '.txt'

### Velocity
#set ($name = 'Foo')
#set ($count = $count + 1)
#set ($str = "${str}.txt")

配列、ハッシュ

配列は「arr[expr]」の形で参照できます。 ハッシュも同じ形で参照できます(*8)

PLプログラム:

:set(list[n] = 10)         ## n番目の要素に代入
:print(list[i], "\n")      ## i番目の要素を出力
:set(hash['key'] = 'foo')  ## ハッシュの要素に代入
:print(hash['key'])        ## ハッシュの要素を出力

出力用スクリプト:

### Ruby
list[n] = 10
print list[i], "\n"
hash["key"] = "foo"
print hash["key"]

### PHP
<?php $list[$n] = 10; ?>
<?php echo $list[$i]; ?>
<?php $hash['key'] = 'foo'; ?>
<?php echo $hash['key']; ?>

### JSP + JSTL
<c:set var="list[n]" value="10"/>
<c:out value="${list[i]}" escapeXml="false"/>
<c:set var="hash['key']" value="foo"/>
<c:out value="${hash['key']}" escapeXml="false"/>

### eRuby
<% list[n] = 10
 %><%= list[i] %>
<% hash['key'] = 'foo'
 %><%= hash['key'] %>

### ERB
% list[n] = 10
<%= list[i] %>
% hash['key'] = 'foo'
<%= hash['key'] %>

### Velocity
#set ($list.get($n) = 10)
$!{list.get($i)}
#set ($hash.key = 'foo')
$!{hash.get('key')}

ハッシュは、「hash[:key]」という形でも参照できます。 このとき、「key」は英数字と「_」のみからなる文字列である必要があります。

hash[:key]」は、各プログラム言語に応じて次のように変換されます。

ターゲット言語 変換結果
Ruby, eRuby, ERB hash[:key]
PHP $hash['key']
JSP hash['key']
Velocity hash.key

PLプログラム:

:set(hash[:key] = 'foo')
:print(hash[:key])

出力用スクリプト:

### Ruby
hash[:key] = "foo"
print hash[:key]

### PHP
<?php $hash['key'] = 'foo'; ?>
<?php echo $hash['key']; ?>

### JSP + JSTL
<c:set var="hash['key']" value="foo"/>
<c:out value="${hash['key']}" escapeXml="false"/>

### eRuby
<% hash[:key] = 'foo'
 %><%= hash[:key] %>

### ERB
% hash[:key] = 'foo'
<%= hash[:key] %>

### Velocity
#set ($hash.key = 'foo')
$!{hash.key}
(*8)
配列とハッシュとで同じ演算子を用いているため、Perlのように異なる演算子を必要とするプログラミング言語への変換は困難です。

プロパティ

オブジェクトのプロパティは、「object.property」の形式で参照します。 プロパティはメソッド呼び出しではないので、引数をとることはできません。

またPLではオブジェクトやプロパティを参照することはできても、オブジェクトを自分で生成したりクラスを定義したりすることはできません。 それらはPLの外部で(つまりメインプログラムの中で)行われることになります。

PLプログラム:

:print(user.name)

出力用スクリプト:

### Ruby
print user.name

### PHP
<?php echo $user->name; ?>

### JSP + JSTL
<c:out value="${user.name}" escapeXml="false"/>

### eRuby
<%= user.name %>

### ERB
<%= user.name %>

### Velocity
$!{user.name}

繰り返し

繰り返しは「:foreach(var=list) ... :end」で表します。 いわゆるforeach文であり、配列listの要素をひとつひとつvarに代入しながら繰り返しを行います。

PLプログラム:

:foreach(item = list)      ## listの要素をitemに代入しながら
  :print(item, "\n")       ## 繰り返し出力する
:end

出力用スクリプト:

### Ruby
for item in list do
  print item, "\n"
end

### PHP
<?php foreach ($list as $item) { ?>
<?php echo $item; ?>
<?php } ?>

### JSP + JSTL
<c:forEach var="item" items="${list}">
<c:out value="${item}" escapeXml="false"/>
</c:forEach>

### eRuby
<% for item in list do
 %><%= item %>
<% end
 %>

### ERB
% for item in list do
<%= item %>
% end

### Velocity
#foreach ($item in $list)
$!{item}
#end

またwhile文も使用できます。 ただし、JSTLにはwhile文に相当するカスタムタグがないため、JSPへ変換しようとするとエラーになります。 またVelocityもwhile文に相当するVelocityディレクティブがないため、変換時にエラーとなります。

PLプログラム:

:set(i = 0)
:while(i < length)
  :print(list[i])
  :set(i += 1)
:end

出力用スクリプト:

### Ruby
i = 0
while i < length do
  print list[i]
  i += 1
end

### PHP
<?php $i = 0; ?>
<?php while ($i < $length) { ?>
<?php echo $list[$i]; ?><?php $i += 1; ?>
<?php } ?>

### eRuby
<% i = 0
 %><% while i < length do
 %><%= list[i] %><% i += 1
 %><% end
 %>

### ERB
% i = 0
% while i < length do
<%= list[i] %>
%   i += 1
% end

なおJavaやCのようなfor文は用意されていません。ご注意ください。


条件分岐

条件分岐は「:if(condition) ... :elsif(condition) ... :else ... :end」で表します。

PLプログラム:

:if(ctr % 2 == 0)         ## 偶数なら
  :set(klass='even')      ## 文字列'even'を代入
:else                     ## 奇数なら
  :set(klass='odd')       ## 文字列'odd'を代入
:end

出力用スクリプト:

### Ruby
if ctr % 2 == 0 then
  klass = "even"
else
  klass = "odd"
end

### PHP
<?php if ($ctr % 2 == 0) { ?>
  <?php $klass = 'even'; ?>
<?php } else { ?>
  <?php $klass = 'odd'; ?>
<?php } ?>

### JSP + JSTL
<c:choose>
<c:when test="${ctr % 2 == 0}">
 <c:set var="klass" value="even"/>
</c:when>
<c:otherwise>
 <c:set var="klass" value="odd"/>
</c:otherwise>
</c:choose>

### eRuby
<% if ctr % 2 == 0 then
 %><% klass = 'even'
 %><% else
 %><% klass = 'odd'
 %><% end
 %>

### ERB
% if ctr % 2 == 0 then
%   klass = 'even'
% else
%   klass = 'odd'
% end

### Velocity
#if ($ctr % 2 == 0)
  #set ($klass = 'even')
#else
  #set ($klass = 'odd')
#end

マクロ

PLでは、マクロを定義することができます。 マクロは「:macro(macro_name) ... :end」で定義し、「:expand(macro_name)」または「@macro_name」で展開します。

次の例では、「<li>#{item}#</li>」を開始タグ、内容、終了タグの3つに分割し、マクロとして定義しています。

## 3つのマクロを定義
:macro(stag_item)
  :print("<li>")
:end

:macro(cont_item)
  :print(item)
:end

:macro(etag_item)
  :print("</li>")
:end

## 「<li>#{item}#</li>」と同じ結果になる
:expand(stag_item)
:expand(cont_item)
:expand(etag_item)

マクロ定義の中でマクロ展開を使うことができます。

## マクロを定義
:macro(stag_item)
  :print("<li>")
:end

:macro(cont_item)
  :print(item)
:end

:macro(etag_item)
  :print("</li>")
:end

:macro(elem_item)    ## マクロ展開を含むマクロ定義
  :expand(stag_item)
  :expand(cont_item)
  :expand(etag_item)
:end

## マクロが展開され、「<li>#{item}#</li>」と同じ結果になる
:expand(elem_item)

またエレメント用のマクロ定義をより簡単に書ける構文が用意されています。 この構文はエレメント用のマクロ定義でしか使用できません。

## エレメント用のマクロ定義
:elem(item)
  @stag
  @cont
  @etag
:end

## これは次と同じ
:macro(elem_item)
  :expand(stag_item)
  :expand(cont_item)
  :expand(etag_item)
:end

@stag, @cont, @etag:elem() .. :end でのみ使用可能です。 :macro() .. :end では使用できません。

## NG
:macro(elem_item)	## :elem(item) としなければならない
  @stag
  @cont
  @etag
:end

もうひとつ、エレメントの内容を式の値で置き換えるための専用構文も用意しています。 これは、ディレクティブでいえば id="value:expr" に相当する機能です。

## エレメントの内容を式の値で置き換える
:value(item = expr)

## これは次と同じ
:macro(cont_item)
  :print(expr)
:end

なお、引数をとるようなマクロは定義できません。


マクロBEGN、END

「BEGIN」と「END」は特別なマクロです。 このマクロを定義すると、出力用スクリプトの最初と最後に任意のコードを追加できます。

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

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

PLプログラム:

:macro(BEGIN)
  :print("<html>\n")
  :print(" <body>\n")
:end
:macro(END)
  :print(" </body>\n")
  :print("</html>\n")
:end

出力用スクリプト:

### Ruby
print "<html>\n"
print " <body>\n"
print "  Hello "
print user
print "!\n"
print " </body>\n"
print "</html>\n"

### PHP
<html>
 <body>
  Hello <?php echo $user; ?>!
 </body>
</html>

関数

PLでは、以下の関数を使用することができます。

E(expr)
expr をサニタイズします。サニタイズはコマンドラインオプションの指定に関わらず行われます。
X(expr)
式 expr をサニタイズしません。つまりコマンドラインオプションでサニタイズするよう指定されていても、X(expr)で指定された式はサニタイズされません。

現在のところ、これら以外の関数をユーザが自由に定義することはできません。


empty

値がnullまたは空文字列かどうかを調べるための予約語「empty」を用意しています。 「empty」は「==」または「!=」の右辺にのみ置くことができ、左辺がnullか空文字列なら真を、それ以外なら偽を返します。

PLプログラム:

:if (str1 == empty)
  :print("str1 is empty.\n")
:elsif(str2 != empty)
  :print("str2 is not empty.\n")
:end

出力用スクリプト:

### Ruby
if (str1 == nil || str1 == "") then
  print "str1 is empty.\n"
elsif (str2 != nil && str2 != "") then
  print "str2 is not empty.\n"
end

### PHP
<?php if (! $str1) { ?>
str1 is empty.
<?php } elseif ($str2) { ?>
str2 is not empty.
<?php } ?>

### JSP + JSTL
<c:choose>
<c:when test="${(empty str1)}">
str1 is empty.
</c:when>
<c:when test="${!(empty str2)}">
str2 is not empty.
</c:when>
</c:choose>

### eRuby
<% if (str1 == nil || str1 == '') then
 %>str1 is empty.
<% elsif (str2 != nil && str2 != '') then
 %>str2 is not empty.
<% end
 %>

### ERB
% if (str1 == nil || str1 == '') then
str1 is empty.
% elsif (str2 != nil && str2 != '') then
str2 is not empty.
% end

### Velocity
#if ((! $str1 || $str1 == ''))
str1 is empty.
#elseif (($str2 && $str2 != ''))
str2 is not empty.
#end

プレゼンテーションロジックファイルの読み込み

:load('filename')」で他のプレゼンテーションロジックファイルを読み込みます。 またコマンドラインオプション --load_path=dir1,dir2,... で、読み込むファイルがあるディレクトリを指定できます。

PLプログラム:

:load('file.plogic')

なお読み込むことができるのはプレゼンテーションロジックファイルだけです。 プレゼンテーションデータファイルや出力プログラムは読み込むことができません。


ターゲット言語のプログラムコード

ターゲット言語のプログラムコードを直接記述することもできます。 そのためには「::: raw code」とします。 「:::」の次の文字から改行までが、そのまま出力されます。

次の例は、PHPのコードを直接記述した例です。

PLプログラム:

::: <?php foreach($hash as $key => $value) { ?>
:print("key=", key, " value=", value, "\n")
::: <?php } ?>

出力用スクリプト:

### PHP
 <?php foreach($hash as $key => $value) { ?>
key=<?php echo $key; ?> value=<?php echo $value; ?>
 <?php } ?>

コマンドラインオプション --enable_eruby=true を使用することで、プレゼンテーションロジックの中に複数の言語を埋め込むことができます。 詳しくは「プレゼンテーションロジック中にeRubyコードを記述する」をご覧ください。


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

Kwartzでは、メインプログラムから出力用プログラムに渡される変数をグローバル変数、テンプレートの中だけで使用される変数をローカル変数と呼んでいます。

例えば次のようなプレゼンテーションデータとプレゼンテーションロジックを考えます。

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

<html>
  <body>
    <table>
      <caption id="value:title">Sample</caption>
      <tr id="item_list" bgcolor="#{color}#">
        <td id="value:item_ctr">1</td>
	<td id="value:item">Foo</td>
      </tr>
    </table>
  </body>
</html>

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

:elem(item_list)
  :set(item_ctr = 0)
  :foreach(item = item_list)
    :set(item_ctr += 1)
    :set(color = item_ctr%2 == 0 ? '#FFCCCC' : '#CCCCFF')
    @stag
    @cont
    @etag
  :end
:end

いくつかの変数がでてきますが、これらは次のように分類できます。

グローバル変数
変数 titleitem_list は、メインプログラムからテンプレートに渡される変数であり、メインプログラムで値を設定する必要があります。 Kwartzではこれをグローバル変数と呼んでいます。
ローカル変数
変数 itemitem_ctrcolor はテンプレートの中だけで使用される変数であり、メインプログラムで設定する必要はありません。 Kwartzではこれをローカル変数と呼んでいます。

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

$ kwartz -p analyze.plogic -a analyze analyze.html
global variables: title item_list
local variables:  color item item_ctr

このとき、Kwartzは次のようなルールでグローバル変数とローカル変数とを判別しています。

またグローバル変数へ代入したり、グローバル変数をループ変数としていると、分析時に警告を出します。 なぜなら、テンプレートシステムはメインプログラムで設定されたデータの表示のみを行うべきであり、それらを変更すべきではないから、つまりグローバル変数を変更すべきではないからです(変更してよいのはローカル変数のみのはずです)。 そのほか、初期化されていないローカル変数が使われている場合も分析時に警告が出ます。

テンプレート(プレゼンテーションデータとプレゼンテーションロジック)が複雑になると、メインプログラムで設定しなければならない変数がどれか、わかりづらくなることがあります。 そのようなときは、この分析機能を利用してください。変数名のタイプミスも見つけやすくなります。


注意事項


未実装(または検討中)の機能

以下の機能は実装されていませんが、将来において実装されるかもしれません。



実践的な例

Kwartzを用いた、より実践的な例を示します。

偶数行と奇数行とで背景色を変えたテーブル

偶数行と奇数行とでテーブルの背景色を変えるサンプルです。 この程度なら、プレゼンテーションロジックを別ファイルとして用意しなくても、ディレクティブだけで済ませるのがいいでしょう。

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

<table border="0">
  <tbody id="Foreach:user=user_list">
    <tr bgcolor="#CCCCFF"
        id="attr:bgcolor=color;set:color=user_ctr%2==0?'#FFCCCC':'#CCCCFF'">
      <td id="value:user[:name]">foo</td>
      <td id="value:user[:email]">foo@foo.com</td>
    </tr>
    <tr id="dummy:d1">
      <td>bar</td>
      <td>bar@bar.net</td>
    </tr>
    <tr id="dummy:d2">
      <td>baz</td>
      <td>baz@baz.org</td>
    </tr>
  </tbody>
</table>

中間コード:

:print("<table border=\"0\">\n")
:set(user_ctr = 0)
:foreach(user = user_list)
  :set(user_ctr += 1)
  :print("  <tbody>\n")
  :set(color=user_ctr%2==0?'#FFCCCC':'#CCCCFF')
  :print("    <tr bgcolor=\"", color, "\">\n")
  :print("      <td>", user[:name], "</td>\n")
  :print("      <td>", user[:email], "</td>\n")
  :print("    </tr>\n")
  :print("  </tbody>\n")
:end
:print("</table>\n")

コンパイル:

$ kwartz -l ruby     table1.html > table1.rb
$ kwartz -l ruby2    table1.html > table1.rb2
$ kwartz -l php      table1.html > table1.php
$ kwartz -l jsp      table1.html > table1.jsp
$ kwartz -l eruby    table1.html > table1.rhtml
$ kwartz -l erb      table1.html > table1.erb
$ kwartz -l velocity table1.html > table1.vm

コンパイルすると、次のような出力用プログラムが得られます。

Ruby:

print "<table border=\"0\">\n"
user_ctr = 0
for user in user_list do
  user_ctr += 1
  print "  <tbody>\n"
  color = (user_ctr % 2 == 0 ? "\#FFCCCC" : "\#CCCCFF")
  print "    <tr bgcolor=\"", color, "\">\n"
  print "      <td>", user[:name], "</td>\n"
  print "      <td>", user[:email], "</td>\n"
  print "    </tr>\n"
  print "  </tbody>\n"
end
print "</table>\n"

Ruby2:

_s << "<table border=\"0\">\n"
user_ctr = 0
for user in user_list do
  user_ctr += 1
  _s << "  <tbody>\n"
  color = (user_ctr % 2 == 0 ? "\#FFCCCC" : "\#CCCCFF")
  _s << "    <tr bgcolor=\"" << (color).to_s << "\">\n"
  _s << "      <td>" << (user[:name]).to_s << "</td>\n"
  _s << "      <td>" << (user[:email]).to_s << "</td>\n"
  _s << "    </tr>\n"
  _s << "  </tbody>\n"
end
_s << "</table>\n"

PHP:

<table border="0">
<?php $user_ctr = 0; ?>
<?php foreach ($user_list as $user) { ?>
  <?php $user_ctr += 1; ?>
  <tbody>
  <?php $color = ($user_ctr % 2 == 0 ? '#FFCCCC' : '#CCCCFF'); ?>
    <tr bgcolor="<?php echo $color; ?>">
      <td><?php echo $user['name']; ?></td>
      <td><?php echo $user['email']; ?></td>
    </tr>
  </tbody>
<?php } ?>
</table>

JSP:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<table border="0">
<c:set var="user_ctr" value="0"/>
<c:forEach var="user" items="${user_list}">
 <c:set var="user_ctr" value="${user_ctr + 1}"/>
  <tbody>
 <c:choose>
<c:when test="${user_ctr % 2 == 0}">
  <c:set var="color" value="#FFCCCC"/>
 </c:when>
 <c:otherwise>
  <c:set var="color" value="#CCCCFF"/>
 </c:otherwise>
 </c:choose>
    <tr bgcolor="<c:out value="${color}" escapeXml="false"/>">
      <td><c:out value="${user['name']}" escapeXml="false"/></td>
      <td><c:out value="${user['email']}" escapeXml="false"/></td>
    </tr>
  </tbody>
</c:forEach>
</table>

eRuby:

<table border="0">
<% user_ctr = 0
 %><% for user in user_list do
 %><% user_ctr += 1
 %>  <tbody>
<% color = (user_ctr % 2 == 0 ? '#FFCCCC' : '#CCCCFF')
 %>    <tr bgcolor="<%= color %>">
      <td><%= user[:name] %></td>
      <td><%= user[:email] %></td>
    </tr>
  </tbody>
<% end
 %></table>

ERB:

<table border="0">
% user_ctr = 0
% for user in user_list do
%   user_ctr += 1
  <tbody>
%   color = (user_ctr % 2 == 0 ? '#FFCCCC' : '#CCCCFF')
    <tr bgcolor="<%= color %>">
      <td><%= user[:name] %></td>
      <td><%= user[:email] %></td>
    </tr>
  </tbody>
% end
</table>

Velocity:

<table border="0">
#set ($user_ctr = 0)
#foreach ($user in $user_list)
  #set ($user_ctr = $user_ctr + 1)
  <tbody>
  #if ($user_ctr % 2 == 0)
    #set ($color = '#FFCCCC')
  #else
    #set ($color = '#CCCCFF')
  #end
    <tr bgcolor="$!{color}">
      <td>$!{user.name}</td>
      <td>$!{user.email}</td>
    </tr>
  </tbody>
#end
</table>

「次」や「前」を表すナビゲーション

一連のHTMLファイルを表示するときは、「次に進む」や「前に戻る」というリンクをつけることが多いです。 このリンクを、次のような方針で作ります。

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

<html>
  <body>
    <span id="mark:navilink">
      <a id="mark:prev" href="#{prev_url}#">
        &lt; Previous page
      </a>
      &nbsp;
      <a id="mark:next" href="#{next_url}#">
        Next page &gt;
      </a>
    </span>

    ......
    ......
    ......

    <span id="mark:navilink2">
      &lt; Previous page  &nbsp;  Next page &gt;
    </span>

  </body>
</html>

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

## URLがnullのときは、開始タグと終了タグを表示しない
:elem(next)
  :if(next_url != null)
    @stag
    @cont
    @etag
  :else
    @cont
  :end
:end
:elem(prev)
  :if(prev_url != null)
    @stag
    @cont
    @etag
  :else
    @cont
  :end
:end

## エレメントnavilink2をエレメントnavilinkで置き換える
:elem(navilink2)
  @elem_navilink
:end

なおエレメント 'navilink2' をエレメント 'navilink' で置き換えるには、ディレクティブ「kd="replace:navilink"」を使ってもよいです。

コンパイル:

$ kwartz -p navilink.plogic -l ruby  navilink.html > navilink.rb
$ kwartz -p navilink.plogic -l ruby2 navilink.html > navilink.rb2
$ kwartz -p navilink.plogic -l php   navilink.html > navilink.php
$ kwartz -p navilink.plogic -l jsp   navilink.html > navilink.jsp
$ kwartz -p navilink.plogic -l eruby navilink.html > navilink.rhtml
$ kwartz -p navilink.plogic -l erb   navilink.html > navilink.erb
$ kwartz -p navilink.plogic -l velocity navilink.html > navilink.vm

コンパイルすると、次のような出力用プログラムが得られます。

Ruby:

print "<html>\n"
print "  <body>\n"
if prev_url != nil then
  print "      <a href=\"", prev_url, "\">\n"
  print "        &lt; Previous page\n"
  print "      </a>\n"
else
  print "        &lt; Previous page\n"
end
print "      &nbsp;\n"
if next_url != nil then
  print "      <a href=\"", next_url, "\">\n"
  print "        Next page &gt;\n"
  print "      </a>\n"
else
  print "        Next page &gt;\n"
end

    ......
    ......
    ......

if prev_url != nil then
  print "      <a href=\"", prev_url, "\">\n"
  print "        &lt; Previous page\n"
  print "      </a>\n"
else
  print "        &lt; Previous page\n"
end
print "      &nbsp;\n"
if next_url != nil then
  print "      <a href=\"", next_url, "\">\n"
  print "        Next page &gt;\n"
  print "      </a>\n"
else
  print "        Next page &gt;\n"
end
print "\n"
print "  </body>\n"
print "</html>\n"

Ruby2:

_s << "<html>\n"
_s << "  <body>\n"
if prev_url != nil then
  _s << "      <a href=\"" << (prev_url).to_s << "\">\n"
  _s << "        &lt; Previous page\n"
  _s << "      </a>\n"
else
  _s << "        &lt; Previous page\n"
end
_s << "      &nbsp;\n"
if next_url != nil then
  _s << "      <a href=\"" << (next_url).to_s << "\">\n"
  _s << "        Next page &gt;\n"
  _s << "      </a>\n"
else
  _s << "        Next page &gt;\n"
end

    ......
    ......
    ......

if prev_url != nil then
  _s << "      <a href=\"" << (prev_url).to_s << "\">\n"
  _s << "        &lt; Previous page\n"
  _s << "      </a>\n"
else
  _s << "        &lt; Previous page\n"
end
_s << "      &nbsp;\n"
if next_url != nil then
  _s << "      <a href=\"" << (next_url).to_s << "\">\n"
  _s << "        Next page &gt;\n"
  _s << "      </a>\n"
else
  _s << "        Next page &gt;\n"
end
_s << "\n"
_s << "  </body>\n"
_s << "</html>\n"

PHP:

<html>
  <body>
<?php if ($prev_url != NULL) { ?>
      <a href="<?php echo $prev_url; ?>">
        &lt; Previous page
      </a>
<?php } else { ?>
        &lt; Previous page
<?php } ?>
      &nbsp;
<?php if ($next_url != NULL) { ?>
      <a href="<?php echo $next_url; ?>">
        Next page &gt;
      </a>
<?php } else { ?>
        Next page &gt;
<?php } ?>

    ......
    ......
    ......

<?php if ($prev_url != NULL) { ?>
      <a href="<?php echo $prev_url; ?>">
        &lt; Previous page
      </a>
<?php } else { ?>
        &lt; Previous page
<?php } ?>
      &nbsp;
<?php if ($next_url != NULL) { ?>
      <a href="<?php echo $next_url; ?>">
        Next page &gt;
      </a>
<?php } else { ?>
        Next page &gt;
<?php } ?>

  </body>
</html>

JSP:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html>
  <body>
<c:choose>
<c:when test="${prev_url != null}">
      <a href="<c:out value="${prev_url}" escapeXml="false"/>">
        &lt; Previous page
      </a>
</c:when>
<c:otherwise>
        &lt; Previous page
</c:otherwise>
</c:choose>
      &nbsp;
<c:choose>
<c:when test="${next_url != null}">
      <a href="<c:out value="${next_url}" escapeXml="false"/>">
        Next page &gt;
      </a>
</c:when>
<c:otherwise>
        Next page &gt;
</c:otherwise>
</c:choose>

    ......
    ......
    ......

<c:choose>
<c:when test="${prev_url != null}">
      <a href="<c:out value="${prev_url}" escapeXml="false"/>">
        &lt; Previous page
      </a>
</c:when>
<c:otherwise>
        &lt; Previous page
</c:otherwise>
</c:choose>
      &nbsp;
<c:choose>
<c:when test="${next_url != null}">
      <a href="<c:out value="${next_url}" escapeXml="false"/>">
        Next page &gt;
      </a>
</c:when>
<c:otherwise>
        Next page &gt;
</c:otherwise>
</c:choose>

  </body>
</html>

eRuby:

<html>
  <body>
<% if prev_url != nil then
 %>      <a href="<%= prev_url %>">
        &lt; Previous page
      </a>
<% else
 %>        &lt; Previous page
<% end
 %>      &nbsp;
<% if next_url != nil then
 %>      <a href="<%= next_url %>">
        Next page &gt;
      </a>
<% else
 %>        Next page &gt;
<% end
 %>
    ......
    ......
    ......

<% if prev_url != nil then
 %>      <a href="<%= prev_url %>">
        &lt; Previous page
      </a>
<% else
 %>        &lt; Previous page
<% end
 %>      &nbsp;
<% if next_url != nil then
 %>      <a href="<%= next_url %>">
        Next page &gt;
      </a>
<% else
 %>        Next page &gt;
<% end
 %>
  </body>
</html>

ERB:

<html>
  <body>
% if prev_url != nil then
      <a href="<%= prev_url %>">
        &lt; Previous page
      </a>
% else
        &lt; Previous page
% end
      &nbsp;
% if next_url != nil then
      <a href="<%= next_url %>">
        Next page &gt;
      </a>
% else
        Next page &gt;
% end

    ......
    ......
    ......

% if prev_url != nil then
      <a href="<%= prev_url %>">
        &lt; Previous page
      </a>
% else
        &lt; Previous page
% end
      &nbsp;
% if next_url != nil then
      <a href="<%= next_url %>">
        Next page &gt;
      </a>
% else
        Next page &gt;
% end

  </body>
</html>

Velocity:

<html>
  <body>
#if ($prev_url)
      <a href="$!{prev_url}">
        &lt; Previous page
      </a>
#else
        &lt; Previous page
#end
      &nbsp;
#if ($next_url)
      <a href="$!{next_url}">
        Next page &gt;
      </a>
#else
        Next page &gt;
#end

    ......
    ......
    ......

#if ($prev_url)
      <a href="$!{prev_url}">
        &lt; Previous page
      </a>
#else
        &lt; Previous page
#end
      &nbsp;
#if ($next_url)
      <a href="$!{next_url}">
        Next page &gt;
      </a>
#else
        Next page &gt;
#end

  </body>
</html>

Breadcrumbs

Webページにはよく「Home > Ruby > Kwartz」のようなリンクがついています。 これを「Breadcrumbs」または「Trail」というそうです。

これを、次のような方針で作成します。

メインプログラム:

## Ruby
breadcrumbs = []
breadcrumbs << { :title => 'Home',   :path => '/' }
breadcrumbs << { :title => 'Ruby',   :path => '/ruby/' }
breadcrumbs << { :title => 'Kwartz', :path => '/ruby/kwartz/' }

## PHP
$breadcrumbs = array('title' => 'Home',   'path' => '/');
$breadcrumbs = array('title' => 'Ruby',   'path' => '/ruby/');
$breadcrumbs = array('title' => 'Kwartz', 'path' => '/ruby/kwartz/');

## Java
List breadcrumbs = new ArrayList();
Map  hash = new HashMap();
hash.put('title', 'Home');
hash.put('path',  '/');
breadcrumbs.add(hash);
hash = new HashMap();
hash.put('title', 'Ruby')
hash.put('path', '/ruby/')
breadcrumbs.add(hash);
hash = new HashMap();
hash.put('title', 'Kwartz')
hash.put('path', '/ruby/kwartz/')
breadcrumbs.add(hash);

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

<span id="mark:breadcrumbs">
  <a id="mark:item" href="#{item_path}#">
    <span id="value:item_title">Home</span>
  </a>
  <span id="mark:arrow"> &gt; </span>
</span>

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

:elem(breadcrumbs)
  :set(item_ctr = 0)
  :foreach(item = breadcrumbs)
    :set(item_ctr += 1)
    :if(item_ctr > 1)       ## 繰り返しの2回目以降では
      @elem_arrow           ## &lt; を表示する
    :end
    @elem_item              ## <a></a>を表示する。
  :end
:end

:elem(item)
  :set(item_path  = item[:path])   ## ハッシュからパス名と
  :set(item_title = item[:title])  ## タイトルを取り出す。
  :if(item_path!=null)
    @stag
    @cont
    @etag
  :else                     ## パス名がnullなら
    @cont                   ## <a></a>は表示しない。
  :end
:end

コンパイル:

$ kwartz -p breadcrumbs.plogic -l ruby  breadcrumbs.html > breadcrumbs.rb
$ kwartz -p breadcrumbs.plogic -l ruby2 breadcrumbs.html > breadcrumbs.rb2
$ kwartz -p breadcrumbs.plogic -l php   breadcrumbs.html > breadcrumbs.php
$ kwartz -p breadcrumbs.plogic -l jsp   breadcrumbs.html > breadcrumbs.jsp
$ kwartz -p breadcrumbs.plogic -l eruby breadcrumbs.html > breadcrumbs.rhtml
$ kwartz -p breadcrumbs.plogic -l erb   breadcrumbs.html > breadcrumbs.erb
$ kwartz -p breadcrumbs.plogic -l velocity breadcrumbs.html > breadcrumbs.vm

コンパイルすると、次のような出力用プログラムが得られます。

Ruby:

item_ctr = 0
for item in breadcrumbs do
  item_ctr += 1
  if item_ctr > 1 then
    print "&gt; "
  end
  item_path = item[:path]
  item_title = item[:title]
  if item_path != nil then
    print "  <a href=\"", item_path, "\">\n"
    print item_title
    print "  </a>\n"
  else
    print item_title
  end
end

Ruby2:

item_ctr = 0
for item in breadcrumbs do
  item_ctr += 1
  if item_ctr > 1 then
    _s << "&gt; "
  end
  item_path = item[:path]
  item_title = item[:title]
  if item_path != nil then
    _s << "  <a href=\"" << (item_path).to_s << "\">\n"
    _s << (item_title).to_s
    _s << "  </a>\n"
  else
    _s << (item_title).to_s
  end
end

PHP:

<?php $item_ctr = 0; ?>
<?php foreach ($breadcrumbs as $item) { ?>
  <?php $item_ctr += 1; ?>
  <?php if ($item_ctr > 1) { ?>
&gt; <?php } ?>
  <?php $item_path = $item['path']; ?>
  <?php $item_title = $item['title']; ?>
  <?php if ($item_path != NULL) { ?>
  <a href="<?php echo $item_path; ?>">
<?php echo $item_title; ?>  </a>
  <?php } else { ?>
<?php echo $item_title; ?><?php } ?>
<?php } ?>

JSP:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<c:set var="item_ctr" value="0"/>
<c:forEach var="item" items="${breadcrumbs}">
 <c:set var="item_ctr" value="${item_ctr + 1}"/>
 <c:choose>
<c:when test="${item_ctr > 1}">
&gt; </c:when>
 </c:choose>
 <c:set var="item_path" value="${item['path']}"/>
 <c:set var="item_title" value="${item['title']}"/>
 <c:choose>
<c:when test="${item_path != null}">
  <a href="<c:out value="${item_path}" escapeXml="false"/>">
<c:out value="${item_title}" escapeXml="false"/>  </a>
 </c:when>
 <c:otherwise>
<c:out value="${item_title}" escapeXml="false"/></c:otherwise>
 </c:choose>
</c:forEach>

eRuby:

<% item_ctr = 0
 %><% for item in breadcrumbs do
 %><% item_ctr += 1
 %><% if item_ctr > 1 then
 %>&gt; <% end
 %><% item_path = item[:path]
 %><% item_title = item[:title]
 %><% if item_path != nil then
 %>  <a href="<%= item_path %>">
<%= item_title %>  </a>
<% else
 %><%= item_title %><% end
 %><% end
 %>

ERB:

% item_ctr = 0
% for item in breadcrumbs do
%   item_ctr += 1
%   if item_ctr > 1 then
&gt; 
%   end
%   item_path = item[:path]
%   item_title = item[:title]
%   if item_path != nil then
  <a href="<%= item_path %>">
<%= item_title %>  </a>
%   else
<%= item_title %>
%   end
% end

Velocity:

#set ($item_ctr = 0)
#foreach ($item in $breadcrumbs)
  #set ($item_ctr = $item_ctr + 1)
  #if ($item_ctr > 1)
&gt; #end
  #set ($item_path = $item.path)
  #set ($item_title = $item.title)
  #if ($item_path)
  <a href="$!{item_path}">
$!{item_title}  </a>
  #else
$!{item_title}#end
#end

エラーメッセージの表示

条件式によって表示を切り替える例を示します。 ここでは、変数error_reportの値がnullかどうかで表示を切り替えることにします。

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

<span id="error_report">
  <span id="noerror">エラーはありません。</span>
  <font id="error" color="red">エラーが発生しました。</font>
</span>

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

:elem(error_report)
  @stag
  :if(error_report == null)
     @elem_noerror	## 「エラーはありません」を出力
  :else
     @elem_error	## 「エラーが発生しました。」を出力
  :end
  @etag
:end

プレゼンテーションロジックでは、エレメントの内容(@cont)を使用せずに他のエレメント(@elem_noerror@elem_error)を直接使用しています。 このやり方は便利ですので、覚えておくとよいでしょう。

またエラーメッセージが複数あり、それが配列に格納されている場合は次のようにします。

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

<font id="error_list" color="red">
  <span id="value:error">名前が入力されていません。</span><br>
</font>

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

:elem(error_list)
  :if(error_list != null)
    @stag
    :foreach(error = error_list)
      @cont
    :end
    @cont
  :end
:end

なお「プレゼンテーションとビジネスロジックの分離」という観点からいうと、エラーメッセージをメインプログラムの中で設定すべきではありません。なぜなら、エラーが発生したときに「どんなエラーメッセージを表示するか」というのはプレゼンテーション層(つまりテンプレート)が担当すべきだからです。

その観点からいうと、エラーメッセージは次のようにすることが望ましいです。

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

<font id="if:errors!=null" color="red">
  <!-- メインプログラムで設定されたエラーメッセージをそのまま表示する -->
  <span id="if:errors['age']!=null">#{errors['age']}#<br><span>
  <span id="if:errors['tel']!=null">#{errors['tel']}#<br><span>
  <!-- 別のエラーメッセージを表示する -->
  <span id="if:errors['name']!=null">名前が入力されていません。<br></span>
  <span id="if:errors['mail']!=null">メールアドレスが入力されていません。<br></span>
</font>

ほかに、エラーメッセージを別ファイルとして用意し、実行時に読み込むという方法もあります。 お好きな方法をお使いください。


フォーム入力

名前と性別を登録するフォームを作成してみます。

今回は次のようなファイルがあります。 このうち、出力用スクリプトはKwartzによって自動的に作成されます。

register.cgi
CGIメインプログラム(Ruby)。
register.html
登録ページのテンプレート。
register.rhtml
登録ページの出力用スクリプト(eRuby)。
finish.html
完了ページのテンプレート。
finish.rhtml
完了ページの出力用スクリプト(eRuby)。

フォームを表示するCGIは、次のような方針で作成します。

今回はコンパイルオプション '-s' をつけて、サニタイズ機能を有効にしてみます。 またターゲット言語としてerubyを使用してみました。

登録ページのテンプレート(register.html):

<html>
 <body>
  <form action="/cgi-bin/register.cgi" method="POST">

   <span id="if:error_list==null">
    <font color="#FF0000" id="foreach:error=error_list">
     <span id="value:error">名前が入力されていません。</span><br>
    </font>
   </span>

   <table border="0" cellspacing="1" cellpadding="5">

    <tr>
     <td>お名前</td>
     <td>
      <input type="text" name="name" size="20"
             id="attr:value=param[:name]">
     </td>
    </tr>

    <tr>
     <td>性別:</td>
     <td>
      <input type="radio" name="gender" value="M"
             #{X(param[:gender]=='M'?'checked':'')}#>男性
      &nbsp;
      <input type="radio" name="gender" value="W"
             #{X(param[:gender]=='W'?'checked':'')}#>女性
     </td>
    </tr>

    <tr>
     <td colspan="2" align="right">
      <input type="submit" value=" 登録する ">
      <input type="reset"  value="取り消す">
     </td>
    </tr>

   </table>
  </form>
 </body>
</html>

登録ページの出力用プログラム(register.rhtml):

<html>
 <body>
  <form action="/cgi-bin/register.cgi" method="POST">

<% if error_list == nil then
 %><% for error in error_list do
 %>    <font color="#FF0000">
<%= CGI.escapeHTML((error).to_s) %><br>
    </font>
<% end
 %><% end
 %>
   <table border="0" cellspacing="1" cellpadding="5">

    <tr>
     <td>お名前</td>
     <td>
      <input type="text" name="name" size="20" value="<%= CGI.escapeHTML((param[:name]).to_s) %>">
     </td>
    </tr>

    <tr>
     <td>性別:</td>
     <td>
      <input type="radio" name="gender" value="M"
             <%= (param[:gender] == 'M' ? 'checked' : '') %>>男性
      &nbsp;
      <input type="radio" name="gender" value="W"
             <%= (param[:gender] == 'W' ? 'checked' : '') %>>女性
     </td>
    </tr>

    <tr>
     <td colspan="2" align="right">
      <input type="submit" value=" 登録する ">
      <input type="reset"  value="取り消す">
     </td>
    </tr>

   </table>
  </form>
 </body>
</html>

完了ページのテンプレート(finish.html):

<html>
 <body>
  以下の内容で登録しました。<br>
  名前:
   <span id="value:param[:name]">Foo</span><br>
  性別:
   <span id="if:param[:gender]=='M'">男性</span>
   <span id="if:param[:gender]=='W'">女性</span>
 </body>
</html>

完了ページの出力用ページ(finish.rhtml)

<html>
 <body>
  以下の内容で登録しました。<br>
  名前:
<%= CGI.escapeHTML((param[:name]).to_s) %><br>
  性別:
<% if param[:gender] == 'M' then
 %>男性<% end
 %><% if param[:gender] == 'W' then
 %>女性<% end
 %> </body>
</html>

メインプログラム(register.cgi):

#!/usr/bin/ruby

## CGIオブジェクトからハッシュparamを作成する
require 'cgi'
cgi = CGI.new
param = {}
cgi.param.each do |key, value|
  param[key.intern] = value[0]
end
param.default = ''

## Submitボタンを押された場合の処理
error_list = nil
if !param.empty? then
  ## 入力チェック
  error_list = []
  if param[:name] == '' then
    error_list << 'ユーザ名が入力されていません。'
  end
  case param[:gender]
  when 'M', 'W'
    # OK
  else
    error_list << '性別が選択されていません。'
  end

  ## 入力が正しければ完了ページ(finish.rhtml)、
  ## そうでなければもう一度同じページ(register.rhtml)
  if error_list.empty? then
    error_list = nil
    filename = 'finish.rhtml'
    ... データを登録する処理 ...
  else
    filename = 'register.rhtml'
  end

end

## ページを出力
require 'eruby'
ERuby::import(filename)

カレンダー

カレンダーを表示するサンプルを通じて、コンポーネント指向による開発方法を示します。 また最終的な表示結果はこちらのようになります。

カレンダーは、かなり複雑なプレゼンテーションロジックが必要となります。 そのため、プレゼンテーションロジックを分離する効果が最もよく現れる例のひとつです。

今回は、プレゼンテーションデータとプレゼンテーションロジックを2つずつ用意します。 またメインプログラムはPHPで書いてみました。

calendar.html, calendar.plogic
ひと月分のカレンダーを表示するためのプレゼンテーションデータとプレゼンテーションロジック。
calendar-page.html, calendar-page.plogic
表示するWebページを表すプレゼンテーションデータとプレゼンテーションロジック。
calendar-page.php
メインプログラム。

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

     <table cellpadding="2" summary="">
       <caption>
	 <i id="value:month">Jan</i>&nbsp;<i id="value:year">20XX</i>
       </caption>
       <thead>
	 <tr bgcolor="#CCCCCC">
	   <th><span class="holiday">S</span></th>
	   <th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th>
	 </tr>
       </thead>
       <tbody>
	 <tr id="mark:week">
	   <td><span id="mark:day" class="holiday">&nbsp;</span></td>
	   <td id="dummy:d1">&nbsp;</td>
	   <td id="dummy:d2">1</td>
	   <td id="dummy:d3">2</td>
	   <td id="dummy:d4">3</td>
	   <td id="dummy:d5">4</td>
	   <td id="dummy:d6">5</td>
	 </tr>
	 <tr id="dummy:w1">
	   <td><span class="holiday">6</span></td>
	   <td>7</td><td>8</td><td>9</td>
	   <td>10</td><td>11</td><td>12</td>
	 </tr>
	 <tr id="dummy:w2">
	   <td><span class="holiday">13</span></td>
	   <td>14</td><td>15</td><td>16</td>
	   <td>17</td><td>18</td><td>19</td>
	 </tr>
	 <tr id="dummy:w3">
	   <td><span class="holiday">20</span></td>
	   <td>21</td><td>22</td><td>23</td>
	   <td>24</td><td>25</td><td>26</td>
	 </tr>
	 <tr id="dummy:w4">
	   <td><span class="holiday">27</span></td>
	   <td>28</td><td>29</td><td>30</td>
	   <td>31</td><td>&nbsp;</td><td>&nbsp;</td>
	 </tr>
       </tbody>
     </table>
     &nbsp;

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

:elem(week)

  :set(day = '&nbsp')
  :set(wday = 1)
  :while(wday < first_weekday)
    :if(wday == 1)
      @stag
    :end
    @cont
    :set(wday += 1)
  :end

  :set(day = 0)
  :set(wday -= 1)
  :while(day < num_days)
    :set(day += 1)
    :set(wday = wday % 7 + 1)
    :if(wday == 1)
      @stag
    :end
    @cont
    :if(wday == 7)
      @etag
    :end
  :end

  :if(wday != 7)
    :set(day = '&nbsp;')
    :while(wday != 6)
      @cont
      :set(wday += 1)
    :end
    @etag
  :end

:end

:elem(day)
  :if(wday == 1)
    @stag
    :print(day)
    @etag
  :else
    :print(day)
  :end
:end

プレゼンテーションデータ(calendar-page.html):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <style type="text/css">
      <!--
	.holiday   {color:#FF0000;}
	-->
    </style>
  </head>
  <body>
    
    <table border="0" summary="">
      <tr id="mark:calendar_list">
	<td id="mark:calendar" valign="top">
	  .... calendar here ...
	</td>
      </tr>
    </table>
    
  </body>
</html>

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

:elem(calendar_list)
  :set(calendar_ctr = 0)
  :foreach(calendar = calendar_list)
    :set(calendar_ctr += 1)
    :if(calendar_ctr % colnum == 1)
      @stag
    :end
    @cont
    :if(calendar_ctr % colnum == 0)
      @etag
    :end
  :end
  :if(calendar_ctr % colnum != 0)
    :set(calendar = '')
    :while(calendar_ctr % colnum != 0)
      @cont
      :set(calendar_ctr += 1)
    :end
    @etag
  :end
:end

:elem(calendar)
   @stag
   :print(calendar)
   @etag
:end

メインプログラム(calendar-main.php):

<?php
	// year, month, number of days, 1st weekday
	// (or use date() function)
	$month_data[] = array(2004, 'Jan', 31, 'Thu');
	$month_data[] = array(2004, 'Feb', 29, 'Sat');
	$month_data[] = array(2004, 'Mar', 31, 'Tue');
	$month_data[] = array(2004, 'Apr', 30, 'Thu');
	$month_data[] = array(2004, 'May', 31, 'Sat');
	$month_data[] = array(2004, 'Jun', 30, 'Tue');
	#$month_data[] = array(2004, 'Jul', 31, 'Thu');
	#$month_data[] = array(2004, 'Aug', 31, 'Sun');
	#$month_data[] = array(2004, 'Sep', 30, 'Wed');
	#$month_data[] = array(2004, 'Oct', 31, 'Fri');
	#$month_data[] = array(2004, 'Nov', 30, 'Mon');
	#$month_data[] = array(2004, 'Dec', 31, 'Wed');

	$weekday2num = array( 'Sun'=>1, 'Mon'=>2, 'Tue'=>3, 'Wed'=>4,
			      'Thu'=>5, 'Fri'=>6, 'Sat'=>7,);


	// output buffering start
	ob_start();

	// main loop
	$colnum = 4;
	foreach($month_data as $data) {
		$year	       = $data[0];
		$month         = $data[1];
		$num_days      = $data[2];
		$first_weekday = $weekday2num[$data[3]];

		// include 'calendar.inc' content as string
		include('calendar.php');
		$str = ob_get_contents();
		ob_clean();

		// append content string into $calendar_list[]
		$calendar_list[]  = $str;
	}

	// output buffering stop
	ob_end_clean();

	// include main page, with $calendar_list[]
	include('calendar-page.php');
 ?>

コンパイルと実行:

$ kwartz -l php -p calendar.plogic calendar.html > calendar.php
$ kwartz -l php -p calendar-page.plogic calendar-page.html > calendar-page.php
$ php -q calendar-main.php > calendar-main.html


Tips

出力結果を文字列として取り出す

出力スクリプトの実行結果を文字列として取り出す方法を説明します。対象はRubyとPHPです。

Rubyの場合は、ruby2またはERBを使用します。

## ruby2
s = File.open('file.erb') { |f| f.read() }   # or File.read('file.erb') in 1.8
s.untaint
_s = ''
eval s
str = _s			  # get output as string

## ERB
s = File.open('file.erb') { |f| f.read() }   # or File.read('file.erb') in 1.8
require 'erb'
s.untaint
erb = ERB.new(s, $SAFE, '%')
str = erb.result(binding())       # get output as string

PHPの場合は、php2を指定するか、自力で出力バッファリング関数(ob_start(), ob_get_contents(), ob_end_clean())を使用します。

// php2
<?php
	include('file.php');
	$str = $_s;
?>

// use output buffering functions
<?php
	ob_start();                 // start output buffering
	include('file.php');
	$str = ob_get_contents();   // get result as string
	ob_end_clean();             // stop output buffering
?>

タイムスタンプを比較して自動コンパイルを行う

プレゼンテーションファイルや出力用スクリプトファイルのタイムスタンプを実行時に比較して、自動コンパイルを行うことができます。 詳しくいうと、次の条件のときにコンパイルを行います。

ただし、Kwartzの実装はRubyでしか行われていないため、自動コンパイル機能が使えるのはメインプログラムがRubyで書かれている場合のみです。

自動コンパイルを行うには、Kwartz::compile()を使用します。 メインプログラムは、例えば次のようになります。

pdata  = 'sample.html'		# プレゼンテーションデータ
script = 'sample.rhtml'		# 出力用スクリプトファイル
plogic = 'sample.plogic'	# プレゼンテーションロジック
lang   = 'eruby'		# ターゲット言語

require 'kwartz'
Kwartz::compile(pdata, script, plogic, lang)

requre 'eruby'
ERuby::import(script)

checked, selected, disabled

HTML/XHTMLのための機能として、「checked="checked"」や「selected="selected"」や「disabled="disabled"」を簡単に出力する機能を追加しました。

#{@CHECK(condition)}# or #{@C(condition)}#

条件がtrueなら「checked="checked"」を出力します。

#{@SELECT(condition)}# or #{@S(condition)}#

条件がtrueなら「selected="selected"」を出力します。

#{@DISABLE(condition)}# or #{@D(condition)}#

条件がtrueなら「disabled="disabled"」を出力します。

例えば、次のようなプレゼンテーションデータがあるとします。

<input type="radio" name="gender" value="M"
       #{gender=='M' ? 'checked="checked"' : ''}#>Man
<option name="lang" value="ruby"
       #{lang=='ruby' ? 'selected="selected"' : ''}#>Ruby
<input type="radio" name="os" value="win"
       #{os=='mac' ? 'disabled="disabled"' : ''}#>Windows

これは次のようにより簡潔に記述することができます。

<input type="radio" name="gender" value="M"
       #{@C(gender=='M')}#>Man
<option name="lang" value="ruby"
       #{@S(lang=='ruby')}#>Ruby
<input type="radio" name="os" value="win"
       #{@D(os=='mac')}#>Windows

そして、次のような中間コードにコンバートされます。

:print("<input type=\"radio\" name=\"gender\" value=\"M\"\n")
:print("       ", @C(gender=='M'), ">Man\n")
:print("<option name=\"lang\" value=\"ruby\"\n")
:print("       ", @S(lang=='ruby'), ">Ruby\n")
:print("<input type=\"radio\" name=\"os\" value=\"win\"\n")
:print("       ", @D(os=='mac'), ">Windows\n")

なお、この機能は実験中です。将来、変更される可能性がありますが、ご了承ください。


プレゼンテーションロジック中にeRubyコードを記述する

コマンドラインオプション --enable_eruby=true を指定すると、プレゼンテーションロジック中にeRubyのコードを記述できます。 eRubyコードでは「%」記法が使用でき、また変換先言語名を変数__lang__で参照できます。

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

:set(message = 'a sample message')
:set(number  = 20)
% if __lang__ == 'php'
::: <?php $str = sprintf('(%03d) %s', $number, $message); ?>
% elsif __lang__ == 'ruby'
::: s = '(%03d) %s' % [number, message]
% elsif __lang__ == 'erb'
:::% str = '(%03d) %s' % [number, message]
% elsif __lang__ == 'eruby'
::: <<% %>% str = '(%03d) %s' % [number, message] %<% %>>
% else
:set(s = '(' .+ number .+ ') ' .+ message)
% end
:print(s, "\n")

出力スクリプト:

### Ruby
message = "a sample message"
number = 20
 s = '(%03d) %s' % [number, message]
print s, "\n"

### PHP
<?php $message = 'a sample message'; ?>
<?php $number = 20; ?>
 <?php $str = sprintf('(%03d) %s', $number, $message); ?>
<?php echo $s; ?>

### JSP + JSTL
<c:set var="message" value="a sample message"/>
<c:set var="number" value="20"/>
<c:set var="s" value="${'('}${number}${') '}${message}"/>
<c:out value="${s}" escapeXml="false"/>

### eRuby
<% message = 'a sample message'
 %><% number = 20
 %> <% str = '(%03d) %s' % [number, message] %>
<%= s %>

### ERB
% message = 'a sample message'
% number = 20
% str = '(%03d) %s' % [number, message]
<%= s %>

### Velocity
#set ($message = 'a sample message')
#set ($number = 20)
#set ($s = "(${number}) ${message}")
$!{s}

eRubyコードを埋め込むにはERBが必要です。 Ruby 1.8以降には標準でERBが付属しますが、1.6の場合は別途ERBをインストールしてください。


環境変数KWARTZ_OPTIONS

環境変数「KWARTZ_OPTIONS」に、kwartzコマンドラインオプションを指定することができます。

例えば、変換先の言語としてERBを使用し、また必ずサニタイズを行うのであれば、環境変数を次のように設定します。

$ export KWARTZ_OPTIONS='-l erb -s'    # sh, bash
$ setenv KWARTZ_OPTIONS '-l erb -s'    # csh, tcsh

また環境変数に設定したオプションよりも、実際にコマンドラインで指定されたオプションのほうが優先されます。 例えば上のような設定を行っていても、コマンドラインで -l eruby と指定されれば、変換先の言語としてERBでなくeRubyが使用されます。

なお環境変数のパースは、String#split(' ')で行っているだけです。 そのため、あまり複雑なオプションを指定してもうまく解釈されません。


テンプレートをRuby/PHPのメソッドへコンパイルする

付属のmkmethodスクリプトを使用することで、コンパイルしたテンプレートをRubyまたはPHPのメソッドとして定義することができます。

mkmethodの使い方は次の通りです。

bash$ mkmethod -h
Usage: mkmethod [-svh] [-p file] [-l lang] [-M module] [-m method] file.html
  -p file            : presentation logic file
  -l lang            : ruby/ruby2/eruby/erb/php/php2 (default 'ruby2')
  -M module/class    : module/class name (default none)
  -m method          : method name (default 'expand_' + file)
  -A arg1,arg2,...   : method arguments (default nil)
  -s                 : sanitizing (equals to '--escape=true')
  -h, --help         : print help and exit
  -v                 : print version and exit
  --optname=value    : options for kwartz

コマンドラインオプションとして '-l ruby' や '-l eruby' や '-l php' を指定した場合は標準出力に出力するメソッドとして、また '-l ruby2' や '-l erb' や '-l php2' を指定した場合は文字列を返すメソッドとして定義されます。デフォルトはruby2です。

bash$ mkmethod -p hoge.plogic hoge.html
  def expand_hoge(_args)
    user = _args[:user]
    list = _args[:list]
    _s = ''
    _s << "Hello " << (user).to_s << "!\n"
    _s << "<ul>\n"
    for item in list do
      _s << "  <li>" << (item).to_s << "</li>\n"
    end
    _s << "</ul>\n"
    return _s
  end

bash$ mkmethod -l php2 -p hoge.plogic hoge.html
<?php
        function expand_hoge(&$_args) {
                $user = &$_args['user'];
                $list = &$_args['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;
        } // function end
?>

またコマンドラインオプション '-M' を指定すると、Rubyではモジュールが、PHPではクラスが定義されます。

bash$ mkmethod -p hoge.plogic -M Hoge hoge.html
module Hoge
  def self.expand_hoge(_args)
    user = _args[:user]
    list = _args[:list]
    _s = ''
    _s << "Hello " << (user).to_s << "!\n"
    _s << "<ul>\n"
    for item in list do
      _s << "  <li>" << (item).to_s << "</li>\n"
    end
    _s << "</ul>\n"
    return _s
  end
end

bash$ mkmethod -l php2 -p hoge.plogic -M Hoge hoge.html
<?php
class Hoge {
        function expand_hoge(&$_args) {
                $user = &$_args['user'];
                $list = &$_args['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;
        } // function end
} // class end
?>

mkmethodでは、KwartzのAnalyze機能を使ってテンプレートのグローバル変数を自動的に調べます。 またコマンドラインオプション -A を使用すると、メソッドの引数を指定できます。

bash$ mkmethod -p hoge.plogic -M Hoge -A 'user,list' hoge.html
module Hoge
  def self.expand_hoge(_args)
    user = _args[:user]
    list = _args[:list]
    return self._expand_hoge(user, list)
  end
  def self._expand_hoge(user, list)
    _s = ''
    _s << "Hello " << (user).to_s << "!\n"
    _s << "<ul>\n"
    for item in list do
      _s << "  <li>" << (item).to_s << "</li>\n"
    end
    _s << "</ul>\n"
    return _s
  end
end

bash$ mkmethod -l php2 -p hoge.plogic -M Hoge -A 'user,list' hoge.html
<?php
class Hoge {
        function expand_hoge(&$_args) {
                $user = &$_args['user'];
                $list = &$_args['list'];
                return Hoge::_expand_hoge($user, $list);
        } // function end
        function _expand_hoge(&$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;
        } // function end
} // class end
?>

このほか、サニタイズを行う -s や --escape=true も指定できます。


W3C Markup Validationサービスを使用する

W3Cでは、HTMLファイルが正しいかどうかをチェックするサービスを提供しています(http://validator.w3.org)。 Kwartzを使用したHTMLファイル(プレゼンテーションデータファイル)をこのサービスでチェックすると、当然ながらkd属性のせいでエラーになります。

そこで、HTMLファイルからkd属性を取り除いてから、このサービスに送信するPHP/CGIスクリプトを用意しました。 Kwartzのアーカイブの中に validator.{php,cgi} というスクリプトがありますので、これを適当なWebサーバで実行してください。 またKwartzホームページでも試すことができます


予約語と同じ変数名を使用しない

RubyやJSPのように、変数に「$」のような特別な記号を使わない言語では、変数名が予約語と同じにならないようにしてください。 また予約語ではなくても、実質的に予約語のようなもの(定義済み変数や組み込み関数など)もありますので、それらともかぶらないような変数名にしたほうがよいでしょう。

これらと同じ名前の変数を使用すると、奇妙なエラーに悩まされますので注意してください。

例えば、Rubyの予約語は次の通りです(『オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル』より抜粋)。

Rubyの予約語一覧
    BEGIN    class    ensure   nil      self     when
    END      def      false    not      super    while
    alias    defined? for      or       then     yield
    and      do       if       redo     true
    begin    else     in       rescue   undef
    break    elsif    module   retry    unless
    case     end      next     return   until

JSP(およびJSTLのExpression Language)における予約語や定義済変数名は次の通りです (『JavaServer Pages(TM) Standard Tag Library Specification (Final Release)』より抜粋)。

JSPおよびJSTL式言語における予約語と定義済変数の一覧
    and		or	not
    eq		ne	lt	le	gt	ge
    div		mod
    true	false		null
    empty
    page	pageScope	request		requestScope
    session	sessionScope	application	applicationScope
    header	headerValues	param		paramValues
    cookie

なおPHPでは変数名の前に「$」をつけるので、このような問題はありません。 しかし、例えば現在はPHPで開発しているけど将来はJavaにするかもしれないという場合は、やはり変数に予約語と同じ名前をつけないようにしてください。


HTTPリクエストパラメータを表す変数の名前をparamにする

JSTLの式言語(Expression Language)では、HTTPリクエストパラメータは変数paramで表されており、他の名前にすることができません。 これにならって、他の言語(RubyやPHP)を使う場合でもパラメータはparamという変数で表すと、テンプレートのポータビリティが向上します。

例えば次のようなフォームでデータが入力されるとします。

<form action="/cgi/form.cgi" method="post">
  ユーザ名: <input type="text" name="user"><br>
  年齢:     <input type="text" name="age"><br>
  <input type="submit">
</form>

そして、これを表示するために次のようなプレゼンテーションデータを作成したとします。

入力の確認:
 ユーザ名:<span id="value:param['user']">Foobar</span><br>
 年齢:    <span id="value:param['age']">99</span><br>

このプレゼンテーションデータをどの言語でも利用できるようにするには、パラメータを表す変数をparamという名前に統一する必要があります。

例えばRubyでは次のようにします。

require 'cgi'
cgi = CGI.new
param = {}
cgi.params.each do |key,value|
  param[key] = value.first
end
....
s = File.open('form.rb') { |f| f.read }
s.untaint
eval s
## または ERuby::import('form.rhtml') など

PHPでは、参照による代入を用いるのがいいでしょう。

<?php
     $param = &$_REQUEST;
     ....
     include('form.php');
 ?>

同様な理由で、クッキーを表す変数名はcookie、セッションを表す変数名はsessionに統一するのがよいでしょう。

ただし、JSPではパラメータの値を設定することができません(*9)。 例えばPHPでは「$_POST['user'] = trim($_POST['user']);」のようにパラメータを変更できますが、JSPではそれができないのです。 これが困る場合は、パラメータをHashMapなどに格納しなおし、paramではない別の名前(例えばparamsなど)をつけてください。

// Servletの例
public void doPost(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {
   
   // HTTPリクエストパラメータをとりだし、HashMapに格納する
   String[] names = { 'name', 'age' };
   for (int i = 0; i < names.length; i++) {
      String key   = names[i];
      String value = (String)request.getParameter(key);
      if (value != null) {
         value = value.trim();            // 前後の空白を取り除く
         params.put(key, value); 
      }
   }
   
   // HashMapオブジェクトをparamsという名前で使用する。
   // JSP(JSTL)中ではparamを使わずparamsを使用する
   request.setAttribute("params", params);
   
   // JSPを呼び出す。
   String filename = "form.jsp";
   String url = filename;
   request.getRequestDispatcher(url).forward(request, response);
}
(*9)
これはHttpServletRequestにgetParameter()はあってもsetParameter()はないことが理由です。

条件式には論理式を書く

if文や3項演算子における条件式の解釈は、各言語によって大きくばらつきがあります。 そのため、条件式ではなるべく論理式を書くようにしてください。

例えば次のようなプレゼンテーションデータを考えます。

<font id="if:error" color="red">
  エラーが発生しました:#{error}#
</font>

この場合、変数errorの値によって各言語ごとに条件式の解釈が異なります。 特にJSTLのExpression Languageは、「true以外はすべて偽」という思い切った方針のようです。

各言語における条件式の解釈結果
変数errorの値 Ruby PHP JSP(JSTL) Velocity
空の文字列('')
空ではない文字列
0
数字(0以外)
true
false
null

このような違いがあるため、テンプレートの移植性を高めるにはif文や3項演算子の条件式には論理式を書くようにしてください。 例えば「:if(error)」ではなく「:if(error!=null)」や「:if(error!='')」と書いてください。 また「:if(!error)」ではなく「:if(error==null)」や「:if(error=='')」と書いてください。

また文字列が空かどうかを調べるには、予約語emptyを使用してください。 emptyについては「empty」をご覧下さい。



付録

付録1:Kwartzコマンド

テンプレートを出力用プログラムへコンパイルするには、Kwartzコマンドを使用します。

そのまえに用語のおさらいをしておきます。

コンバート(Convert)
プレゼンテーションデータを中間コードへ変換することです。
トランスレート(Translate)
中間コードを出力用プログラムへ変換することです。 これには、プレゼンテーションロジックをマージすることも含みます。
コンパイル(Compile)
プレゼンテーションデータとプレゼンテーションロジックから、出力用プログラムを生成することです。 つまり、コンパイルとはコンバートとトランスレートの両方を行うことです。

コンパイル/コンバート/トランスレートを行うには、Kwartzスクリプトを使用します。

使い方:

kwartz [options...] [-p plogic-file] file.html > file.output

ファイルをひとつずつ指定して処理します(ノーマルモード)。

kwartz -O outfile-suffix [-P plogic-suffix] [options...] *.html

複数のファイルをまとめて処理します(バッチモード)。 出力ファイルの拡張子を -O で、プレゼンテーションロジックファイルの拡張子を -P で指定します。

オプションは次の通りです。

-h, --help
ヘルプを表示。
-v
バージョン情報を表示
-a action
実行する処理。'convert', 'translate', 'compile', 'analyze' のどれか。デフォルトは'compile'。
-l lang
ターゲット言語を指定。指定できるのは ruby, ruby2, php, php2, jsp, eruby, erb, erbscan, velocity.
-s
サニタイズを行う。--escape=trueと同じ。ただしVelocityはサニタイズできない。
-p plogic-file
プレゼンテーションロジックのファイル名。「,」で区切ることで、複数のファイル名を指定できる。
-O outfile-suffix
出力用スクリプトの拡張子。これを指定するとバッチモードになり、標準出力ではなくファイルに直接出力される。
-P plogic-suffix
プレゼンテーションロジックファイルの拡張子。バッチモードで使用する。
-T
タイムスタンプを比較し、出力用スクリプトのほうが新しければ何も実行しない。バッチモードで使用する。
-S
メッセージの出力を抑える。バッチモードで使用する。
--attr_name=name
ディレクティブで使用する属性名。デフォルトは 'kd'。
--charset=charset
文字コード。JSPにおいて '<%@ page contentType="text/html; charset=charset "%>' を出力する。
--delete_idattr=true|false
プレゼンテーションデータから id="name" を取り除く。
--enable_eruby=true|false
プレゼンテーションロジック中にeRuby(ERB)の記述を許す。「%」記法が使用できる。 変数 __lang__ でターゲット言語が参照できる。ERBがインストールされていることが必要。
--escape=true|false
式をサニタイズして出力する。
--even_value=value
偶数のときの値。デフォルトは「'even'」。ディレクティブFOREACHとLISTで使われる。
--footer=string
フッター文字列。
--header=string
ヘッダー文字列。 JSPではデフォルトで '<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>' が設定される。 これを出力したくないときは --header='' とする。
--include_path=dir1,dir2],...
includeディレクティブで読み込むファイル(プレゼンテーションデータファイル)のディレクトリを指定する。
--load_path=dir1,dir2],...
loadディレクティブや:load()文で読み込むファイル(プレゼンテーションロジックファイル)のディレクトリを指定する。
--odd_value=value
奇数のときの値。デフォルトは「'odd'」。ディレクティブFOREACHとLISTで使われる。

使い方の例です。


付録2:PL(Presentation Language)のBNF

(undocumented)