outf.pl とは、greg_calc.pl に実装した &outf 関数を機能拡張した出力ルーチンで、いわゆるフォーマッタの一種。フォーマッタとは、プログラムによって生成されたデータを、ユーザーが好みの体裁で出力するための整形ルーチンの総称だ。例えば、内部表現形式のデータなどを、ユーザーが好みの体裁で出力するためには欠かせない機能といえる。
Perl は元々 sed や awk と同様、『レポート作成用言語』として利用されてきた経緯があって、format という書式制御構造を実装している。つまり Perl 自体に、フォーマッタの機能が組み込まれているわけだ。しかし、それはプレーンテキストを整形することを想定していて、html 文書の整形には適していない。html では『align属性』や『スタイルシート』を使って整形指定をし、テキスト自体は加工しない。そうなると、CGI スクリプトを使って html 文書を整形する場合、必然的に sprintf や printf を使うことになる。
outf.pl は、ぶっちゃけると sprintf を簡便に利用するためのラッパーで、『フォーマット定義関数』『置換関数』『出力関数』を一まとめにしたサブルーチン・ライブラリとなっている。独自の特殊リテラル(outfリテラル)を対象の定数値と関連付けて、ユーザーが記述したテンプレート文字列を一括置換する。
文章での説明が難しいので、執筆を断念した。
outf.pl を使うためには、まず最初に次の記述をする必要がある。
require "outf.pl";
上記の記述は必須となる。以下に主要関数の簡単(?)な書式を示す。
| 概要: | outf リテラルの定義(登録)。 定数値 $xx と __xx (定数値リテラル)、/xx: 〜// (単位名リテラル) を関連付ける。 |
| 引数:$name_list | outf リテラル名を空白文字で区切ったリストで記述する。 個々の outf リテラル名は、先頭が英字から始まる 1〜6文字の英数字(a-zA-Z0-9)でなければならない。 記述例→ 'yy1 mo1 dd1' |
| 引数:@value | $name_list のリテラル名に対応するスカラー値を指定。 スカラー値は 数値, 文字列, 式, スカラー変数, スカラー要素を指定する。 |
| 引数:"clear" | グローバルハッシュ %outf::literal をクリア(初期化)する。 undef %outf::literal; を実行するだけ。 |
| 戻値: | なし(voidコンテキスト)。 |
| 文例: | $year=2008; $mon=6; $mday=27; &outf::def('yy mo dd', $year, $mon, $mday); |
| 文例の解説: | &def 関数の実行によって __yy, __mo, __dd という定数値リテラルと、 /yy: 年// などの単位名リテラルが使用可能となる。これによって、 『__yy年__mo月__dd日』という文字列を『2008年6月27日』 のように置換できるようになる。 |
| 備考: | リテラル名と値は、リテラル名をキーにしたハッシュ配列 %outf::literal に格納される。基本的には、グローバルのハッシュに『キー』と『値』 を代入するだけだが、$name_list は『"』『'』『,』 を記述せずにリストの指定ができるので、タイピングの手間が軽減できる。 見た目もスッキリ記述できるので、可読性が向上する。 |
| 概要: | 変換仕様 一括適用ルーチン(整形関数)。 ぶっちゃけ、sprintf, &short_or_full (内部関数)のラッパー |
| 薀蓄: | 変換仕様(%xx.xx)…型指定文字列、クエリ文字列、書式指定文字列、 フォーマット文字列…様々な呼び方があるが、作者は『変換仕様』 で通すことにする。『変換仕様』の出典は『LSI-C 86(試食版)』の printf のドキュメント。そこに『変換仕様』と書かれてあった… 作者はその呼称を支持することにした。『文字数が少ない』 というのが支持した理由。変換仕様は『型指定子』と 『修飾子(整形指定子)』で構成される。つまり型を変換する conversion の機能と、その型を修飾・整形する modify の機能を併せ持っている。 『呼び名』が統一できていないのは、その二面性に原因がある。 |
| 引数:$type | "%xx.xx"(変換仕様) と "Xxx" 形式が指定できる。 "Xxx" 形式とは、"x", "X" の組み合わせで『文字数』『大文字』 『小文字』の指定をする(英単語専用)。"x", "X" 以外の文字は強制的に除去され、その結果が 1文字以下の文字列となった場合、デフォルトとして "Xxx" がセットされる(英略 3文字)。英語名称の文字数は 2文字(xx)、 3文字(xxx)、フルスペル(xxxx) のいずれかしか指定できない。 "Xxx" 形式の詳細は、説明書モドキの『&short_or_full』 (outf.pl の内部関数)のコメントを参照してほしい。 |
| 引数:@value | $type を適用する値を指定(通常はスカラー変数のリスト)。 記述例→ $year, $mon, $mday, $hour, $min …のようなリスト。 |
| 引数:$name_list | outf リテラル名を空白文字で区切ったリストで記述する。個々の outf リテラル名は、先頭が英字から始まる1〜6文字の英数字(a-zA-Z0-9) でなければならない。 記述例→ 'yy1 mo1 dd1' |
| 戻値:@value | $type 適用後の @value。 |
| 文例1(書式1): | 指定の変数値の全てに "%02d" を適用。 ($mon, $mday, $hour, $min, $sec) = &outf::spf("%02d", $mon, $mday, $hour, $min, $sec); |
| 文例2(書式1): | 指定の変数値の全てに "Xxx" を適用。 $mon="january"; $wday="tuesday"; ($mon, $wday) = &outf::spf("Xxx", $mon, $wday); 結果→ $mon, $wday は "Jan", "Tue" に置換される。 |
| 文例3(書式2): | グローバル変数 %outf::literal の値に "%02d" を適用。 &outf::spf("%02d", "mo dd hh mi ss"); |
| 備考: | 書式1 はリストコンテキスト専用となる。スカラーコンテキストなら、
sprintf を使えば済むことなので。→ $year = sprintf("%04d", $year); リストコンテキスト以外は書式2 と判断する。void(無効)コンテキスト、 スカラーコンテキストで、尚かつ引数が 2つの時は、%outf::literal を操作対象とする。スカラーコンテキスト時の戻値は無規定(undef となる)。 |
| 概要: | 入力文字列に含まれる『outf リテラル』を置換して返す(出力関数)。 |
| 引数:$iline | outf リテラルが記述された入力文字列(テンプレート文字列)。 outf リテラルは、事前に &outf::def で登録済でなければならない。 |
| 戻値:$oline | outf リテラルが記述された入力文字列(テンプレート文字列)。 outf リテラルが置換された後の出力用文字列。 引数に outf リテラルが全く含まれていない場合は、 何もせずに $iline をそのまま戻値として返す。 |
| 文例: | &outf::def("yy mo dd wd tz", 2008, 5, 26, "Mon", "JST-9"); $iline = 'Today: __yy.__mo.__dd __wd (__tz)'; $oline = &outf::conv($iline); |
| 文例の結果: | $oline は、 'Today: 2008.5.26 Mon (JST-9)' …となる。 |
主要関数は上記のとおり、&def, &spf, &conv の 3本だけなのだが、どうも、文章ではうまく説明できない…。
例) 名前を入力すると挨拶を返すサンプル。
#!/usr/bin/perl
# "hello_test.pl"
require "outf.pl";
print 'あなたは誰? > ';
$name = <STDIN>;
chomp($name);
print "\n";
# 今日の日付を取得
($sec, $min, $hour, $mday, $mon, $year, $wd) = localtime;
++$mon; $year += 1900;
@wname = (sunday, monday, tuesday, wednesday, thursday, friday, saturday);
$wday = $wname[$wd];
# 時間によってメッセージ(挨拶)を変える
$mes = "";
($hour > 4 and $hour < 12) && ($mes = 'おはよう!');
($hour > 11 and $hour < 19) && ($mes = 'こんにちは!');
$mes || ($mes = 'こんばんは!');
&outf::def(
"ss mi hh dd mo yy na mes wd",
$sec, $min, $hour, $mday, $mon, $year, $name, $mes, $wday
);
&outf::spf("%02d", "ss mi hh dd mo");
&outf::spf("Xxx", "wd");
foreach (<DATA>) {
chomp;
$line = &outf::conv($_);
print "$line\n";
}
__DATA__
今日: __yy/__mo/__dd __wd.
時刻: __hh:__mi:__ss
__mes __na さん。
お待ちしておりました。
hello_test.pl の実行例↓
G:\usr\lib\outf>hello_test.pl あなたは誰? > foussin 今日: 2008/06/28 Sat. 時刻: 02:56:07 こんばんは! foussin さん。 お待ちしておりました。
どう? CGI スクリプトで使えそうだと思わない? 上記のサンプルは定数値リテラルだけだったので、次は単位名リテラルも使ったサンプルを考えてみる。
#!/usr/bin/perl
# "tan_test.pl"
# 単位リテラルの使用例
require "outf.pl";
@text = <DATA>;
chomp(@text);
for ($i=0; $i<3; ++$i) {
&outf::def("ct xx", $i, $i);
foreach (@text) {
$line = &outf::conv($_);
print "$line\n";
}
}
__DATA__
__ct type0 : __xx /xx:file#s//
__ct type1 : __xx /xx:(真 ! (偽// !)
__ct type2a: __xx /xx:xx<2; lily ! lilies//
__ct type2b: __xx /:xx<2; lily ! lilies//
__ct type3 : __xx /:xx==2; 「今、2 になりました」//
tan_test.pl の実行例↓
G:\usr\lib\outf>tan_test.pl 0 type0 : 0 type1 : 0 (偽 !) 0 type2a: 0 type2b: 0 lily 0 type3 : 0 1 type0 : 1 file 1 type1 : 1 (真 !) 1 type2a: 1 lily 1 type2b: 1 lily 1 type3 : 1 2 type0 : 2 files 2 type1 : 2 (真 !) 2 type2a: 2 lilies 2 type2b: 2 lilies 2 type3 : 2 「今、2 になりました」
ご覧のとおり、単位リテラルには type0 〜 type3 の記述方法がある。この出力例を見れば、英単語の単数形、複数形を表記できることが分かると思う。特に注目してほしいのが type0、type2a だ。これらのタイプでは、定数値が偽値になると、定数値、単位文字列もろとも除去される仕組みになっている。この機能は、次のようなシーンで利用することを想定している。
『0通は未開封』なんて、どう考えても日本語として変でしょ? outf リテラル(定数値リテラル、単位名リテラル)の詳細については、説明書モドキを参照してほしい。