1. としきのやしき正門
  2. 電算棟
  3. 板塀(掲示板)の手入れ(改造)
  4. 発言番号リンク

発言番号リンク

私はこの掲示板を、かつてのTTY時代のニフティのフォーラムの電子会議室のように運用しようと思っています。もっと具体的に言うと、いわゆる各フォーラムの「壁会議室」のイメージですね。ほとんどフリートークなんだけど、たまにそうではないこともある、まさに落書き用。一部フォーラムでは「堀」って名称だったたようですが、堀には落書きはできません。
このような考えに基づいて、この掲示板のスクリプトを拡張してきました。例えばツリー表示とか、ニフティの会議室でのrmnコマンドもどきであるスレッド一覧表示とか。

さて、当時のニフティでは、例えば発言番号nn番の発言を指定するのに「#nn」という表記を使っていました。ある一つの発言は最大でもどれか一つの発言のコメントにしかなれなかったため、複数の発言を参照したり引用したりする場合に、特にこの表記が効果的でした。
現在のWEBでは好きなようにリンクを張れるため、このような表記を使用する必然性はありません。ただ、素直にURLを書くと長くなるので、URLではなく発言番号で個別の発言を特定することにメリットが全くないわけではありません。

かつて、あじゃ氏という方が、WebForumでこのような記述をした場合に、それをその発言にジャンプするリンク文字列にするという改造を実装したことがあります。ただ、これ、いかんせん不完全なものだったのですね。一番大きな欠陥は、WebForumの標準機能である過去ログ機能を全くサポートしておらず、過去ログ送りになった発言に対してリンクすることができないこと。にもかかわらず、逆に過去ログ送りになった発言で他の発言を指定する記述があった場合、確かこれは自動的に、リンクできないリンクを作り上げるような構造になっていたように記憶しています。ただ、この部分に関しては、氏の作成したシステムに対する理解が足りないので間違っているかもしれません。
もう一つ些細な欠点がありました。それは、表示されるのが、純粋にその指定された発言のみである、ということ。具体的には、現在のこの板塀で言うところの「新着記事」に似た体裁なのですが、返信欄もなければ返信用のボタンもない、というもの。「関連記事」のリンクはあるので、それをクリックすれば他の場面で「関連記事」をクリックしたときと同じようにツリー一括表示がなされるのですが、ちょっと不便。
さらに、実用上は全く問題のない点ではあるのですが、存在しない発言を指定した場合、例えば現在1000発言しかない状況で2000番を指定したような場合、画面に表示されるのが意味不明な内容になってしまっています。これはもっとタコなシステムだとハングアップしてしまうような場合もあるわけですから、それに比べればはるかにましですが。それに、ブラウザの「戻る」ボタンで戻れますし。
単にこれは仕様の考慮もれに過ぎないのでしょう。


今回、この板塀に、この形式の発言番号指定リンク機能を導入してみました。
上記の、私の気がついた欠陥に関しては回避するように努力しています。
過去ログ機能って基本的にツリー一括表示しかないわけで、過去ログとそれ以外でルーチンを分けるのも面倒ですから、全ての場合においてツリー一括表示になるようにしてみました。過去ログでない場合は当然、返信ボタンを押せば、通常の返信欄が表示されるようになっています。
存在しない発言を指定した場合も、少なくともその旨表示するようにはしました。

必要な改造は、以下のような感じ。
まず、新しいモードを一つ、定義します。何でもいいのですが、あじゃ氏の例に習い、articleという名称にします。
wforum.cgiの先頭に近い部分に、モードごとの分岐ルーチンがありますので、そこにこのモードでの条件判定を仕込みます。


if ($mode eq "msgview") { &msgview; }
elsif ($mode eq "article") { &article; }
elsif ($mode eq "allread") { &allread; }

こんな感じ。
次に、実際にリンク文字列を作る部分ですが、これも追加する必要があるのは実質的には1行ですね。
URLをリンクにするルーチンが元々ありますので、その中に組み込んでしまいました。


#---------------------------------------
#  自動リンク
#---------------------------------------
sub auto_link {
    local($_) = @_;

    s/([^=^\"]|^)(https?\:\/\/[\w\.\~\-\/\?\&\=\;\#\:\%\+\@]+)/$1<a href="$2" target="_blank">$2<\/a>/g;
#    s/(^|[\x81-\x9F\xE0-\xFC][\x40-\x7E\x80-\xFC]|[^0-9a-zA-Z\#\"]+)#([0-9]+)([^0-9\;\.\"])/$1<a href="$script?mode=article&amp;no=$2">#$2<\/a>$3/g; ## 旧版
    s/#([0-9]+)($|[^0-9a-zA-Z\"\#\$\%\&\'\(\)\=\-\^\?\<\>\;\.]|[\<][bB][rR][\>]|\n|\r)/<a href="$script?mode=article&amp;no=$1">#$1<\/a>$2/g;
    $_;
}

正規表現文字列は、当初は旧版としてあるものを使っていました。これは、発言番号と認識する文字列の前後に1文字ずつのガード文字を設ける、という形です。でも、これだと、二つの発言番号文字列を連続させる場合、最初の発言番号の後ガード文字と次の発言番号の前ガード文字がそれぞれ必要です。つまり、二つの発言番号文字列のあいだに必ず2文字の間隔を開けなければいけないわけです。
私自身としては間隔は1文字だけでもきちんとリンクしてほしいのですが、実は2年半も旧版のままで使っていて、2文字の間隔が必要と気が付いていませんでした。今回、この点の修正と合わせて、掲示板にプログラムコードなどが書きこまれた場合に誤認しにくくなるよう、ちょっとだけ正規表現文字列の見直しを図ってみました。正規表現については、もっと工夫のしようもあるかもしれませんが、こんなものでとりあえずは十分でしょう。
で、最後、本体です。
なお、過去ログでの発言番号指定もシームレスに行うためには、別記「永続的URL対応」のルーチンが実装されている必要があります。


#---------------------------------------
#  個別発言表示
#---------------------------------------
sub article {
    local($no,$re,$lx,$sub,$eml,$url,$nam,$dat,$msg,$t,$ho,$pw,$wrap,$oya,$sml,$child,$date);

    $flag=0;
    while ($flag == 0) {
        # 過去ログの場合
        if ($in{'act'} eq "past") {
            # ログファイルを定義
            if ($in{'pastlog'}) {
                $count = $in{'pastlog'};
            } else {
                open(NO,"$nofile") || &error("Open Error: $nofile");
                $count = <NO>;
                close(NO);
            }
            $logfile = sprintf("%s%04d\.cgi", $pastdir,$count);
        }
    
        # 親記事を出力
        @x=();
        $x[0] = 0;
        open(IN,"$logfile") || &error("Open Error: $logfile");
        $top = <IN> if ($in{'act'} ne "past");
        while (<IN>) {
            ($no,$re,$lx,$sub,$eml,$url,$nam,$dat,$msg,$t,$ho,$pw,$wrap,$oya,$sml,$child) = split(/<>/);
            if ($in{'no'} == $no) {
                $flag=1;
                print "Location: http://$myhost:9801/bbs/$script?mode=allread&no=$oya#$no\n";
                print "Content-type: text/html\n\n";
                print "<html><head>";
                print "<META HTTP-EQUIV=\"refresh\" CONTENT=\"0; URL=$script?mode=allread&amp;no=$oya#$no\">";
                print "<title>$title</title></head><body> </body></html>";
                exit;
            }
            elsif ($flag && $in{'no'} != $no) { last; }
        }
        close(IN);
        if ($flag == 0){
            if ($in{'act'} ne "past") {
                $in{'act'} = "past";
            }
            if ($in{'pastlog'}) {
                $in{'pastlog'} = $in{'pastlog'} - 1;
                if ($in{'pastlog'} == 0) {
                    $flag=1;
                }
            } else {
                open(NO,"$nofile") || &error("Open Error: $nofile");
                $in{'pastlog'} = <NO>;
                close(NO);
            }
        }
    }

    # HTMLを出力
    &header;
    print "該当発言がありません";
    print "</body>\n</html>\n";
    exit;
}

このルーチンの7割は、過去ログ対応のためのものです。それにもかかわらず、その部分が十分にチェックされていないというのが噴飯物なのですが。
また、見ればわかるとおり、該当の発言が見つかったときの処理の量はたいしたことはありません。キモは、HTTPのヘッダである「Location」です。きちんとHTTPを実装してあるブラウザであれば、これを受取った段階で、自動で指定されたURLにジャンプしてくれます。一応、タコなブラウザ用にHTMLのMETAタグも使ってありますが、これはほんとに蛇足でしょう。なぜって、私、「Location」を実装していないブラウザって見たことがないのです。MS-DOS上で動くLOUKですら、きちんと実装しています。また、「Location」を実装していないブラウザが「refresh」を解釈できるとも思えないので、やはりこれは蛇足かな。
なお、この「Location」ヘッダは、規格上は絶対URLで指定しなければならないことになっています。それを知らなくて、当初、私は相対URLで指定してしまっていました。ほとんどのブラウザは、相対URLでもうまいこと解釈してくれるので、たいていは問題は起きません。ところが、ブラウザによっては、HTTPのポートが80番決め打ちになっている場合があるようです。うちの板塀は80番ではないポートを使っているので、時々うまくジャンプしないことがある、という報告をもらってしまいました。調査の結果、相対URLではダメだとわかったので、現在は上記のように絶対URLで記述してあります。ただ、ドメインを取得していないためIPアドレスの変化に応じてホスト名が時々変化するので、この部分は変数になっています。もし、IPアドレスが変化したら、スクリプトの設定も手動または自動で容易に変更できるようにしたのです。
サーバで使っているのが80番ポートなら、ここで相対URLを使っても、それほど問題は発生しないでしょう。

本当は該当発言が見つからなかったときの表示をもっと丁寧にしたり、あるいは戻るボタンをつけたり、ということも可能なのですが、家主がそこまでを必要と感じなかったということもあって、素直に手を抜いてあります。


板塀(掲示板)の手入れ(改造)
電算棟
やしきの間取り


Copyright © 2006-2010,2012 としき
このページの情報及び著作権は「としき」に帰属します。
Toshiki / toshiki @ jcom.zaq.ne.jp