検索主体のCGIプログラム

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

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

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

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

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

このような形式のものをCGI化するには,入力に関る部分を CGIパラメータから受け取るように変え,出力する部分を HTML 形式で書き出すように変えればよい。 db-search.rb では,「開講時期」と「担当教員」の2つの検索条件を入力している。 前者をCGI変数 jiki,後者をCGI変数 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>

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

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

開講時期
担当教員

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

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

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

#!/usr/bin/env ruby
# coding: utf-8

require 'cgi'
c = CGI.new(:accept_charset => "UTF-8")

# データファイルが別の文字コードかもしれないので kconv の toutf8 メソッドを
# 用いて utf-8 に統一して正規表現マッチを行なう
require 'kconv'

db = {}
open("syllabus.txt", "r") do |df|	#データベース読み取り処理は全く同じ
  while line = df.gets
    line = line.toutf8		
    if /^科目名:\s*(.*)/ =~ line
      key = $1
      db[key] = Hash.new
    elsif /^(\S+):\s*(.*)/ =~ line
      db[key][$1] = $2
    end
  end
end

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

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

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

<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 {|k, h|
  printf("<tr class=\"head\"><td colspan=\"2\">%s</td></tr>\n", k)
  h.each {|key, value|
    printf("<tr><th>%s</th><td>%s</td></tr>\n", key, value)
  }
}
puts("</table>")
puts("</body>
</html>")

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

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

開講時期
担当教員

入力フォームの自動出力

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

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

たとえば,ここで例に挙げている履修科目データベースは, 1科目分の情報を持つハッシュにキー "開講時期" を与えて取り出せるもの すべてを集めれば選択肢一覧が得られる。 search-cgi.rb 中の変数 db から「開講時期」の値一覧を得るには 以下のようにすればよい。

db.collect {|kamoku, info|
  info["開講時期"]
}.uniq

collect は,後続するブロック内で評価した値を 集めた配列を新規作成するメソッドである。これにより,全科目の 「開講時期」の値が集められ,さらにそこから uniq メソッドで 重複を取り除いて選択肢一覧ができ上がる。この例では,

["前期", "後期"]

の配列が得られる。これを応用し option リストにするには以下のようにすると効率的である。

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

これにより

<option>前期</option>
<option>後期</option>

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

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

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

これをプログラム化したものを示す(実行例)。

form-search-cgi.rb

#!/usr/bin/env ruby
# coding: utf-8

require 'cgi'
c = CGI.new(:accept_charset => "UTF-8")

# データファイルが別の文字コードかもしれないので kconv の toutf8 メソッドを
# 用いて utf-8 に統一して正規表現マッチを行なう
require 'kconv'

db = {}
open("syllabus.txt", "r") do |df|	#データベース読み取り処理は全く同じ
  while line = df.gets
    line = line.toutf8		
    if /^科目名:\s*(.*)/ =~ line
      key = $1
      db[key] = Hash.new
    elsif /^(\S+):\s*(.*)/ =~ line
      db[key][$1] = $2
    end
  end
end
opts = db.collect {|kamoku, info|
  info["開講時期"]
}.uniq.collect {|j| "<option>#{j}</option>"}.join("\n")


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

<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>")
print(opts)		# データベースから自動生成した「開講時期」の選択肢
print('</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>')

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

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

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

目次