それでは、まず read.cgi のメインルーチン、ReadCGI のソースから追っていきましょう。
〜 read.cgiの大まかな処理の流れ 〜
ReadCGI 冒頭部分で、各種オブジェクト生成*1・作業用変数宣言
↓
read.cgiに渡される引数(パス名 & 表示開始・終了レス番号)の(字句)解析処理
↓
read.cgiに渡される引数の正統性チェック
↓
datファイル読み込み、PrintMain 呼び出し
↓
PrintMain 内で PrintXXX を順次呼び出してHTMLタグ出力
*1 基本的に module/*.pl 内のクラスは、*.cgi(read.cgi, bbs.cgi, admin.cgi) 内で必要に応じてオブジェクトを生成し、使用される。
以下に疑似コードを示します(疑似コード中に埋め込まれているコメントは、一部おいらが加筆しています)。
# ***** read.cgiサブルーチン群 *****
# ReadCGI read.cgiメインルーチン
# ***** ReadCGIの下請けサブルーチン *****
#
# CheckThreadKey スレッドキーの検査
# PrintMain HTMLタグ表示メインルーチン
# PrintDiscovery datファイル検査結果出力
# ***** PrintMainの下請けサブルーチン *****
#
# PrintThreadLink スレッドのリンク表示
# PrintResponses レス表示
# PrintThreadFoot フッタ表示
# PrintResForm レスフォーム表示
ReadCGI(); # メインルーチン呼び出し
exit; # 終了
# read.cgiメインルーチン
sub ReadCGI{
... メインルーチン冒頭部分で、各種オブジェクト生成・作業用変数宣言 ...
# read.cgiに渡される引数(パス名)の解析処理
# (引数の正当性については関知しない、誤解を恐れずに書くと、いわゆる構文解析の前処理の字句解析の様な処理を行っている)
@arg = $G->GetArgument(\%ENV);
# スレッドキーとしてありえないような変な数字、あるいは数字以外を指定していないか?をチェックする
if($err = CheckThreadKey($arg[1])){
... エラー表示処理 ...
return;
}
$M->Set('MODE', 0); # モードはread
$M->Set('BBS', $arg[0]); # 掲示板名
$M->Set('KEY', $arg[1]); # スレッドキー
$M->Set('AGENT', $arg[7]); # ユーザエージェント
$M->MakeAnyPath(); # パス生成
if($A->Load($M, $M->Get('PATH-DAT'))){ # datファイル読み込み
$M->SetOption($arg[2], $arg[3], $arg[4], $arg[5], $arg[6]); # 仮設定?
($arg[3], $arg[4]) = $G->RegularDispNum($M, $A, $arg[2], $arg[3], $arg[4]); # 正規化
$M->SetOption($arg[2], $arg[3], $arg[4], $arg[5], $arg[6]); # オプション設定
PrintMain(); # HTMLタグ出力メインルーチン
return;
}else{ # datファイル読み込み失敗
PrintDiscovery(); # datファイル検査結果出力
}
}
$Mオブジェクトの MakeAnyPath メソッドのソースを読んでいたら少し気になる点が。。。
MakeAnyPath メソッドのリファレンスの備考にあるように "'BBS' と 'KEY' の値を適切な値にセット" しておかないと完全な動作はしないようです。
だから MakeAnyPath メソッド呼び出しの前に、Set メソッドで 'BBS' と 'KEY' の値を予めセットしておいたんですな。
"何故こういう仕様なのか?、MakeAnyPath メソッドの引数として 'BBS' と 'KEY' の値を渡すような仕様だと都合が悪い?"
は、0chスクリプトを読み続けるとそのうち分かるかも知れません(現時点ではおいらも分かってないw)。。。
datファイルの読み込みに成功すると、$Mオブジェクトの SetOption メソッドでスレッドを表示するためのオプション値を設定するのですが、これまた少し気になる点が。。。
$M->SetOption($arg[2], $arg[3], $arg[4], $arg[5], $arg[6]); # 仮設定?
($arg[3], $arg[4]) = $G->RegularDispNum($M, $A, $arg[2], $arg[3], $arg[4]); # 正規化
$M->SetOption($arg[2], $arg[3], $arg[4], $arg[5], $arg[6]); # オプション設定
SetOption メソッドが2回呼び出されているのですが、その間に挟まれて$Gオブジェクトの RegularDispNum というメソッドが呼び出されています。
RegularDispNum メソッドの引数として$Mオブジェクトが渡されていますが、$MオブジェクトからはGetOption メソッドで>>1のスレを表示するか否かのフラグを取得しているだけで*2、それ以外の情報(lastフラグ、表示開始レス番号、表示終了レス番号)は全て別引数として受け取っています。
ここら辺のソースだけを読んでいると、なんとなくインターフェース周りが不統一な感じがするのですが、こういうインターフェースにしなければならない理由があるのかもしれません(読み進めると理由が分かるだろう)。
*2 厳密には$M->Get('LIMTIME')で "時間帯による表示レス数制限のあり・なし" のフラグも取得している。
HTMLタグ出力メインルーチンである PrintMain の疑似コードを以下に示します。
sub PrintMain{
... サブルーチン冒頭部分で、各種オブジェクト生成・作業用変数宣言 ...
if(!$I->Load($M)){ # 各掲示板毎に固有の設定情報の読み込み
... エラー表示処理 ...
return;
}
$D->Load($M); # バナー設定ファイル読み込み
$E->PrintHTMLHeadA($T, $M, $M->Get('AGENT'), '', $A->GetSubject(), 'Shift JIS'); # HTMLヘッダ出力
$E->PrintHTMLBodyA($T, $I, $M->Get('AGENT')); # HTMLボディ出力
$D->Print($T, $M, 100, 2); # バナー出力
# PrintMainの下請けサブルーチン群
PrintThreadLink($T, $M, $A, $I, $G); # スレッドのリンク表示
PrintResponses($T, $M, $A, $G, $I, $E); # レス表示
PrintThreadFoot($T, $M, $G); # フッタ表示
PrintResForm($T, $M, $I, $G, $A); # レスフォーム表示
$T->Flush(0, 0, ''); # std出力
return;
}
PrintMain 下請けサブルーチン群の第1段、PrintThreadLink の疑似コードを以下に示します。
sub PrintThreadLink{
...
$bbsPath = $M->Get('PATH-BBS1');
...
$rn = $A->GetResNum(); # レス数取得
$rmax = $M->Get('RESMAX'); # 最大書き込みレス数取得
... HTMLタグ出力(掲示板に戻る) ...
$cgiPath = $G->CreatePath($M, 0, $M->Get('BBS'), $M->Get('KEY'), ''); # 全て表示
... HTMLタグ出力(全て表示) ...
$cgiPath = $G->CreatePath($M, 0, $M->Get('BBS'), $M->Get('KEY'), '1-100'); # 1-100表示
... HTMLタグ出力(1-100表示) ...
# 以下のforループ文は、もう少しシンプルに出来るかも。。。
for($i = 1; $i < 11; $i++){ # 残りの 101-200 〜 901-1000 表示
if($i * 100 < $rn){ #
... HTMLタグ出力($i * 100 + 1 〜 $i * 100 + 100 表示) ...
}else{
last; # レス数分出力したら、途中で処理を打ち切る
}
}
... HTMLタグ出力(最新50件表示) ...
if($rn >= $rmax){ # レス数が1000越えている
...
T->Print("レス数が$rmaxを越えています。残念ながら全部は表示しません。");
...
}elsif($rn >= $rmax - int($rmax / 20)){ # レス数が950越えている
...
T->Print("レス数が950を越えています。$rmaxを越えると表示できなくなるよ。");
...
}elsif($rn >= $rmax - int($rmax / 10)){ # レス数が900越えている
...
T->Print("レス数が900を越えています。$rmaxを越えると表示できなくなるよ。");
...
}
$b1 = $I->Get('TEXT_COLOR_SUBJECT');
$b2 = $A->GetSubject(); # スレッドタイトル取得
$T->Print("\n<dl><font color=$b1 size=+1>$b2</font><br><br>\n"); # タイトル表示
}
PrintMain 下請けサブルーチン群の第2段、PrintResponses の疑似コードを以下に示します。
sub PrintResponses{
...
$pDat = $A->Object(); # datデータ取得
$work = $M->Get('OPTION'); # スレッド表示用オプション値取得
...
if($one == 0 && $start != 1){ # >>1の表示
$E->PrintResRead($T, $M, $I, $G, $$pDat[0], 1);
}
for($i = $start; $i <= $end; $i++){ # 残りレスの表示
$E->PrintResRead($T, $M, $I, $G, $$pDat[$i - 1], $i);
}
}
PrintMain 下請けサブルーチン群の第3段、PrintThreadFoot の疑似コードを以下に示します。
sub PrintThreadFoot{
...
($dmy, $st, $ed, $dmy) = split(/\,/, $M->Get('OPTION'));
$path = $M->Get('PATH-READ');
$line = $M->Get('PATH-BBS1');
$rmax = $M->Get('RESMAX');
...
@fstat = stat($M->Get('PATH-DAT'));
...
if($M->Get('LIMTIME')){ # 時間帯による表示レス数制限あり
$T->Print('(08:00PM - 02:00AM の間一気に全部は読めません)');
}
...
$path = $G->CreatePath($M, 0, $M->Get('BBS'), $M->Get('KEY'), "$ed-");
$T->Print("<a href = \"$path\">新着レスの表示</a></center><hr>");
... 以下ずらずらと並ぶ ...
}
PrintMain 下請けサブルーチン群の第4段、PrintResForm の疑似コードを以下に示します。
sub PrintResForm{
...
$path = $M->Get('PATH-CGI'); # CGIパス
$bbs = $M->Get('PATH-BBS'); # 掲示板ディレクトリ
$key = $M->Get('KEY'); # スレッドキー
$ver = $M->Get('VERSION'); # スクリプトバージョン
...
if($M->Get('AGENT') == 0 && $I->IsSetting('BBS_COOKIE', 'on')){ # cookie取得
...
$nm = $R->Get('NAME');
$ml = $R->Get('MAIL');
}
if($M->Get('RESMAX') > $A->GetResNum()){ # レス最大書き込み数超えたら、フォームは表示しない
... HTMLタグ出力 ...
}
...
}