検索主体のCGIプログラム

整理されて格納された情報から,必要なものを検索表示する機能は それを必要とするできるだけ多くの人が利用できる状態にしておくことが 有用である。そのために,Webブラウザさえあれば利用できる CGIプログラムの形にするのは有効である。

ここでは,以前作成した履修科目データベース検索をCGI化してみる。

コンソールプログラムのCGI化

コマンドラインで起動し,入出力をすべてコマンドラインで完了させる プログラムのことをコンソールプログラムという。コンソールプログラムで 値を利用者から読み取るときは主に gets を利用しその結果を 変数に代入した。以下がその一例である。

STDERR.print("年齢を入力して下さい: ")
age = gets.to_i

このような形式のものをCGI化するには,入力に関る部分を CGIパラメータから受け取るように変え,出力する部分を HTML 形式で書き出すように変えればよい。 csv-search.rb では,「開講時期」と「担当教員」の2つの検索条件を入力している。 前者を入力名 jiki,後者を入力名 kyoi で受け渡すことを見越してHTMLフォームを記述すると以下のようになる。

<form method="POST" action="db-cgi.rb">
 <p>検索条件を入力して下さい。</p>
 <table border="1">
  <tr><td>開講時期</td><td><select name="jiki">
			    <option value="">------ (指定なし)</option>
			    <option>春学期</option>
			    <option>秋学期</option>
			   </select></td></tr>
  <tr><td>担当教員</td><td><input name="kyoi" size="8"></td></tr>
 </table>
</form>

この記述により,ブラウザには以下のような入力フォームが出る。

検索条件を入力して下さい。

開講時期
担当教員

これにより入力される値を入力名 jikikyoi から受け取るようなRubyプログラムは以下のようになる。

require 'cgi'
c = CGI.new(:accept_charset => "UTF-8")
term = c["jiki"]
whom = c["kyoi"]

以上のような置き換えに注意し,結果出力をHTML形式で行なえば CGI化は完了する。以下の書き換え例では,結果を桁揃えして読みやすくなるよう table にして出力している。

search-cgi.rb

#!/usr/bin/env ruby
# coding: utf-8
Encoding.default_external = 'utf-8'	# UTF-8のCSVファイルを読むため
require 'cgi'
require 'csv'
c = CGI.new(:accept_charset => "UTF-8")

#データベース読み取り処理は全く同じ
csv = CSV.read("syllabus.csv", headers: true)
db  = csv		# あとで全データを使う場合にそなえcsv変数は温存

# 検索パターンはHTMLフォームへの入力値を取得する
term = c["jiki"]
whom = c["kyoi"]

if term > ""			# 開講時期指定に何か文字列を入れたなら
  ptn = Regexp.new(term)	# 文字列を正規表現に変換
  db = db.select {|row| ptn =~ row["開講時期"]}
end
if whom > ""			# 担当教員指定に何か文字列を入れたなら
  ptn = Regexp.new(whom)	# 文字列を正規表現に変換
  db = db.select {|row| ptn =~ row["担当教員"]}
end

print("Content-type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head><title>検索結果</title>
<style type=\"text/css\">
<!--
 tr.head {background: cyan;}
 th {width: 8em;}
 -->
</style>
</head>
<body>")
puts("<h1>該当科目一覧</h1>")
puts("<table border=\"1\">")
db.each {|row|
  printf("<tr class=\"head\"><td colspan=\"2\">%s</td></tr>\n",
         row["科目名"])
  row.each {|key, value|
    printf("<tr><th>%s</th><td>%s</td></tr>\n", key, value)
  }
}
puts("</table>")
puts("</body>
</html>")

上の例(下線部)にあるように、Rubyプログラムで " " で囲まれた文字列で、HTMLの文としての " " を出すときは、直前にバックスラッシュを追加してエスケープし、 Rubyプログラムとしての文字列がそこで終わらないように気をつける。

以下の入力フォームによって実際に検索できる。

検索条件を入力して下さい。

開講時期
担当教員

入力フォームの自動出力

search-cgi.rb の利用にはこのプログラムを呼ぶためのHTML文書を用意する必要があった。 その中で,「開講時期」の検索オプションとして「春学期」と「秋学期」 の値を選択肢に提示した。この場合選択肢が2つなので問題ないが, これが多数になると option 要素一覧を用意するのが困難になる。また,データに選択肢が増えるたびに HTMLのフォームも修正する必要があるため,互いに食い違いが発生する危険もある。

そのため,データの値に関連する選択肢を入力フォームに入れるような場合は 入力フォーム自体もデータの値を管理するCGIプログラムで生成するほうが望ましい。

たとえば,ここで例に挙げている履修科目データベースは, 1科目分の情報を持つハッシュにキー "開講時期" を与えて取り出せるもの すべてを集めれば選択肢一覧が得られる。 search-cgi.rb 中の変数 db から「開講時期」の値一覧を得るには 以下のようにすればよい。ヘッダオプション(headers) を true に設定した CSV オブジェクトでは添字に見出し名をキーとして設定すると 列の値が配列として得られる。つまり、

db["開講時期"]

["春学期", "秋学期", "春学期"] となる。さらに uniq メソッドを組み合わせると重複をすべて削った一覧が得られるので

db["開講時期"].uniq

によって開講時期の値一覧の重複のない配列が得られる。 これを応用し option リストにするには以下のようにすると効率的である。

db["開講時期"].uniq.collect {|j| "<option>#{j}</option>"}.join("\n")

これにより

<option>春学期</option>
<option>秋学期</option>

という文字列が得られる。

以上ことをふまえつつ,以下の方針でHTML文を出力する。

1. データベースを読み込む
2. 自分自身を呼ぶform文を出力する
3. formの値が入力されていたら検索結果を出力

これをプログラム化したものを示す(実行例)。 この例ではprint文のところで <<区切り のような記法を用いている(ヒアドキュメント)。これは、 区切り が行の先頭に登場する部分までをひとつの文字列と見なす記法で、 複数行に渡る長い文字列を扱うのに便利である。

form-search-cgi.rb

#!/usr/bin/env ruby
# coding: utf-8
Encoding.default_external = 'utf-8'
require 'csv'
require 'cgi'
c = CGI.new(:accept_charset => "UTF-8")

#データベース読み取り処理は全く同じ
db = CSV.read("syllabus.csv", headers: true)
opts = db["開講時期"].uniq.collect {|j| "<option>#{j}</option>"}.join("\n")

# HTMLヘッダと入力フォーム出力
print <<EOF
Content-type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head><title>科目検索</title>
<style type="text/css">
<!--
 tr.head {background: cyan;}
 th {width: 8em;}
 -->
</style>
</head>
<body>
<form method="POST" action="form-search-cgi.rb">
 <h1>科目検索</h1>
 <p>検索条件を入力して下さい。</p>
 <table border="1">
  <tr><td>開講時期</td><td><select name="jiki">
			    <option value="">------ (指定なし)</option>
EOF

print(opts)	# これで自動生成した option 一覧が出る

print <<EOF
</select></td></tr>
  <tr><td>担当教員</td><td><input name="kyoi" size="8"></td></tr>
 </table>
 <p><input name="ok" type="submit" value="検索">
 <input name="cl" type="reset" value="リセット"></p>
</form>
EOF
# 検索パターンはHTMLフォームへの入力値を取得する
term = c["jiki"]
whom = c["kyoi"]

if term>"" or whom>""		# 検索条件の最低1つが入力されていたら
  if term > ""			# 開講時期指定に何か文字列を入れたなら
    ptn = Regexp.new(term)	# 文字列を正規表現に変換
    db = db.select {|row| ptn =~ row["開講時期"]}
  end
  if whom > ""			# 担当教員指定に何か文字列を入れたなら
    ptn = Regexp.new(whom)	# 文字列を正規表現に変換
    db = db.select {|row| ptn =~ row["担当教員"]}
  end

  puts("<h2>該当科目一覧</h2>")
  printf("<p>検索語: 開講時期=[%s],担当教員=[%s]</p>", term, whom)
  puts("<table border=\"1\">")
  db.each {|row|
    printf("<tr class=\"head\"><td colspan=\"2\">%s</td></tr>\n",
           row["科目名"])
    row.each {|key, value|
      printf("<tr><th>%s</th><td>%s</td></tr>\n", key, value)
    }
  }
  puts("</table>")
end
puts("</body>\n</html>")

目次