Text Formatter


概要

基本的にテンプレート中の特定箇所をプログラマが与えたMapにしたがって置換した結果を得るものです。
テンプレートはHTMLエディッタ等でも基本的に不都合無く編集が行えるので、事務のおねーさんにだってOKっす。(注1)

特徴

仕様

以下の情報を渡すことでフォーマッティングを行います。

生成時 変換を行うテンプレートファイルとエンコーディング
format時 変換表となるMapインスタンス

テンプレートファイルに以下のようにコマンド/キーを記述しておきます。
キーに対応するsubstitute(変換候補)オブジェクト群はMapとしてFormatterに与えます。

コマンド(ignore case) 書式 意味
substitute
#
@#substitute key@
または@##key@
Map上のkeyに対応するvalueに置換。
keyがMap上に存在しない場合は無視される。
exist〜endexist @#exist key@〜
[@##key@]〜
@#endexist@
keyが存在すれば囲まれた範囲が評価される。
存在しなければ無視される。
while〜endwhile @#while key@〜@#endwhile@ 囲まれた範囲をkeyに対応する値の個数だけ繰り返す。
値はMapの配列またはMapのListで無ければならない。
if〜endif @#if key = value1@〜@#endif@
@#if key != value2@〜@#endif@
キーの値に応じて評価される部分を変更する。
通常はexistで対応可能。
foreach〜endforeach @#foreach key@〜
[@##key@]〜
@#endforeach@
囲まれた範囲をkeyに対応する値の個数だけ繰り返す。
値はObject配列またはListでなければならない。
囲まれた範囲に一種類のkeyしか存在しないはこちらが手軽。
他の置換キーがあった場合は同じ値が繰り返し出力される。
"@#"+キーワード、キー、演算子(ifの場合の"=","!=")、suffixである"@"の間には空白(tab/改行を含む)を入れることも省略することもできます。
以下は等価です。
@##key@
@## key @
@##
        key
@
以下も同様です。(日本語もOK)
@#if キーワード!=値@
@#ifキーワード!=値@
@#if キーワード != 値 @
@#if 
   キーワード
   !=
   値
@
字句解析器の都合で、コマンドと関連しない箇所で"@#"、"@@"、"@\"という連続する2文字を使用できなくなっています。
コマンドの直前の'@'もだめ。直後は大丈夫です。
・・・foo@bar・・・                     -> OK 
・・・@#exist abc@@・・・         -> OK
・・・foo@@・・・                         -> NG 
・・・@#exist abc@@@・・・        -> NG 
・・・foo@#bar・・・              -> NG 
・・・foo@#exisy abc@・・・   -> NG
・・・@@#exist abc@・・・         -> NG
エスケープされていない"@"の直後に"#"、"@"、"\"が現れた場合、TextFormatterは文法エラー(WrappedParseException)を発生させます。
これらの文字列をテンプレートファイル中で使用する場合は、先頭の@を'\'でエスケープします。
("\@#" "\@@" "\@\"のように)
"\@"という文字列を"@"と解釈してほしく無い場合は、もちろん"\\@"とします。
・・・foo\@@・・・
・・・foo\@#bar・・・
・・・\@@#exist abc@・・・
・・・@#exist abc@\@@・・・
普通はこの問題が発生するようなtemplateにはならないとは思いますが・・・。
でも事務のおねーさんがこの問題に直面したらまずいので、なんとかすべきではあるとは思っています。
# JavaCCなんぞでやってしまったから・・・Lexなら行けるかと思ったけど、JLexではJavaCCと同様の問題がでそうですし。
# 字句の評価の優先順位の付け方がよくわからないや。

変換の実行は、以下のように行います。

import com.sk_jp.text.*;
  :
TextFormatter   formatter;
formatter = new TextFormatter("file.template", "Shift_JIS");
Map args = new HashMap(); args.put("key1", "value1");
args.put("key2", "value2");
String result = formatter.format(args);
// or formatter.format(args, writer);

変換例

以降でそれぞれのコマンド使用方法を具体的な例で説明します。

1.単純な置き換え

<html>
<head>
<title>@#substitute filetitle@</title>
</head>
<body>
今日の日付は@##date@です。
</body>
</html>

与えるMapのインスタンス

filetitle "今日は"
contents new Date()

結果

<html>
<head>
<title>今日は</title>
</head>
<body>
今日の日付はTue Mar 14 08:35:31 JST 2000です。
</body>
</html>
new Date()ではなく、
DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date())
のように文字列化したものを渡すことももちろん出来ます。

2.エントリの有無によるテンプレートの部分削除

<html>
@#exist stylesheeturl@
<meta http-equiv="Content-Style-Type" content="text/css" />
<link rel="stylesheet" href="@##stylesheeturl@" type="text/css" />
@#endexist@
<body>
@#exist title@<h1>@##title@</h1>@#endexist@
  :
</body>
</html>

与えるMapのインスタンス

stylesheeturl "/sample.css"

結果

<html>

<meta http-equiv="Content-Style-Type" content="text/css" />
<link rel="stylesheet" href="/sample.css" type="text/css" />

<body>
  :
</body>
</html>

"title"というキーに対応する値が無い場合は<h1>タグも出力されない


3.繰り返し

<html><body>
<table><tbody>
<tr><th>ヘッダ1</th><th>ヘッダ2</th></tr>
@#while row@
<tr><td>@##column1@</td><td>@##column2@</td></tr>
@#endwhile@
</tbody></table>
</body></html>

与えるMapのインスタンス

row Mapインスタンスの配列またはMapの集合を格納したListインスタンス

Mapの配列

column1 列1-1
column2 列2-1
column1 列1-2
column2 列2-2
column1 列1-3
column2 列2-3

結果

<html><body>
<table><tbody>
<tr><th>ヘッダ1</th><th>ヘッダ2</th></tr>
<tr><td>列1-1</td><td>列2-1</td></tr>
<tr><td>列1-2</td><td>列2-2</td></tr> 
<tr><td>列1-3</td><td>列2-3</td></tr> 
  :
</tbody></table>
</body></html>

"row"というキーの値は繰り返される個数分のMapの集合となる。
もちろんそのマップには"column1""column2"というキーが存在する。
# こんなにMapインスタンスを多数生成してたら効率悪すぎか?
# しかし、キー名にindex付けを行うなどでは使いづらいし。


4.繰り返しのネスト

<html><body>
<h1>集計結果</h1>
@#while category@
  <table>
  <thead><caption>@##categorytitle@</caption></thead>
  <tbody> 
  @#while item@
        <tr><td>項目名</td><td>@##itemname@</td></tr>
  @#while@
  </tbody></table>
@#while@
</body></html>

与えるMapのインスタンス

category Mapインスタンスの配列またはMapの集合を格納したListインスタンス

Mapの配列

categorytitile 表1
item Map配列
categorytitile 表2
item Map配列
categorytitile 表3
item Map配列

上記のそれぞれの「Map配列」には、キー「itemname」に対応する値を持つMapの配列が格納。

結果

<html><body>
<h1>集計結果</h1>
  <table>
  <thead><caption>表1</caption></thead>
  <tbody>
        <tr><td>項目名</td><td>項目1</td></tr>
        <tr><td>項目名</td><td>項目2</td></tr> 
          :
  </tbody></table>
  <table>
  <thead><caption>表2</caption></thead>
  <tbody>
        <tr><td>項目名</td><td>項目1</td></tr>
        <tr><td>項目名</td><td>項目2</td></tr>
          :
  </tbody></table> 
  <table>
  <thead><caption>表3</caption></thead>
  <tbody>
        <tr><td>項目名</td><td>項目1</td></tr>
        <tr><td>項目名</td><td>項目2</td></tr>
          :
  </tbody></table> 
</body></html>

5.条件による選択

通常は@#exist name@ @#exist@で事足りる。ループ中でその項目ごとにフォーマットを変更できるようにしたい場合に用いる。

<html><body>
<h1>集計結果</h1>
@#while category@
  @#if type=table>@
        <h2>@## title@の選択数</h2>
        <table><tbody>
        @#while item@
          <tr><td>項目名</td><td>@## itemname@</td></tr>
        @#while@
        </tbody></table>
  @#endif@
  @#if type=link@
        <h2><a href="/servlets/listing.jsp?id=@## id@">
                @## title@の一覧</a></h2> 
  @#endif@
@#endwhile@
</body></html>

与えるMapのインスタンス

category Mapインスタンスの配列またはMapの集合を格納したListインスタンス※

※:Mapの配列

if table
title 項目1
item Map配列※2
if link
title 項目2
id 2
if table
title 項目3
item Map配列

※2:Mapの配列

column1 選択項目1
column2 5
column1 選択項目2
column2 3

結果(空白行は見やすくしているだけです)

<html><body>
<h1>集計結果</h1>

<h2>項目1の選択数</h2>
<table><tbody>
  <tr><td>選択項目1</td><td>5</td></tr>
  <tr><td>選択項目2</td><td>3</td></tr> 
        :
</tbody></table> 

<h2><a href="/servlets/listing.jsp?id=2">項目2の一覧</a></h2> 

<h2>項目3の選択数</h2>
<table><tbody>
  <tr><td>選択項目1</td><td>14</td></tr>
  <tr><td>選択項目2</td><td>8</td></tr>
        : 
</tbody></table>
  :
</body></html>

6.二次元の繰り返し

<html><body>
<table><tbody>
<tr>@#while column@<th>@##head@</th>@#while@</tr>
@#while row@<tr>@#while column@<td>@##body@</td>@#while@</tr>@#while@
</tbody></table>
</body></html>

Mapのインスタンス

column Mapインスタンスの配列またはMapの集合を格納したListインスタンス
row Mapインスタンスの配列またはMapの集合を格納したListインスタンス

[row]の値であるMap配列

column Mapインスタンスの配列またはMapの集合を格納したListインスタンス

[column]にはそれぞれ[head][body]というキーを持ったMapの配列が入る。
すみません例が悪かった。今はforeachが使えるのでもうちょっと簡潔になります。

結果

<html><body>
<table><tbody>
<tr><th>ヘッダ1</th><th>ヘッダ2</th><th>ヘッダ3</th></tr>
<tr><td>ボディ1-1</td><td>ボディ2-1</td><td>ボディ3-1</td></tr>
<tr><td>ボディ1-2</td><td>ボディ2-2</td><td>ボディ3-2</td></tr> 
<tr><td>ボディ1-3</td><td>ボディ2-3</td><td>ボディ3-3</td></tr> 
  :
</tbody></table>
</body></html>

注1
JSPも基本的にはHTMLエディッタで編集可能ですが、JSPはプラットフォームのデフォルトエンコーディングに依存しているので配布に向いていないという問題があります。このライブラリはHTMLに限定しませんしね。
また、JSPの場合タグで表現するため、HTMLエディッタでは未知のタグとして扱われてしまいます。
但し、<HEAD></HEAD>内にキーワード("@##nnnn@")を配置するとホームページビルダーというソフトではエラーとして扱われて補正がされてしまいました。(JSPでもほとんど同じ問題はありますが)
エラー自動補正機能のあるHTMLエディッタの場合は、その機能をOFFにした方が良いとおもわれます。