Kwartzユーザーズガイド
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 Velocity や Template-Toolkit のようにHTMLテンプレートのデザインを崩してしまうことがありません。
- 任意のテキストファイルで使用可能
-
Kwartzでは、Kwartz用の属性がついたタグのみを認識し、それ以外のタグはただのプレーンテキストとみなします。 またXMLパーサーを使用せず独自のパーサーを使用しています。 そのため、Enhydra XMLC や amrita のようにXMLやHTMLでしか使用できないということはなく、任意のテキストファイルで使用可能です。
- 自動サニタイズ機能をサポート
-
Kwartzでは、サニタイズを自動的に行うようにすることができます。 つまり、いちいち「
CGI.escapeHTML(var)
」や「htmlspecialchars($var)
」と書く必要がありません。 またサニタイズ機能はオン/オフすることができます。 さらに、ある部分だけをサニタイズする/しないを細かく指定できます。
- (*2)
- 'Quartz'と同じように発音してください。
簡単なサンプル
Kwartzは、テンプレートをプレゼンテーションデータとプレゼンテーションロジックとに分けて記述します。 ここではその例を示します。
まずプレゼンテーションデータの例です。
- 「
#{var}#
」は変数varの値を出力することを表します。 - 「
id="xxx"
」はそのエレメントにxxxという名前で「目印」をつけること、つまり操作対象とすることを表します。
プレゼンテーションデータ(example.html):
<table> <tr id="xxx"> <td>#{var}#</td> </tr> </table>
次はプレゼンテーションロジックの例です。 プレゼンテーションロジックでは、プレゼンテーションデータにつけた「目印」に対して操作を行います。
- 「
:elem(xxx)
」は「xxx」という名前で目印がつけられたエレメント(Element, ここでは<tr>
から</tr>
まで)を表します。 - 「
@stag
」は開始タグ(Start tag, ここでは<tr>
)を表します。 - 「
@cont
」は内容(Content, ここでは<td>#{user}#</td>
)を表します。 - 「
@etag
」は終了タグ(End tag, ここでは</tr>
)を表します。 - 「
:foreach(var=list) ... :end
」は繰り返しを表します。 つまり、開始タグから終了タグまでを繰り返すようにエレメントの定義を変更しているわけです。
プレゼンテーションロジック(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つのマクロを自動生成します。
- マクロ「stag_xxx」 … 開始タグ(start tag)を表します。
- マクロ「cont_xxx」 … 内容(content)を表します。つまり、開始タグと終了タグで囲まれた範囲を表します。
- マクロ「etag_xxx」 … 終了タグ(end tag)を表します。
- マクロ「elem_xxx」 … エレメント(element)を表します。エレメントは、開始タグ、内容、終了タグからなります。
例えば次のようなプレゼンテーションデータがあるとします。
<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)」という名前がついています。
プレゼンテーションロジック
プレゼンテーションロジックは次のようにして記述します。
- 中間言語であるPLで記述する。
- マクロを上書きする形で記述する。
次がプレゼンテーションロジックの例です。 マクロ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();
プレゼンテーションロジックの応用例
例で示したプレゼンテーションロジックは、開始タグから終了タグまでを繰り返すものでした。 ここでは他の例を紹介します。
- 内容だけを繰り返すことができます。<dl></dl>で使うのに向いています。
:macro(elem_list) :expand(stag_list) :foreach(item=itemlist) :expand(cont_list) :end :expand(etag_list) :end
or:elem(list) @stag :foreach(item=itemlist) @cont :end @etag :end
- 内容のかわりに、変数や式などの値を出力することができます。
:macro(elem_list) :expand(stag_list) :print(item['key']) :expand(etag_list) :end
or:elem(list) @stag :print(item['key']) @etag :end
- エレメントのかわりに、変数や式などの値を出力することができます。
:macro(elem_list) :print(item['key']) :end
or:elem(list) :print(item['key']) :end
- エレメント全体を、別のマクロで置き換えることができます。
:macro(elem_list) :expand(other_macro) :end :macro(other_macro) :print('...') :print('...') :end
or:elem(list) :expand(other_macro) :end :macro(other_macro) :print('...') :print('...') :end
- 内容だけを残し、開始タグと終了タグを消すことができます。
そのためには、開始タグと終了タグをコメントアウトします。
:macro(elem_list) ## :expand(stag_list) :expand(cont_list) ## :expand(etag_list) :end
or:elem(list) ## @stag @cont ## @etag :end
- エレメント全体を消すことができます。これは、ダミーデータを消すときに便利です。
:macro(elem_list) ## :expand(stag_list) ## :expand(cont_list) ## :expand(etag_list) :end
or:elem(item) ## @stag ## @cont ## @etag :end
- 複雑なプレゼンテーションロジックを含めることができます。
:macro(elem_list) :set(ctr = 0) :foreach(item=itemlist) :set(ctr += 1) :set(color = ctr%2==0 ? 'red' : 'blue') :expand(stag_item) :expand(cont_item) :expand(etag_item) :end :end
or:elem(list) :set(ctr = 0) :foreach(item=itemlist) :set(ctr += 1) :set(color = ctr%2==0 ? 'red' : 'blue') @stag @cont @etag :end :end
ここで重要なのは、プレゼンテーションロジックにはタグ名や属性名が一切出てきていないという点です。 プレゼンテーションデータのほうでどんなにタグを変更したとしても、プレゼンテーションロジックはまったく変更する必要はありません。
つまり、プレゼンテーションデータとプレゼンテーションロジックとが完全に分離されているわけです。
まとめ
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)
開発者が手動で作成するもの:
- プレゼンテーションデータ(HTMLファイル)
- プレゼンテーションロジック
- メインプログラム
自動生成されるもの:
- 中間コード
- 出力用プログラム
- Webページ
用語:
- コンバート(Convert)
- テンプレートファイルを中間コードに変換すること。
- トランスレート(Translate)
- 中間コードとプレゼンテーションロジックとをマージして出力用プログラムに変換すること。
- コンパイル(Compile)
- コンバートしてトランスレートすること。 つまりプレゼンテーションデータとプレゼンテーションロジックから出力用ファイルを生成すること。
ディレクティブ
ディレクティブとは?
Kwartzでは、プレゼンテーションデータの中にプレゼンテーションロジックを埋め込むこともできます。 そのための命令を「ディレクティブ(Directive)」といいます。
ディレクティブとは、プレゼンテーションデータの中に埋め込む命令であり、プレゼンテーションロジックを表します。
今まで見てきたマーキング「id="name"
」や「id="mark:name"
」も、ディレクティブのひとつです。
他に、繰り返しや条件分岐のためのディレクティブが用意されています。
Kwartzでは、ディレクティブはid属性またはkd属性を使用します。 通常はid属性を使用しますが、id属性を他の用途で使用した場合にはkd属性を使用します。また両方を同時に使用することもできます。 id属性とkd属性の使い分けについては、「id属性とkd属性について」をご覧ください。
Kwartzではせっかくプレゼンテーションデータとプレゼンテーションロジックとを分離することができるのに、なぜ両者を一体化するような機能も用意されているのでしょう?
それは、開発者の好みに応じてどちらも選べるようにするため、つまり選択肢を増やすためです。 Kwartzは、開発者にどちらか一方を押しつけるようなことはしません。 分離するほうが望ましいのであれば分離すればよいし、一体化するほうが好みであれば一体化すればよいのです。
また両者を一体化すると、他のテンプレートと差別化できないと思われるかもしれません。 しかし一体化してもなお、Kwartzは他のテンプレートと比べて次のような利点があります。
- HTMLデザインを崩しません(Jakarta VelocityやTemplate-Toolkitと比べてみてください)。
- 複雑なプログラミングが必要ありません(DOMプログラミングが必要なEnhydra XMLCと比べてみてください)。
- 実行時の動作が軽くて高速です(amritaと比べてみてください)。
- XMLやHTMLだけでなく、どのようなテキスト形式でも利用できます(XMLCやamritaと比べて見てください)。
- 各種のプログラミング言語で使用できます(このようなテンプレートシステムは他にありません!)。
マーキング
「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> > <a href="/ruby">Ruby</a> > <a href="/ruby/kwartz">Kwartz</a> </span> .... <span id="replace:breadclumbs"> Home > Ruby > Kwartz </span> </body> </html
中間コード:
:macro(stag_breadclumbs) :print(" <span id=\"breadclumbs\">\n") :end :macro(cont_breadclumbs) :print(" <a href=\"/\">Home</a>\n") :print(" > <a href=\"/ruby\">Ruby</a>\n") :print(" > <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属性が使用できます。 ここでは、両者の使い分けについて説明します。
- id属性もkd属性も、機能としては同じです。今までid属性で説明していましたが、すべてkd属性に書いても動作します。
- ひとつのタグにid属性とkd属性の両方を記述した場合、kd属性のほうが優先されます。
例えば「
id="name" kd="foreach:item=list"
」とした場合、kd属性のほうが有効となります。
- ただし、kd属性に記述したディレクティブが
attr
のみの場合は、id属性のディレクティブも有効となります。 例えば「id="name" kd="attr:href=url"
」とした場合、そのエレメントは名前name
を使ってマーキングされます。
- HTMLの仕様では、id属性の値に含めることのできる記号はアンダーバー(
_
)、コロン(:
)、ピリオド(.
)、ハイフン(-
)のみです。 そのため、id="foreach:item=list"
やid="attr:name=value"
という記述はHTMLの仕様からみると正しくありませんので、id="foreach:item:list"
やid="attr:name:value"
と書くほうがよいでしょう。
- kd属性は、HTMLの仕様にはない独自の属性です。 そのため、そのままではW3C Markup Vaidation ServiceのようなHTML検証ツールでエラーとなります。 そこで、kd属性を取り除いたうえでW3C Markup Vaidation Serviceに送信するCGI/PHPスクリプトを用意しました。 スクリプトはKwartzのアーカイブの中に含まれています。 またこちらでも実行できます。
- kd属性の名前は、コマンドラインオプション
--attr_name=xxx
で変更することができます。 例えばkd属性のかわりにstyle属性を使う場合は、 --attr_name=style としてください。
注意事項
ディレクティブを使用する際の注意事項です。
- ひとつのid属性またはkd属性にはひとつのディレクティブしか記述できません。
例えば「
id="set:var=100;value:var"
」のような記述はエラーになります。## NG <span id="set:var=100;value:var">???</span>
- ただし、attrディレクティブだけは他のディレクティブといっしょに記述できます。
また複数の
attr
ディレクティブを設定することもできます。## OK <foo id="foreach:var=list;attr:class=var.class;attr:href=var.href"> .... </foo>
- タグにディレクティブを含めた場合、終了タグは省略できません。
また
<foo .../>
のような空要素タグは使用できます。## NG <ul> <li id="value:item">foo </ul> ## OK <a id="attr:name=refname"/>
- ディレクティブしか含まないような<span></span>は、削除されます。
例えば「
Hello <span id="value:name">World</span>!
」というプレゼンテーションデータは、<span></span>が削除されて「:print('Hello ', name, '!')
」という中間コードに変換されます## プレゼンテーションデータ Hello <span id="value:name">World</span>! ## 中間コード :print("Hello ") :print(name) :print("!\n")
- ディレクティブを含むタグでは、ほかの属性は必ず「
attr="value"
」の形で記述し、属性名や属性値を省略しないようにしてください。 これはHTML/XML以外でもKwartzを使用できるようにするための意図した仕様であり、バグではありません。## NG <input type="checkbox" id="foreach:item=list" checked/> ## OK <input type="checkbox" id="foreach:item=list" checked="checked"/> ## NG <option id="attr:value=item.value" #{item.selected ? 'selected' : ''}#/> ## OK <option value="#{item.value}#" #{item.selected ? 'selected' : ''}#>
- ディレクティブ中では、記号「
>
」を使わないようにしてください。 例えば「<span id="while:i>0">
」というタグは、「<span id="while:0<i">
」と記述してください。
これは、Kwartzではプレゼンテーションデータの字句解析を正規表現によるパターンマッチで行っているためです。 そのため、「<span id="while:i>0">
」は「<span id="while:i>
」と「0">
」とに分かれて解釈されてしまうので、うまくコンバートできません。注意してください。<span id="while:i>0"> ... </span> ## NG <span id="while:0<i"> ... </span> ## OK
- 「
id="set:var=flag?v1:v2"
」や「id="value:flag?v1:v2"
」は文法エラーになります。 これは、「:v2
」が「:print
」や「:if
」のように文の開始とみなされるからです。 そのため、「:v2
」をカッコで囲むか、「:
」と「v2
」の間に空白をいれるようにしてください。<span id="set:var=flag?v1:v2"/> ## NG <span id="set:var=flag?v1:(v2)"/> ## OK <span id="set:var=flag?v1: v2"/> ## OK
プレゼンテーション用言語「PL」
Kwartzが使用している中間言語を、PL(Presentation Language)といいます。 PLは、次の2つの用途で使用されます。
- プレゼンテーションデータから自動生成した中間コードを表すため
- プレゼンテーションロジックを記述するため
つまり、KwartzではプレゼンテーションデータもプレゼンテーションロジックもPLで表現されるわけです。
この章では、PLの書き方について説明します。
コメント
「#
」から改行まではコメントになります。
ただし、将来は「#*
」や「#@
」などを特別扱いする可能性があります。
そのため、できればコメントは「##
」にしてください。
## ここはコメント
文字列
文字列は「"..."
」または「'...'
」で表します。
前者には改行(Line Feed)「\n」と復改(Carriage Returen)「\r」とタブ(Tab)「\t」を含めることができます。
'foo bar' ## 文字列 "foo bar\n" ## 改行を含む文字列
真偽値、null
式の中で true
、false
、null
が使えます。
:set(flag = obj == null? true : false)
true
、false
、null
は各言語において次のように変換されます。
変換先言語 | 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
いくつかの変数がでてきますが、これらは次のように分類できます。
- グローバル変数
-
変数
title
とitem_list
は、メインプログラムからテンプレートに渡される変数であり、メインプログラムで値を設定する必要があります。 Kwartzではこれをグローバル変数と呼んでいます。 - ローカル変数
-
変数
item
とitem_ctr
とcolor
はテンプレートの中だけで使用される変数であり、メインプログラムで設定する必要はありません。 Kwartzではこれをローカル変数と呼んでいます。
kwartzコマンドにオプション -a analyze
をつけて起動すると、テンプレートを分析してグローバル変数とローカル変数を報告してくれます。
$ kwartz -p analyze.plogic -a analyze analyze.html global variables: title item_list local variables: color item item_ctr
このとき、Kwartzは次のようなルールでグローバル変数とローカル変数とを判別しています。
- 変数が初めて現れたときに…
- 代入文の左辺に現れた変数はローカル変数
- foreach文のループ変数として現れた変数はローカル変数
- それ以外はグローバル変数
またグローバル変数へ代入したり、グローバル変数をループ変数としていると、分析時に警告を出します。 なぜなら、テンプレートシステムはメインプログラムで設定されたデータの表示のみを行うべきであり、それらを変更すべきではないから、つまりグローバル変数を変更すべきではないからです(変更してよいのはローカル変数のみのはずです)。 そのほか、初期化されていないローカル変数が使われている場合も分析時に警告が出ます。
テンプレート(プレゼンテーションデータとプレゼンテーションロジック)が複雑になると、メインプログラムで設定しなければならない変数がどれか、わかりづらくなることがあります。 そのようなときは、この分析機能を利用してください。変数名のタイプミスも見つけやすくなります。
注意事項
- 「
:print(condition?v1:v2)
」や「:set(var=flag?v1:v2)
」は文法エラーになります。 これは、「:v2
」が「:print
」や「:if
」のように文の開始とみなされるからです。 そのため、「:v2
」をカッコで囲むか、「:
」と「v2
」の間に空白をいれるようにしてください。:set(var=flag?v1:v2) ## NG :set(var=flag?v1:(v2)) ## OK :set(var=flag?v1: v2) ## OK
- マクロ定義やマクロ展開が入れ子になっていると、コンパイル時に無限ループになってしまいます。
入れ子にしないように注意してください。
:macro(elem_foo) :expand(stag_foo) :expand(elem_foo) ## 入れ子になっている :expand(etag_foo) :end :expand(elem_foo) ## 無限ループ
未実装(または検討中)の機能
以下の機能は実装されていませんが、将来において実装されるかもしれません。
- break, continue
- ユーザ定義関数
- メソッド呼び出し
- 引数つきマクロ
- 配列やハッシュの作成
実践的な例
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ファイルを表示するときは、「次に進む」や「前に戻る」というリンクをつけることが多いです。 このリンクを、次のような方針で作ります。
- メインプログラム側で、次のページや前のページを表すリンクのURLを変数next_url、prev_urlに設定します。 もし次のページや前のページがない場合は、変数にはnullを設定します。
- テンプレート側で、変数next_urlとprev_urlをもとにタグ<a></a>を使ってリンクを表示します。 ただし変数がnullの場合はタグは表示させません。
- リンクは、文章の先頭と最後尾の両方に置きます。
プレゼンテーションデータ(navilink.html):
<html> <body> <span id="mark:navilink"> <a id="mark:prev" href="#{prev_url}#"> < Previous page </a> <a id="mark:next" href="#{next_url}#"> Next page > </a> </span> ...... ...... ...... <span id="mark:navilink2"> < Previous page Next page > </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 " < Previous page\n" print " </a>\n" else print " < Previous page\n" end print " \n" if next_url != nil then print " <a href=\"", next_url, "\">\n" print " Next page >\n" print " </a>\n" else print " Next page >\n" end ...... ...... ...... if prev_url != nil then print " <a href=\"", prev_url, "\">\n" print " < Previous page\n" print " </a>\n" else print " < Previous page\n" end print " \n" if next_url != nil then print " <a href=\"", next_url, "\">\n" print " Next page >\n" print " </a>\n" else print " Next page >\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 << " < Previous page\n" _s << " </a>\n" else _s << " < Previous page\n" end _s << " \n" if next_url != nil then _s << " <a href=\"" << (next_url).to_s << "\">\n" _s << " Next page >\n" _s << " </a>\n" else _s << " Next page >\n" end ...... ...... ...... if prev_url != nil then _s << " <a href=\"" << (prev_url).to_s << "\">\n" _s << " < Previous page\n" _s << " </a>\n" else _s << " < Previous page\n" end _s << " \n" if next_url != nil then _s << " <a href=\"" << (next_url).to_s << "\">\n" _s << " Next page >\n" _s << " </a>\n" else _s << " Next page >\n" end _s << "\n" _s << " </body>\n" _s << "</html>\n"
PHP:
<html> <body> <?php if ($prev_url != NULL) { ?> <a href="<?php echo $prev_url; ?>"> < Previous page </a> <?php } else { ?> < Previous page <?php } ?> <?php if ($next_url != NULL) { ?> <a href="<?php echo $next_url; ?>"> Next page > </a> <?php } else { ?> Next page > <?php } ?> ...... ...... ...... <?php if ($prev_url != NULL) { ?> <a href="<?php echo $prev_url; ?>"> < Previous page </a> <?php } else { ?> < Previous page <?php } ?> <?php if ($next_url != NULL) { ?> <a href="<?php echo $next_url; ?>"> Next page > </a> <?php } else { ?> Next page > <?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"/>"> < Previous page </a> </c:when> <c:otherwise> < Previous page </c:otherwise> </c:choose> <c:choose> <c:when test="${next_url != null}"> <a href="<c:out value="${next_url}" escapeXml="false"/>"> Next page > </a> </c:when> <c:otherwise> Next page > </c:otherwise> </c:choose> ...... ...... ...... <c:choose> <c:when test="${prev_url != null}"> <a href="<c:out value="${prev_url}" escapeXml="false"/>"> < Previous page </a> </c:when> <c:otherwise> < Previous page </c:otherwise> </c:choose> <c:choose> <c:when test="${next_url != null}"> <a href="<c:out value="${next_url}" escapeXml="false"/>"> Next page > </a> </c:when> <c:otherwise> Next page > </c:otherwise> </c:choose> </body> </html>
eRuby:
<html> <body> <% if prev_url != nil then %> <a href="<%= prev_url %>"> < Previous page </a> <% else %> < Previous page <% end %> <% if next_url != nil then %> <a href="<%= next_url %>"> Next page > </a> <% else %> Next page > <% end %> ...... ...... ...... <% if prev_url != nil then %> <a href="<%= prev_url %>"> < Previous page </a> <% else %> < Previous page <% end %> <% if next_url != nil then %> <a href="<%= next_url %>"> Next page > </a> <% else %> Next page > <% end %> </body> </html>
ERB:
<html> <body> % if prev_url != nil then <a href="<%= prev_url %>"> < Previous page </a> % else < Previous page % end % if next_url != nil then <a href="<%= next_url %>"> Next page > </a> % else Next page > % end ...... ...... ...... % if prev_url != nil then <a href="<%= prev_url %>"> < Previous page </a> % else < Previous page % end % if next_url != nil then <a href="<%= next_url %>"> Next page > </a> % else Next page > % end </body> </html>
Velocity:
<html> <body> #if ($prev_url) <a href="$!{prev_url}"> < Previous page </a> #else < Previous page #end #if ($next_url) <a href="$!{next_url}"> Next page > </a> #else Next page > #end ...... ...... ...... #if ($prev_url) <a href="$!{prev_url}"> < Previous page </a> #else < Previous page #end #if ($next_url) <a href="$!{next_url}"> Next page > </a> #else Next page > #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"> > </span> </span>
プレゼンテーションロジック:
:elem(breadcrumbs) :set(item_ctr = 0) :foreach(item = breadcrumbs) :set(item_ctr += 1) :if(item_ctr > 1) ## 繰り返しの2回目以降では @elem_arrow ## < を表示する :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 "> " 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 << "> " 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) { ?> > <?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}"> > </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 %>> <% 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 > % 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) > #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':'')}#>男性 <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' : '') %>>男性 <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> <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"> </span></td> <td id="dummy:d1"> </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> </td><td> </td> </tr> </tbody> </table>
プレゼンテーションロジック(calendar.plogic):
:elem(week) :set(day = ' ') :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 = ' ') :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 リファレンスマニュアル』より抜粋)。
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)』より抜粋)。
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で使われる。
使い方の例です。
- プレゼンテーションデータ(file1.html)をコンパイルして、出力用スクリプト(file1.rb)に変換する。
ターゲット言語はRuby。
$ kwartz -l ruby file1.html > file1.rb or $ kwartz -l ruby -O .rb file1.html
- プレゼンテーションデータ(file1.html)とプレゼンテーションロジック(file1.plogic)から、出力用スクリプト(file1.php)を作成する。
ターゲット言語はPHP。
$ kwartz -l php -p file1.plogic file1.html > file1.php or $ kwartz -l php -p file1.plogic -O .php file1.html or $ kwartz -l php -O .php -P .plogic file1.html
- それぞれのプレゼンテーションデータとプレゼンテーションデータから、出力用のJSPファイル(*.jsp)を作成する(バッチモード)。
このとき、文字コードとしてEUC-JPを指定する。
$ kwartz -l jsp -O .jsp -P .plogic --charset=EUC-JP *.html
- プレゼンテーションデータを中間コードにコンバートする。
$ kwartz -a convert file1.html | more
付録2:PL(Presentation Language)のBNF
(undocumented)