□Perl 講座 特別編 > 検索

◆ページ処理も目印があれば簡単

書き込み数が多くなってくれば、いつまでも1つのページに記事出力させるわけには行きません。もちろん出来ますが、重たいページになってしまいますよね?そこで、1ページに出力する数を決めてあげて分割して出力するようにしてあげましょう!
ページ処理の最もポピュラーな方法として、次ぎの10件・前の10件などのフォームボタンでページ移動できる方法を今回は説明していきます。この他にも、何ページ目かを選択して直接そのページに飛べるようにする方法などもありますが、それはいつか何処かで・・・。

>検索>第一段階

まず準備しなくてはならないのが、検索窓を何処に設置するかを決めるところからです。ヘッダ部分の検索窓をつけてしまっても良いでしょうし、別ページ(サブルーチン)を用意するのも一つの手です。

ヘッダ内に検索窓をつける場合
#ヘッダ部分プログラム
sub header {
    print <<"EOT";
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>簡易掲示板</TITLE>
</HEAD>
<BODY bgcolor="$bgcolor">
<A href="$home">HOME</A><BR>
<P>簡易掲示板
<FORM action="bbs.cgi" method="POST">
<INPUT type="hidden" name="mode" value="write">
なまえ:<INPUT size="20" type="text" name="name"><BR>
E-mail:<INPUT size="20" type="text" name="email"><BR>
URL:<INPUT size="50" type="text" name="url" value="http://"><BR>
メッセージ:<BR>
<TEXTAREA rows="5" cols="50" name="msr"></TEXTAREA><BR>
<INPUT type="submit" value="投稿する"><INPUT type="reset" value="クリア">
</FORM><HR>

<!-- 検索窓部分 ここから -->
<FORM action="bbs.cgi" method="POST">
<INPUT type="hidden" name="mode" value="search">
<INPUT type="text" name="word" size="20">
<INPUT type="submit" value="検索">
</FORM>
<HR><!-- 検索窓部分 ここまで -->

EOT
}

別ページとしてサブルーチンを用意する場合
#メインルーチン
&decode;
if ($FORM{'mode'} eq 'write') { &write; }
if ($FORM{'mode'} eq 'search') { &search; } #検索処理用サブルーチン
if ($FORM{'mode'} eq 'search_html') { &search_html; } #別ページ用サブルーチン
&html;

sub search_html {
    print <<"EOT";
<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>簡易掲示板</TITLE>
</HEAD>
<BODY bgcolor="$bgcolor">
<A href="$home">HOME</A><BR>
<P>簡易掲示板 検索
<FORM action="bbs.cgi" method="POST">
<INPUT type="hidden" name="mode" value="search">
<INPUT type="text" name="word" size="20">
<INPUT type="submit" value="検索">
</FORM>
<HR>
EOT

    #検索された記事があれば出力
    if (@NEW) {
        foreach (@NEW) {
            local($name,$email,$url,$msr) = split(/<>/,$_);
            &output;
        }
    }

    &footer;

}

追加したサブルーチンを呼び出すためには、何処かのHTML部分に
<A href="$script?mode=search_html">検索</A>
と記述すればOKです。

>検索>第二段階

次に実際検索させます。第一段階で、2パターン例を挙げましたが、どちらも検索用窓の名前をwordとしてあります。また、検索結果を@NEWと言う配列に格納することをあらかじめ決めておきます。別ページ(サブルーチン)から検索できるタイプで今後説明していきます。
先にも述べましたが、全文を検索させるか?それとも、投稿記事だけ・投稿者名だけなど、検索対象を限定する方法の2つが大きく分けて考えられます。

まずは全文検索から説明します。ここで言う全文検索とは、保存されている投稿記事データ一行一行の全てから、入力されたキーワードと一致するか否かを調べることです。その条件を満たすのに簡単な命令文があります。

index("検索対象文字列","キーワード");
検索対象文字列とは、投稿記事データ一行分に相当し、キーワードとは$FORM{'word}に相当します。
検索対象文字列内にキーワードが存在しなかった場合、その値は-1が与えられ、存在した場合は最初にマッチした文字の位置の値が与えられます。(文字列の一番最初にマッチした場合は0)
この機能を使うと、マッチしたら正の数が得られ、マッチしなかったら負の数が与えられることを意味します。

その条件を使って、マッチした投稿記事を@NEWに代入していけば良いわけです。

全文検索
sub search {
    #ログの読み込み
    open(IN,"$file") || &error("Can't open $file");
    @lines = <IN>;
    close(IN);
    #全文検索処理
    foreach (@lines) {
        if (index($_,$FORM{'word'}) >= 0) {
            push(@NEW,$_);
        }
    }

    #検索窓出力へ戻る。
    &search_html;

}

次に名前だけ、記事内容だけなど、個別のデータを対象としたマッチのさせ方の一例をご紹介します。
個別に判別させる場合は一行のデータを今までの投稿記事出力時に行っていたデータの分解作業を個別に行ってそれぞれ判別させれば良いわけです。下記の例では記事内容のみを検索対象としてみましょう。

検索対象を限定する
sub search {
    #ログの読み込み     open(IN,"$file") || &error("Can't open $file");
    @lines = <IN>;
    close(IN);
    #全文検索処理
    foreach $line(@lines) {
        #データの分解作業
        local($name,$email,$url,$msr) = split(/<>/,$line);
        #個別にパターンマッチ
        if ($msr =~ /$FORM{'word'}/) {
            push(@NEW,$line);
        }
    }

    #検索窓出力へ戻る。
    &search_html;

}
以上のようにやっても良いでしょうし、完全一致を求めるのであれば、単純に
if ($msr eq $FORM{'word'}) { push(@NEW,$line); }
と言う風にif文を差し替えても構いません。
但しこれを使う場合は記事内容などではなく、投稿者名検索などに使うのが得策だと思いますので、ケースバイケースで使い分けてください。

>検索>最後に

検索=パターンマッチングと言っても過言じゃない?と私は思っております。どういった検索をさせたいかによって、当然上記で紹介した方法以外で検索させる場合もあります。複数の条件に適合する場合にはもっと複雑なルーチンが必要になってくるでしょう。いつかきっと?その辺の複数条件対応の検索ルーチンも説明できればなぁと思っております。

もし、ここまで何とか頑張って理解できるようになってきたら、後一息でかかる時間はともかく、ある程度の物は作れるようになっているはずです。同じ失敗を何度も繰り返すでしょうが、経験こそが一番大切です。頑張ってください。

□特別編 一緒に簡易掲示板を作ってみよう