%ENV で HTTP リクエストの情報を調べる

特殊変数 %ENV には、スクリプトが動作しているコンピュータの「環境変数」が入っています。環境変数とは、OS の内部の設定などを設定/記録する為の変数で「名前=値」の組が集まっているものです。環境変数を調べる事でシステムの設定や情報を調べる事ができます。
%ENV の内容を調べるために、以下のスクリプトを実行してみました。

use strict;

while (my ($key, $value) = each %ENV) {
    print "$key=$value\n";
}

palmo の環境での実行結果は以下のようになりました。一部省略しています。

HOMEDRIVE=C:
CLIENTNAME=Console
COMMONPROGRAMFILES=C:\Program Files\Common Files
PROGRAMFILES=C:\Program Files
OS=Windows_NT
(中略)
TEMP=C:\DOCUME~1\palmo\LOCALS~1\Temp
PROCESSOR_REVISION=2701
SYSTEMDRIVE=C:
SYSTEMROOT=C:\WINDOWS
COMSPEC=C:\WINDOWS\system32\cmd.exe

環境変数を調べれば、システムで使われるファイルやフォルダ(ディレクトリ)のパスを調べたり、OS のバージョンを調べる事ができますね。(^_^)


CGI プログラムとして動かした場合、%ENV には環境変数に加えて「HTTP リクエスト」に関する情報も入っています。サイトの利用者が、ページを取得するためにサーバーに送ってくる要求を「リクエスト」と呼びますが、リクエストには「ヘッダー」と呼ばれる「名前: 値」形式のパラメータが付属しています。
例えば Mozilla Firefox 1.5 でこの「ぱるも日記」のトップページ http://d.hatena.ne.jp/palmo/ を要求する際の HTTP リクエストは、以下のようになります。(LiveHTTPHeaders エクステンションを利用しました)

GET /palmo/ HTTP/1.1
Host: d.hatena.ne.jp
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

%ENV には、この「ヘッダー」が入っています。各パラメータの名前には、頭に「HTTP_」が付き、全て大文字になって、「-」(ハイフン)が「_」(アンダーバー)に置き換えられた名前が使われます。例えば、上の「User-Agent」の値は $ENV{'HTTP_USER_AGENT'} に入っています。
また、リクエストの一番最初の行の情報は、頭に「REQUEST_」がついた名前で表されています。サーバーに要求する操作(メソッド)を表す「GET」は $ENV{'REQUEST_METHOD'} に、要求されているページのアドレス「/palmo/」は $ENV{'REQUEST_URI'} に入っています。
他にも、%ENV には CGI スクリプト自身のパスを表す SCRIPT_NAME や、サーバーに関する情報など、CGI の処理に便利な情報が入っています。


確認の為に %ENV の内容を表示する CGI を作りました。

#!/usr/local/bin/perl
use strict;

my $rows = "";
for my $key (sort keys %ENV) {
    $rows .= "<tr><th>$key</th><td>$ENV{$key}</td></tr>\n";
}

print <<HTML;
Content-Type: text/html

<html>
<head><title>ENV Values</title></head>
<style type="text/css">
th, td { font-size: 90%; padding: 0.2em 0.5em; border-bottom: 1px #C00 solid; }
th { text-align: left; background-color: #FCC; border-right: 1px #C00 solid; }
</style>
<body>
<table>$rows</table>
</body>
</html>
HTML

__END__

実行結果は下のページで見ることができます。
envtitle.cgi 実行結果
リクエストヘッダーやサーバーの情報などが確認できるはずです。普段は目に見えませんが、色々な情報が送られているんですね。(^_^)

QUERY_STRING でパラメータを受け取る

先ほどのエントリで %ENV を表示する CGI プログラムを作りましたが、この CGI プログラムの URL の末尾に「?abc」と付けて、開いてみてください。
http://palmo.is.land.to/cgi/v/envtable.cgi?abc
$ENV{'QUERY_STRING'} の値として「abc」が入っているのが確認できると思います。


この「URL の最後に ? を付けて文字列を渡す」というのは、CGI などのウェブアプリケーションに簡単なパラメータを渡す時に利用されます。
HTML で送信フォームを作る為には <form> 要素を利用しますが、この際 method という属性を指定する必要があります。method は、HTTP リクエストに用いるメソッドを指定する属性で "GET" か "POST" を指定するのですが、これに "GET" を指定すると、URL の最後に「?」を付けて、「名前=値」の形の各パラメータが「&」でつなげられて渡されます。
例えば、以下のように HTML でフォームを作ってみます。

<form action="query.cgi" method="GET">
  <input type="text" name="value" />
  <input type="submit" value="送信" />
</form>

この場合、以下のように表示されます。

入力欄に何か入力して送信ボタンを押すと、ウェブブラウザソフトは自動的に「query.cgi?value=入力した内容」という URL へと移動します。


つまり CGI プログラム側で $ENV{'QUERY_STRING'} を参照すれば、どのような値が入力されたのか取得できる、という事になります。QUERY_STRING には「abc=012&def=345&ghi=678」のような文字列が入るので、以下のようにすれば各パラメータの名前をキーにしたハッシュを生成する事ができるはずです。

my %param = map { /([^=]+)=(.+)/ } split /&/, $ENV{'QUERY_STRING'};

まず $ENV{'QUERY_STRING'} を & で split すると、「名前=値」のリストを得る事ができます。そのリストに対して名前と値をキャプチャする正規表現map すれば (名前, 値, 名前, 値, 名前, 値...) というリストが得られるので、それをそのままハッシュ変数へ代入しています。
これにより、$param{'value'} を見れば「value=ABC」の "ABC" の部分を得る事ができますね。(^_^)


確認の為に、こんな CGI を作ってみました。

#!/usr/local/bin/perl
use strict;

my %param = map { /([^=]+)=(.+)/ } split /&/, $ENV{'QUERY_STRING'};

my $value;
if (exists $param{'value'}) {
    $value = "あなたが入力したのは「$param{'value'}」です。";
} else {
    $value = "入力してください。";
}

print <<HTML;
Content-Type: text/html

<html>
<head><title>QUERY_STRING Test</title></head>
<body>
<h1>QUERY_STRING Test</h1>

<form action="$ENV{'SCRIPT_NAME'}" method="GET">
  <input type="text" name="value" />
  <input type="submit" value="送信" />
</form>
<p>$value</p>

</body>
</html>
HTML

__END__

さきほど例として書いたフォームを使って、自分自身($ENV{'SCRIPT_NAME'})へ入力した内容(value)を送信するようにしています。
パラメータとして value が渡されていれば、その値を表示します。
実行結果は、以下で見る事ができます。
query.cgi 実行結果
これで、利用者からの入力を受け取って処理した内容を表示するような、動的なページも作る事ができそうですね。できる事が拡がっていくのは、とても楽しいです。(^_^)

HTML::Template(3) TMPL_LOOP

引き続き HTML::Template のテンプレートタグを勉強します。今回は <TMPL_LOOP>。
<TMPL_LOOP> タグは、表示するページの一部分を繰り返したい時に使います。例えば「商品のカタログ」を表示するページを作っている場合、複数の商品の情報(名前や値段など)をそれぞれ表示する為に、商品の数だけ一部分(表の列など)を繰り返す必要がありますよね。


<TMPL_LOOP> タグに利用できる属性は NAME だけです。TMPL_VAR と同じく、TMPL_LOOP が利用するデータを NAME 属性で指定するのですが、TMPL_VAR とは違い、その内容が直接置き換わるわけではなく、<TMPL_LOOP> という開始タグと、</TMPL_LOOP> という終了タグで繰り返す部分を挟み込みます。

<TMPL_LOOP NAME="DATASET">
  この部分が繰り返される
</TMPL_LOOP>

この場合、DATASET の中にあるデータの数だけ、「この部分が繰り返される」という文章が表示される事になります。


TMPL_VAR の時と同じように、TMPL_LOOP に与えるデータも HTML::Template オブジェクトの param メソッドで与えるのですが、次のようなデータ構造で渡します。

$tmpl->param( FRUITS => [
                { NAME => "Apple",  COLOR => "Red" },
                { NAME => "Banana", COLOR => "Yellow" },
                { NAME => "Peach",  COLOR => "Pink" },
              ] );

ここで復習。

  • A => B というのは "A", B と同じ意味(シンタックスシュガー)
  • [ ] でリストを囲んだ場合、無名配列が作られてそのリファレンスが得られる
  • { } でリストを囲んだ場合、無名ハッシュが作られてそのリファレンスが得られる

つまり、param に渡しているのは「"FRUITS" という文字列」と、「無名配列へのリファレンス」です。その無名配列の中には果物のデータが3つ入っています。果物のデータは「無名ハッシュへのリファレンス」で、中には "NAME" と "COLOR" がそれぞれキーとなっている2つの要素が入っています。
ちょっと複雑な構造ですが、基礎を勉強したお陰かすんなり理解できました。「配列はスカラー値の集まり」「リファレンスはスカラー値の一種」という事を覚えていれば、どうしてこのようにリファレンスを多用しているのかもわかります。Perl での2次元配列はリファレンスを使って表現するのでしたね。(^_^)


このようにデータを渡すと、以下のようなテンプレートで使う事ができます。

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>

「FRUITS」についての TMPL_LOOP の中で、「NAME」の内容と「COLOR」の内容を TMPL_VAR で出力しています。このテンプレートを、先ほど与えた果物のデータを使って表示させると、以下のように置き換えられます。

<dl>
    
    <dt>Apple</dt>
    <dd>Red</dd>
    
    <dt>Banana</dt>
    <dd>Yellow</dd>
    
    <dt>Peach</dt>
    <dd>Pink</dd>
    
</dl>

「FRUITS」として与えた配列(のリファレンス)が順番に出力されているのがわかりますね。そして TMPL_VAR は、配列の中のハッシュに則って置換されているのがわかります。素直でわかりやすい動作ですね。


ここで、以下のような疑問を持ちました。
「TMPL_LOOP の外で使う為に与えたデータを、TMPL_LOOP の中で使おうとするとどうなるのでしょう」
どういう事かと言うと、まず param メソッドで以下のような値を与えます。

$tmpl->param( NAME => "Palmo", COLOR => "White" );

この値は <TMPL_VAR NAME="NAME"> や <TMPL_VAR NAME="COLOR"> で表示できるのでしたね。でも、ちょっと待ってください。先ほど書いたテンプレートの中の TMPL_LOOP 内でも、全く同じように TMPL_VAR を使って表示させていました。

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>

この場合、「Palmo」と「White」が優先されて、FRUITS の中の要素数分表示されるのでしょうか。それとも FRUITS の中身がそれぞれちゃんと表示されるのでしょうか。


実験してみると、以下のようなエラーが出てしまいました。

HTML::Template : Attempt to set nonexistent parameter 'name' - this parameter na
me doesn't match any declarations in the template file : (die_on_bad_params => 1
) at tmplloop.cgi line 7

つまり、「NAME」として与えた "Palmo" がテンプレートで使われていない、というエラーのようです。与えたデータはテンプレートの中で漏れなく使わなくてはいけないのでしょうか。ちょっと不便ですね。(^_^;)
エラーがでましたが、結果的に「"Palmo" や "White" が使われていない」=「FRUITS の中身がちゃんと表示される」という事がわかりました。試しに、TMPL_LOOP の外で <TMPL_VAR NAME="NAME"> と <TMPL_VAR NAME="COLOR"> を書いて

<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>
<p><TMPL_VAR NAME="NAME"> is <TMPL_VAR NAME="COLOR">.</p>

表示させてみると、想像通り、TMPL_LOOP の中と外で TMPL_VAR の NAME が区別されて表示されました。

<dl>
    
    <dt>Apple</dt>
    <dd>Red</dd>
    
    <dt>Banana</dt>
    <dd>Yellow</dd>
    
    <dt>Peach</dt>
    <dd>Pink</dd>
    
</dl>
<p>Palmo is White.</p>


perldoc HTML::Template の TMPL_LOOP の項をよく読むと、この事について書かれていました。

Inside a <TMPL_LOOP>, the only variables that are usable are the ones
from the <TMPL_LOOP>. The variables in the outer blocks are not visible
within a template loop. For the computer-science geeks among you, a
<TMPL_LOOP> introduces a new scope much like a perl subroutine call. If
you want your variables to be global you can use 'global_vars' option to
new() described below.

以下、palmo の意訳です。

<TMPL_LOOP> の中では <TMPL_LOOP> で指定された変数しか使用できません。TMPL_LOOP ブロックの外の変数は、ブロックの中に入ると見えなくなります。geek にわかりやすく言えば、<TMPL_LOOP> は Perl のサブルーチン呼び出しのように新しいスコープを生成します。もし、変数をグローバルにしたい場合は、new() の際に 'global_vars' オプションを利用してください。

なるほど。これなら、TMPL_LOOP の中と外で TMPL_VAR の置換対象が異なるのもよくわかります。TMPL_LOOP は新しいスコープを生成するので、スコープの外の変数とは明確に区別されるわけですね。(^_^)
もし、ループの外の変数を、ループの中で参照したい場合は

my $tmpl = HTML::Template->new(filename => 'tmplloop.tmpl',
                               global_vars => 1);

のように、「global_vars」オプションを有効にすればいいとの事です。


今回の勉強に利用した CGI スクリプトは以下の通りです。

#!/usr/local/bin/perl
use strict;
use HTML::Template;

my $tmpl = HTML::Template->new(filename => 'tmplloop.tmpl');

$tmpl->param( NAME => "Palmo", COLOR => "White");

$tmpl->param( FRUITS => [
                { NAME => "Apple",  COLOR => "Red" },
                { NAME => "Banana", COLOR => "Yellow" },
                { NAME => "Peach",  COLOR => "Pink" },
              ] );

print "Content-Type: text/html\n\n", $tmpl->output;

また、利用したテンプレート「tmplloop.tmpl」は、以下の通りです。

<html>
<head><title>TMPL_LOOP Fruits</title></head>
<body>
<h1>TMPL_LOOP Fruits</h1>

<h2>果物の色</h2>
<dl>
    <TMPL_LOOP NAME="FRUITS">
    <dt><TMPL_VAR NAME="MSG"><TMPL_VAR NAME="NAME"></dt>
    <dd><TMPL_VAR NAME="COLOR"></dd>
    </TMPL_LOOP>
</dl>
<p><TMPL_VAR NAME="NAME"> is <TMPL_VAR NAME="COLOR">.</p>

</body>
</html>

実行結果は以下から見る事ができます。
tmplloop.cgi 実行結果


制御構造を覚えると、表現の幅がとても広がりますね。(^_^)
HTML::Template では「繰り返し」の他に「分岐」も利用できるので、プログラムのようにテンプレートを作る事ができます。分岐については、また別のエントリで勉強したいと思います。

CPANモジュールへのパーマリンクを作る Greasemonkey スクリプト

この日記で CPAN モジュールを勉強する時に、search.cpan.org 内のモジュールのページにリンクする事がよくあるのですが、search.cpan.org 内のモジュールページの URL には以下のようにモジュールのバージョンが入っています。

特定のバージョンへリンクするならば、この URL でもいいのですが、どうやら古いバージョンのものは消されてしまう事もあるらしく、時々モジュールページへのリンクが切れているのを見かけます。
そこで、ぱるも日記では search.cpan.org/perldoc を使って、以下のような URL でリンクしています。

これなら、常に最新のバージョンのページへと転送されるので、リンク切れの心配が少なくなります。いわゆる「パーマリンク」と呼ばれる恒久的なリンクになりますね。(^_^)


毎回この URL を手打ちで作るのは面倒な上にミスも多くなるので、自動的にこの“パーマリンク”を作って表示する Greasemonkey スクリプトを作りました。Mozilla Firefox 1.5 + Greasmonkey 0.6.4 で動作確認しています。
cpanmodulepermalink.user.js
インストールすると、モジュールページのパンくずリストの最後に「(Permalink)」というリンクが追加されるようになります。

Firefox なら、表示されたパーマリンクを右クリック→ [リンク URL をコピー]を選択すれば、簡単にパーマリンクをコピーできます。
Enjoy!(って一度書いてみたかった(^_^))

Collatz 予想

キミならどう書く 2.0 - ROUND 2 - が発表されました。前回の問題「100までの整数から素数を列挙せよ」にもPerl で答えたので、今回も Perl で自分なりに挑戦してみます。(^_^)
ちょっと長いですが、以下問題の引用です。

Collatz予想の収束までのステップ数の最大値を求めよ.
Collatz予想とは,1以上の自然数 n に対して,次の関数 f(n) が必ず1を返すものとする.

f(n) = 1 , n = 1 のとき
= f (n/2) , n が偶数のとき
= f (3*n + 1) , n が奇数のとき

たとえば,f(3)が 1 になるまでの経緯は,

f(3) -> f(10) -> f(5) -> f(16) -> f(8) -> f(4) -> f(2) -> f(1) -> 1

となり, f(n) の呼出は8回である.これをステップ数と呼ぶことにする.
ここで f(n) が 1 になるまでのステップ数を g(n) とする.つまり,g(3) = 8 である.
このとき

h(n) = k, 1 ≦ k ≦ n ∧ g(k) = max (g(1),g(2),…,g(n))

について h(100) を求めよ.

また,余力のある者は大きな n についても h(n) を求めよ.

なかなか難しそうですね。早く解くには工夫が必要そうです。


というわけで、今回も他の方の回答は見ずに、自分なりに書いてみました。Perl というよりも、C言語っぽい書き方になってしまいましたが……。(^_^;)

use strict;

sub collatz($) {
  my $n = shift;
  my $step = 1;

  while ($n > 1) {
    $n = ($n & 1) ? (3 * $n + 1) : ($n >> 1);
    $step++;
  }

  return $step;
}

print collatz(3), "\n";
print collatz(100), "\n";

実行結果は以下の通りです。

8
26

つまり、問題への答え(h(100) = collatz(100))は「26」となります。追記:問題を読み違えていました。(^_^;) おっちょこちょいですいません……。正しい回答はこのエントリの最後に追記いたしました。


ちなみに、途中の様子を出力させると、このようになりました。

f(3)->f(10)->f(5)->f(16)->f(8)->f(4)->f(2)->f(1)->1
8
f(100)->f(50)->f(25)->f(76)->f(38)->f(19)->f(58)->f(29)->f(88)->f(44)->f(22)->f(
11)->f(34)->f(17)->f(52)->f(26)->f(13)->f(40)->f(20)->f(10)->f(5)->f(16)->f(8)->
f(4)->f(2)->f(1)->1
26

さらに、大きな数でもほぼ一瞬で解く事ができました。

print collatz(1000000), "\n";
print collatz(100000000), "\n";
153
108


以下、スクリプトの解説です。といっても、難しいのはこの式ぐらいでしょうか。

$n = ($n & 1) ? (3 * $n + 1) : ($n >> 1);

「A ? B : C」は三項演算子と呼ばれる「?:」を利用しています。「A」が真の時は「B」を、偽の時は「C」を返す演算子です。本当は、Perl演算子の優先順位から言えば、カッコは必要ないのですが、少しでもわかりやすくする為につけています。
「$n & 1」の「&」はビット演算子 AND ですね。この式は $n の1ビット目が 1 ならば真を、0 ならば偽を返すわけですが、実はこの「1ビット目」を見るだけで「偶数か奇数か」が判断できてしまうのです。詳しくは長くなるので省略しますが、2進数について調べてみると良いでしょう。
コンピュータはビット単位での計算がとても得意なので、この方法は 2 で割って余りを出すよりも断然速く偶数か奇数かを求める事ができます。
「3 * $n + 1」は、問題にある通り n が奇数の時の式ですね。
そして「$n >> 1」ですが、これは $n が正の数の時に限り「$n / 2」と同じ意味になります。つまり、2 で割っているのです。「>>」は「ビットシフト演算子」と呼ばれる演算子で、この場合「$n を2進数で表して各桁を1つ右にずらす」という操作になります。これもやはり2進数の性質に関係しています。
やはりビット単位での計算なので、普通に 2 で除算するよりも速くなります。


ビット単位での計算は、普段は目に見えませんが、コンピュータの中では頻繁に行なわれています。なぜなら、コンピュータ内部では全てのデータが2進数(0と1)で表現されているからです。
プログラマが、2進数の性質について詳しく知っていれば、それだけ効率よくコンピュータに指示を与える事ができ、プログラムの高速化につながります。ただし、こういった「コンピュータが理解しやすい書き方」は、プログラマにとっては「読みづらい」という事も、覚えておかなければいけませんね。アセンブラ言語と、Perlのような高級言語、どちらが人間にとって読みやすいかは明白です。
「高速化」をとるか「可読性」(読みやすさ)をとるか、このバランスの取り方もプログラマの腕の見せ所ですね。(^_^)

追記(2006-07-13)

問題を読み間違えていました。とんでもない馬鹿な間違いを……。ですが、失敗は成功の母という事で、少し変えて再度挑戦します。
つまり、問題の

h(n) = k, 1 ≦ k ≦ n ∧ g(k) = max (g(1),g(2),…,g(n))

の部分を読み落として、g(100) を求めていたわけです。そりゃいくらなんでも簡単すぎますね……。おっちょこちょいです。(^_^;)
というわけで、再度回答を。

use strict;

sub g($) {
  my $n = shift;
  my $step = 1;

  while ($n > 1) {
    $n = ($n & 1) ? (3 * $n + 1) : ($n >> 1);
    $step++;
  }

  return $step;
}

sub h($) {
  my $n = shift;
  my $max = 0;

  for my $i (1..$n) {
    my $step = g($i);
    $max = $step if $step > $max;
  }

  return $max;
}

print h(100), "\n";

実行結果は以下の通りです。

119

ああ、恥ずかしい。(^_^;)
でも、良い経験になりました。教訓:「問題はよく読みましょう」。

追記の追記

あのDan Kogaiさんから言及を頂きました。ありがとうございます。(^_^)
g の中で $n を変化させていく過程で、$n が大きい数になってしまうとオーバーフロー(桁あふれ)するようです。奇数の時は3倍しているので、そのせいですね。use bignum をすれば対策できますが、Danさんによると「低速ゆえお薦めしない」との事。勉強になります。
そもそも上のコードでは h(1000000) を求めるのに、とてつもなく時間がかかってしまうので、さらなる高速化が必要です。うーん……。

HTML::Template(2) TMPL_VAR

HTML::Template を使ってみる(1) の続きです。
前回勉強した TMPL_VAR タグをもうちょっと掘り下げます。param メソッドで設定した値と置き換わるのでしたね。NAME 属性で、どの値と置き換わるのかを指定しましたが、他にも属性を利用できます。


ESCAPE 属性を指定すると、置き換わる値がエスケープ*1された状態で出力されます。ESCAPE 属性に指定できる値は3種類あり、それぞれ異なるエスケープ処理を行なってくれます。以下にまとめました。

ESCAPE 行なわれるエスケープ処理
指定なし そのまま出力する。
HTML HTML タグの一部にする為のエスケープ処理。
例えば「"」→「&quot;」や、「<>」→「&lt;&gt;」など。
URL URL の一部にする為のエスケープ(エンコード)処理。
「 」→「%20」、「/」→「%2F」など。
JS JavaScript の文字列定数として使う為のエスケープ処理。
「改行」→「\n」、「"」→「\"」など。


例えば、テンプレートファイルに以下のように書いておき、

[]<a href="http://www.google.co.jp/search?q= NAME="KEYWORD" ESCAPE="URL">">[]
「<TMPL_VAR NAME="KEYWORD">」を Google 検索
</a>

param メソッドで「KEYWORD」に "Perl ぱるも" をセットして

$tmpl->param( KEYWORD => "Perl ぱるも" );

output メソッドを実行すると

[]<a href="">http://www.google.co.jp/search?q=Perl%20%82%CF%82%E9%82%E0">[]
「Perl ぱるも」を Google 検索
</a>

のように置き換えられます(Shift-JIS で保存した場合)。


他にも ESCAPE="JS" とすれば、JavaScript の文字列定数の中に埋め込めるので、こんなテンプレートを書いておいて

<a href="javascript:alert('<TMPL_VAR NAME="MSG" ESCAPE="JS">')">メッセージを表示</a>

「MSG」に複数行を文字列をセットして

$tmpl->param( MSG => <<MSG );
Hello, JavaScript world.
I'm Palmo!
MSG

output メソッドを実行すれば

<a href="javascript:alert('Hello, JavaScript world.\nI\'m Palmo!\n')">メッセージを表示</a>

のように、適切に改行やシングルクォートがエスケープされます。便利ですね。(^_^)


また TMPL_VAR 要素には DEFAULT 属性を指定する事ができます。DEFAULT 属性は、読んで字の如く「値が設定されてない場合のデフォルト値」を決める事ができます。

<p>こんにちは、<TMPL_VAR NAME="NAME" DEFAULT="通りすがり">さん</p>

これをテンプレートに書いておき、「NAME」を設定せずに output メソッドを呼び出すと

<p>こんにちは、通りすがりさん</p>

と出力されるわけです。もちろん、「NAME」を設定すれば、設定した値が表示されます。これも便利。


というわけで、今回試したテンプレートはこちらです。「tmplvar.tmpl」として保存しました。

<html>
<head><title>TMPL_VAR Attributes</title></head>
<body>
<h1>TMPL_VAR Attributes</h1>

<h2>ESCAPE</h2>

<p><a href="http://www.google.co.jp/search?q= NAME="KEYWORD" ESCAPE="URL">">「<TMPL_VAR NAME="KEYWORD">」を Google 検索</a></p>

<p><a href="javascript:alert('<TMPL_VAR NAME="MSG" ESCAPE="JS">')">メッセージを表示</a></p>

<h2>DEFAULT</h2>

<p>こんにちは、<TMPL_VAR NAME="NAME" DEFAULT="通りすがり">さん</p>

</body>
</html>

そして、CGI スクリプトがこちらです。外部のデザインと内部のデータの切り離しが出来ていて、スクリプトがとても読みやすいですね。以前、変数を HTML として出力するスクリプトを書きましたが、どちらが読みやすいか一目瞭然です。

#!/usr/local/bin/perl
use strict;
use HTML::Template;

my $tmpl = HTML::Template->new(filename => 'tmplvar.tmpl');

$tmpl->param( KEYWORD => "Perl ぱるも" );

$tmpl->param( MSG => <<MSG );
Hello, JavaScript world.
I'm Palmo!
MSG

print "Content-Type: text/html\n\n", $tmpl->output;

実行結果は以下から見る事ができます。
tmplvar.cgi 実行結果
コードを書かずにエスケープ処理が出来るのは面白いですね。

*1:特殊な記号などの意味を打ち消す為の処理

HTML::Template を使ってみる(1)

昨日の「配列やハッシュを HTML で出力する」に対して id:fbis さんよりコメントを頂きました。以下、引用です。

HTMLを出力するのであればHTML::TemplateなりTemplate-Toolkitなりのテンプレートエンジンを利用した方が良いかと。まずはシンプルなHTML::Templateとかどうですか。

なるほど。テンプレートエンジンとは、HTML 部分を別のファイルとして切り分ける為のモジュールの事ですね。(^_^)
一般に、HTML のようなデザイン部分と、内部のプログラム(ロジック部分)が混在しているのは、好ましくないものとされています。1つのファイルに「Perl」と「HTML」という2つの言語が混在しているわけですから、後から読みづらくなる(可読性が落ちる)のは当然です。
また、大規模なアプリケーションを作る場合、内側のプログラムを作る人と、外側のデザインを作る人が異なっている事も多いので、デザイナさんがプログラムの中身について知らなくても、デザインできるように、という需要もあるようです。
fbis さんにご紹介いただいた HTML::Template や、Template Toolkit などのテンプレートエンジンを使えば、デザイン部分を他のファイルへと自然に切り離す事が自然にできるようになります。


というわけで、HTML::Template を使ってみます。HTML::Template モジュールは標準では付属していないので、インストールする必要があります。CPAN モジュールのインストールについては、以前勉強しましたWindows では ppm コマンドを使えば簡単ですね。コマンドプロンプトを開いて、

ppm install HTML-Template

を実行し、少し待てばインストール完了です。「perl -MCPAN -e 'install HTML::Template'」でも CPAN からダウンロードして自動的にインストールできますね。
インストールできたかどうか「perldoc HTML::Template」を実行してみました。

D:\home\palmo>perldoc HTML::Template
NAME
    HTML::Template - Perl module to use HTML Templates from CGI scripts

インストール成功です。(^_^)


早速 HTML::Template を使ってスクリプトを書き始めたい所ですが、その前に「テンプレート」となるファイルを作ります。このテンプレートが「デザイン部分」になるわけです。
テンプレートは HTML で記述するわけですが、この中では“TMPL_”から始まる名前の特別な要素(タグ)を使う事ができます。便宜上「テンプレートタグ」とでも呼びましょうか。(^_^)
以下を「hello.tmpl」として作成しました。

<html>
<head><title>HTML::Template Test</title></head>
<body>
<h1>HTML::Template Test</h1>
<p>Hello, I am <TMPL_VAR NAME="NAME"> the <TMPL_VAR NAME="LANG"> student!</p>
</body>
</html>

HTML には存在しない要素「TMPL_VAR」が使われていますね。これが「テンプレートタグ」の1種です。
「TMPL_VAR」要素は、HTML::Template によってプログラム側で指定した値と自動的に置き換えられます。NAME 属性が、置き換えられる値の名前を表しています。
これだけではちょっとわかりづらいので、このテンプレートを使って実際にプログラムを組んでみました。

#!/usr/local/bin/perl
use strict;
use HTML::Template;

my $tmpl = HTML::Template->new(filename => 'hello.tmpl');

$tmpl->param(
  NAME => "Palmo",
  LANG => "Perl",
);

print "Content-Type: text/html\n\n", $tmpl->output;

HTML::Template を use して、new メソッド(コンストラクタ)を呼び出しています。その際、パラメータとして filename => 'hello.tmpl' と、読み込むテンプレートファイルの名前を渡しています。
そして重要なのが param メソッド。テンプレートの中で使われる「値」を設定しています。ハッシュのように書いていますが、これは「NAME」に "Palmo" という文字列を、「LANG」に "Perl" という文字列をそれぞれ設定しています。
先ほど書いたテンプレートの中で

<p>Hello, I am <TMPL_VAR NAME="NAME"> the <TMPL_VAR NAME="LANG"> student!</p>

と TMPL_VAR 要素を使っていましたが、このプログラムの場合 <TMPL_VAR NAME="NAME"> というタグは "Palmo" に、<TMPL_VAR NAME="LANG"> というタグは "Perl" にそれぞれ置き換えられるので、実際にはこのように表示されます。

<p>Hello, I am Palmo the Perl student!</p>

output メソッドを呼び出すと、上のような置換が行なわれ、テンプレートが処理された内容が返ってきます。最後に、お馴染みの「Content-Type」ヘッダーと、この output メソッドによって出力されたページを表示しています。
実行結果は以下の通りです。
htmpl.cgi 実行結果


テンプレートの中で使えるテンプレートタグにはまだまだ種類があるので、次回勉強してみたいと思います♪
便利なモジュールの紹介、ありがとうございました! > fbis さん