#!/usr/bin/perl use lib "../lib"; use CGI::Carp 'fatalsToBrowser'; use Algorithm::Diff qw(traverse_sequences); # use strict; # # yukiwiki.cgi - Yet another WikiWikiWeb clone. # # Copyright (C) 2000,2001 by Hiroshi Yuki. # <hyuki@hyuki.com> # http://www.hyuki.com/yukiwiki/ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # $Id: wiki.cgi,v 1.7 2002/02/04 15:27:22 wakaba Exp $ ############################## my $version = "1.6.6"; ############################## # 単独テストのときには 1 にする。 my $testing = 0; ############################## # 漢字ライブラリ my $jcodelib = 'jcode.pl'; ############################## # 保存・表示の漢字コード my $kanjicode = 'euc'; # 'sjis' 'euc' my $charset = 'euc-jisx0213'; # 'Shift_JIS' 'EUC-JP' ############################## # dbmopenが使えるなら1、使えないなら0 my $dbmopen = 0; ############################## # データベース名(.pag, .dir, .dbなどは不要) # $dbmopen = 1のときはデータベース名、 # $dbmopen = 0のときはディレクトリ名になる。 my $dbname = './wikidata'; my $diffdbname = './wikidiff'; ############################## # 修正者の氏名(自由に変更してください) my $modifier = 'suika'; ############################## # 修正者のWebページ(自由に変更してください) my $modifierlink = 'http://suika.fam.cx/'; ############################## # このページのURL my $thisurl = 'wiki'; ############################## # 開始ページ名 my $toppage = 'HomePage'; ############################## # 最終更新ページ名 my $whatsnew = 'RecentChanges'; ############################## # 最終更新に掲載するページ数 my $maxnew = 50; ############################## # アイコンファイル名(カラー版) my $iconfile = ''; ############################## # アイコンファイル名(モノクロ版) # my $iconfile = ''; ############################## # ページを変更したときにtouchするファイル(''なら何もしない) my $touchfile = 'touch.txt'; ############################## # プレビュー用の背景色 my $preview_color = '#FFCCCC'; ############################## # 全ページのスタイル my $style = <<'EOD'; pre, dl, ul, ol, p, blockquote { line-height:120%; } a { text-decoration: none; } a:link { color: #0000FF; background-color: #FFFFFF; } a:visited { color: #9900CC; background-color: #FFFFFF; } a:hover { text-decoration: underline; } EOD ############################## # テキスト入力部分の大きさ my $cols = 80; my $rows = 20; ############################## my %form = (); my %database = (); my %diffbase = (); my $diff_text = ''; my @diff_added = (); my @diff_deleted = (); my $msgrefA; my $msgrefB; ############################## # 編集不可ページ名一覧 my @uneditable = ( $whatsnew ); ############################## # リンク用の正規表現 # YukiWikiのリンクは2種類ある。 # # (1) WikiName (RecentChangesとかFrontPageのようなもの) # (2) BracketName ([[結城浩]]とか[[トラブルシュート]]のようなもの) # # ※シフトJISの2バイト目には ']' が来うるので、 # 文字']'を1つ多くとるようにしている。 # my $WikiName = '([A-Z][a-z]+([A-Z][a-z]+)+)'; my $BracketName = '\[\[([^>\s]+?\]?)\]\]'; # アイコン部分のタグ my $IconTag = ''; #<<"EOD"; #<a href="http://www.hyuki.com/yukiwiki/"><img src="$iconfile" # border="0" width="80" height="80" alt="[YukiWiki]" /></a> #EOD require "$jcodelib"; &init_form($kanjicode); if ($testing) { %form = ( # 'mycmd' => 'write', 'mycmd' => 'read', #'mycmd' => 'search', #'mycmd' => 'edit', 'mymsg' => <<"EOD", はじめまして。 これからいろいろ書き込みますね。 LinkPageも見てください。 TestPageはどうでしょうか。 どうぞよろしく。 http://www.hyuki.com/ [[結城浩]] EOD 'mypage' => '<結城浩>', 'myword' => '結', # '3C8C8B8FE98D5F3E' => '', # 'TestPage' => '', ); } &main; exit(0); # メイン sub main { &normalize_form; if ($dbmopen) { if (!dbmopen(%database, $dbname, 0666)) { &print_error("(dbmopen) $dbname が作れません。"); } } else { if (!tie(%database, "YukiWikiDB", $dbname)) { &print_error("(tie error)"); } } # myspecial対応 foreach (keys %form) { if (/^myspecial_(.*)/) { $form{mycmd} = $1; last; } } if ($form{mycmd} eq 'read') { &do_read; } elsif ($form{mycmd} eq 'preview') { &do_preview; } elsif ($form{mycmd} eq 'write') { &do_write; } elsif ($form{mycmd} eq 'edit') { &do_edit; } elsif ($form{mycmd} eq 'reedit') { &do_reedit; } elsif ($form{mycmd} eq 'search') { &do_search; } elsif ($form{mycmd} eq 'list') { &do_list; } elsif ($form{mycmd} eq 'diff') { &do_diff; } else { $form{mypage} = $toppage; &do_read; } if ($dbmopen) { dbmclose(%database); } else { untie(%database); } } # ページの表示 sub do_read { my $page_name = $form{mypage}; my $percent_name = &encode_percent($page_name); &print_header($page_name); print qq|<h1>$IconTag<a href="$thisurl?mycmd=search;myword=$percent_name">$page_name</a></h1>\n|; &print_toolbar($page_name); print &convert_html(&get_page($page_name)); &print_footer; } # ページの編集 sub do_edit { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } &editpage(&get_page($form{mypage})); } # ページの再編集 sub do_reedit { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; } else { &editpage($form{mymsg}); } } sub editpage { my $page_msg = shift; my $page_name = $form{mypage}; my $digest = &calc_message_digest($page_msg); &print_header($page_name); print qq|<h1>$IconTag${page_name}の編集</h1>\n|; &print_toolbar($page_name); $page_msg = &escape($page_msg); print <<"EOD"; <form action="$thisurl" method="post"> <!--<input type="hidden" name="mycmd" value="preview">--> <input type="hidden" name="mypage" value="$page_name"> <input type="hidden" name="mydigest" value="$digest"> <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$page_msg</textarea><br> <input type="submit" name="myspecial_preview" value="確認"> <input type="submit" name="myspecial_write" value="確認せず変更"> </form> <hr> <h3>テキスト整形のルール</h3> <p>通常は入力した文字がそのまま出力されますが、 以下のルールに従ってテキスト整形を行うことができます。</p> <ul> <li> 空行は段落の区切りとなります。 <li> HTMLのタグは書けません。 <li> ''強調''のようにシングルクォート二つではさむと、強調になります。 <li> '''更に強調'''のようにシングルクォート三つではさむと、更に強調になります。 <li> ----のようにマイナス4つがあると、水平線になります。 <li> *を行頭に書くと大見出しになります。 <li> **を行頭に書くと小見出しになります。 <li> -を行頭に書くと箇条書きになります。- -- --- の3レベルがあります。 <li> :を行頭に書くと用語と解説文が作れます。 <pre> :用語1:いろいろ書いた解説文1 :用語2:いろいろ書いた解説文2 :用語3:いろいろ書いた解説文3 </pre> <li> http://www.hyuki.com/ のようなURLは自動的にリンクになります。 <li> YukiWikiのように大文字小文字を混ぜた英文字列を書くと、 YukiWikiのページ名になります。 <li> [[結城浩]]のように二重の大かっこ[[ ]]でくくった文字列を書くと、 YukiWikiのページ名になります。 大かっこの中にはスペースを含めてはいけません。 日本語も使えます。 <li> 行頭がスペースで始まっていると、 その段落は整形済み扱われます。 プログラムを書き込むときに使うと便利です。 <li> > を行頭に書くと、 引用文が書けます。 > の数が多いとインデントが深くなります(3レベルまで)。 </ul> EOD &print_footer; } # ページの検索 sub do_search { if ($form{myword}) { &print_header('検索結果'); print qq|<h1>$IconTag$form{myword}の検索結果</h1>\n|; &print_toolbar(); print qq|<ul>\n|; my $count = 0; foreach my $page_name (sort keys %database) { # sortするのは無謀かな if ($database{$page_name} =~ /\Q$form{'myword'}\E/) { my $encoded = &encode_percent($page_name); print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n|; $count++; } } print qq|</ul>\n|; if ($count > 0) { print qq|<p><strong>$form{myword}</strong>を含むページは、上に示す<strong>$count</strong> ページです。</p>\n|; } else { print qq|<p><strong>$form{myword}</strong>を含むページは見つかりません。</p>\n|; } } else { &print_header('単語検索'); print qq|<h1>$IconTag単語検索</h1>\n|; &print_toolbar(); } print <<"EOD"; <p> <form action="$thisurl" method="post"> <input type="hidden" name="mycmd" value="search"> <input type="text" name="myword" size="20" value="$form{myword}"> <input type="submit" value="単語検索"> </form> </p> EOD &print_footer; } # ページの一覧 sub do_list { &print_header('ページ一覧'); print qq|<h1>$IconTag ページ一覧</h1>\n|; &print_toolbar(); print qq|<ul>\n|; foreach my $page_name (sort keys %database) { # sortするのは無謀かな my $encoded = &encode_percent($page_name); print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n| } print qq|</ul>\n|; &print_footer; } # プレビュー sub do_preview { my $page_name = $form{mypage}; my $escapedmsg = &escape($form{mymsg}); &print_header($page_name); print qq|<h1>$IconTag${page_name}のプレビュー</h1>\n|; &print_toolbar($page_name); # local $percent_name = &encode_percent($page_name); print qq|<p>以下のプレビューを確認して、よければページ下部のボタンで更新してください。</p>\n|; if ($form{mymsg}) { print qq|<table width="100%" bgcolor="$preview_color" ><tr><td>\n|; # print &convert_html($escapedmsg); print &convert_html($form{mymsg}); print qq|</td></tr></table>\n|; } else { print qq|<p>(ページの内容は空です。更新するとこのページは<strong>削除</strong>されます。)</p>\n|; } &print_preview_buttons($page_name, $escapedmsg, $form{mydigest}); &print_footer; } sub print_preview_buttons { my ($page_name, $escapedmsg, $digest) = @_; print <<"EOD"; <form action="$thisurl" method="post"> <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$escapedmsg</textarea> <br /> <input type="hidden" name="mypage" value="$page_name"> <input type="hidden" name="mydigest" value="$digest"> <input type="submit" name="myspecial_preview" value="再度プレビュー"> <input type="submit" name="myspecial_write" value="ページの更新"> </form> EOD } # 書き込む sub do_write { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } my $page_name = $form{mypage}; # digestを使って、更新の衝突チェック my $original_digest = &calc_message_digest(&get_page($page_name)); if ($form{mydigest} ne $original_digest) { &print_header($page_name); print qq|<h1>$IconTag${page_name}で【更新の衝突】が起きました</h1>\n|; print <<"EOD"; <p>あなたがこのページを編集している間に、 他の人が同じページを更新してしまったようです。 </p><p> 以下に、あなたの編集したテキストがありますので、 あなたの編集内容が失われないように、 いますぐ、メモ帳などにコピー&ペーストしてください。 </p><p> コピー&ペーストが済んでから、 最新の内容を見て再度編集し直してください。 最新の内容は <a target="_blank" href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a> で見ることができます。 </p> EOD # &print_toolbar($page_name); &print_preview_buttons($page_name, &escape($form{mymsg}), $form{mydigest}); &print_footer; return; } # diff生成 { &opendiff; my @msg1 = split(/\n/, &get_page($page_name)); my @msg2 = split(/\n/, $form{mymsg}); $msgrefA = \@msg1; $msgrefB = \@msg2; &diff_check; $diffbase{$form{mypage}} = $diff_text; $diff_text = ''; &closediff; } &print_header($page_name); &set_page($page_name, $form{mymsg}); if ($form{mymsg}) { print qq|<h1>$IconTag${page_name}を更新しました</h1>\n|; &print_toolbar($page_name); print &convert_html(&get_page($page_name)); } else { print qq|<h1>$IconTag${page_name}を削除しました</h1>\n|; &print_toolbar($page_name); print qq|<p>${page_name}を削除しました。</p>\n|; } &print_footer; # 更新されたのでタッチしておく。 if ($touchfile) { open(FILE, "> $touchfile"); print FILE "\n"; close(FILE); } } # ページの変更点 sub do_diff { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } &opendiff; &print_header($form{mypage} . 'の変更点'); print qq|<h1>$IconTag <a href="$thisurl?mycmd=read&mypage=$form{mypage}">$form{mypage}</a>の変更点</h1>\n|; &print_toolbar(); $_ = &escape($diffbase{$form{mypage}}); print <<"EOD"; <ul> <li>追加された行は<ins>青色</ins>です。 <li>削除された行は<del>赤色</del>です。 <li><a href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a>へ行く。 </ul> <hr /> EOD print qq|<pre>\n|; foreach (split(/\n/, $_)) { if (/^\+(.*)/) { print qq|<ins>$1</ins>\n|; } elsif (/^\-(.*)/) { print qq|<del>$1</del>\n|; } elsif (/^\=(.*)/) { print qq|$1\n|; } else { print qq|??? $_\n|; } } print qq|</pre>\n|; &print_footer; &closediff; } sub opendiff { if ($dbmopen) { if (!dbmopen(%diffbase, $diffdbname, 0666)) { &print_error("(dbmopen) $diffdbname が作れません。"); } } else { if (!tie(%diffbase, "YukiWikiDB", $diffdbname)) { &print_error("(tie error)"); } } } sub closediff { if ($dbmopen) { dbmclose(%diffbase); } else { untie(%diffbase); } } # フォームからの情報を連想配列 %form に入れる # &init_form('euc'); sub init_form { my ($charcode) = @_; my $query; if ($ENV{REQUEST_METHOD} =~ /^post$/i) { read(STDIN, $query, $ENV{CONTENT_LENGTH}); } else { $query = $ENV{QUERY_STRING}; } my @assocarray = split(/[&;]/, $query); foreach my $assoc (@assocarray) { my ($property, $value) = split(/=/, $assoc); $value =~ tr/+/ /; $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg; &jcode::convert(\$value, $charcode); $form{$property} = $value; } } # エラーページを出力する sub print_error { my ($msg) = @_; &print_header('Error'); print "<h1>$IconTag$msg</h1></body></html>"; exit(0); } sub escape { my ($line) = shift; $line =~ s|<|<|g; $line =~ s|>|>|g; $line =~ s|"|"|g; # $line =~ s|\&|&|g; return $line; } sub inline { my ($line) = shift; $line = &escape($line); $line =~ s|'''([^']+?)'''|<strong>$1</strong>|g; $line =~ s|''([^']+?)''|<em>$1</em>|g; $line =~ s! ( (?:<(?:mailto|http|https|ftp|urn):[\x21-\x7E]*)> | (?:$WikiName) # LocalLinkLikeThis | (?:$BracketName) # [[日本語リンク]] ) ! &make_link($1) !gex; return $line; } # ページのタイトルからページの内容を得る sub get_page { my $page_name = shift; return $database{$page_name}; } # ページの内容を与える # &set_page($title, $txt) sub set_page { # ページを更新する my $title = $_[0]; $database{$title} = $_[1]; # 空ページなら削除する unless ($database{$title}) { delete $database{$title}; } # RecentChangesを更新する my $delim = ' - '; my @pages = split(/\n/, $database{$whatsnew}); my $datestr = &get_current_datestr; unshift(@pages, qq|-$datestr$delim$title|); # 同一ページの更新は最新のもののみにし、 # 存在しないページはスキップする。 my %count; my @newpages; foreach my $line (@pages) { my ($prefix, $title) = split(/$delim/, $line); $count{$title}++; if ($count{$title} == 1 and exists($database{$title})) { push(@newpages, qq|$prefix - $title|); } } # ここで本当に更新 $database{$whatsnew} = join("\n", splice(@newpages, 0, $maxnew)); } # ページのヘッダを出力 sub print_header { my $title = shift; print <<"EOD"; Content-type: text/html <html><head> <title>$title</title> <style type="text/css"> $style </style> </head> <body> EOD } # ツールバーを出力 sub print_toolbar { my $page_name = shift; my $percent_name = &encode_percent($page_name); my $editlink = ''; if ($page_name ne '' and &is_editable($page_name)) { $editlink = <<"EOD"; <a href="$thisurl?mycmd=edit;mypage=$percent_name">編集</a> | <a href="$thisurl?mycmd=diff;mypage=$percent_name">差分</a> | EOD } print <<"EOD"; <p> [ <a href="$thisurl?mycmd=read;mypage=$toppage">トップ</a> | <a href="$thisurl?mycmd=list">一覧</a> | $editlink <a href="$thisurl?mycmd=search">単語検索</a> | <a href="$thisurl?mycmd=read;mypage=$whatsnew">最終更新</a> ] </p> EOD } # ページのフッタを出力 sub print_footer { print <<"EOD"; <address> <a href="http://www.hyuki.com/yukiwiki/">YukiWiki</a> 1.6.6 Copyright (C) 2000,2001 by <a href="http://www.hyuki.com/">Hiroshi Yuki.</a> + <a href="$modifierlink">$modifier</a> ${version}. [<a href="/" title="このサーバーの首頁">/</a> <a href="/map" title="このサーバーの案内">地図</a> <a href="/search/" title="このサーバーの検索">検索</a>] </address> </body></html> EOD } # URLやページの名前からリンクを作る sub make_link { my $name = shift; $name =~ s/^<(.*)>$/$1/; if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) { if ($2) { return qq|<a href="$name"><img border="0" src="$name" /></a>|; } else { return qq|<<a href="$name">$name</a>>|; } } elsif ($name =~ /^mailto:(.*)/) { my $address = $1; return qq|<<a href="$name">$address</a>>|; } elsif ($name =~ /^urn:[0-9A-Za-z_:-]+/) { return qq|<<a href="/uri-res/N2L?${name}">$name</a>>|; } elsif ($database{$name}) { my $percent_name = &encode_percent($name); return qq|<a href="$thisurl?mycmd=read;mypage=$percent_name">$name</a>|; } else { my $percent_name = &encode_percent($name); return qq|$name<a href="$thisurl?mycmd=edit;mypage=$percent_name">?</a>|; } } # %xx の形式にエンコードする # これは、 # http://www.hyuki.com/yukiwiki/yukiwiki.cgi?mycmd=read&mypage=%3C%8C%8B%8F%E9%8D_%3E # という形式のために使われる。 # '<結城浩>' → '%3C%8C%8B%8F%E9%8D_%3E' sub encode_percent { my $name = shift; my $encoded = ''; foreach my $ch (split(//, $name)) { if ($ch =~ /[A-Za-z0-9_]/) { $encoded .= $ch; } else { $encoded .= '%' . sprintf("%02X", ord($ch)); } } return $encoded; } # テキスト本体をHTMLに変換する sub convert_html { my ($txt) = shift; my (@txt) = split(/\n/, $txt); foreach (@txt) { chomp; if (/^\*\*(.*)/) { push(@result, splice(@saved), '<h3>' . &inline($1) . '</h3>'); } elsif (/^\*(.*)/) { push(@result, splice(@saved), '<h2>' . &inline($1) . '</h2>'); } elsif (/^----/) { push(@result, splice(@saved), '<hr>'); } elsif (/^(-{1,3})(.*)/) { &back_push('ul', length($1)); push(@result, '<li>' . &inline($2) . '</li>'); } elsif (/^:([^:]+):(.*)/) { &back_push('dl', 1); push(@result, '<dt>' . &inline($1) . '</dt>', '<dd>' . &inline($2) . '</dd>'); } elsif (/^(>{1,3})(.*)/) { &back_push('blockquote', length($1)); push(@result, &inline($2)); } elsif (/^\s*$/) { push(@result, splice(@saved)); unshift(@saved, "</p>"); push(@result, "<p>"); } elsif (/^(\s+.*)$/) { &back_push('pre', 1); push(@result, &escape($1)); # Not &inline, but &escape } else { push(@result, &inline($_)); } } push(@result, splice(@saved)); return join("\n", @result); } # &back_push($tag, $count) # $tagのタグを$levelレベルまで詰める。 sub back_push { my ($tag, $level) = @_; while (@saved > $level) { push(@result, shift(@saved)); } if ($saved[0] ne "</$tag>") { push(@result, splice(@saved)); } while (@saved < $level) { unshift(@saved, "</$tag>"); push(@result, "<$tag>"); } } # 編集可能ページか? sub is_editable { my ($pagename) = @_; foreach (@uneditable) { if ($pagename eq $_) { return 0; } } if (&is_valid_name($pagename)) { return 1; } return 0; } # Validな名前か? sub is_valid_name { my ($pagename) = @_; if ($pagename =~ /^$WikiName$/) { return 1; } elsif ($pagename =~ /^$BracketName$/) { return 1; } else { return 0; } } # 現在時刻の文字列を得る sub get_current_datestr { my (@wdays) = ( "日", "月", "火", "水", "木", "金", "土" ); my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time); return sprintf("%4d-%02d-%02d (%s) %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $wdays[$wday], $hour, $min, $sec); } # URL?SomePageや、 # URL?[[結城浩]]の形式だった場合、(not yet) # 強制的にmycmdをreadにして$formの内容を設定する。 sub normalize_form { foreach my $key (keys %form) { if ($key =~ /^$WikiName$/) { $form{mycmd} = 'read'; $form{mypage} = $1; last; } elsif ($key =~ /^$BracketName$/) { $form{mycmd} = 'read'; $form{mypage} = $1; last; } } } # 変換テストを行なうときのサンプル sub print_sample { my $txt = &convert_html(<<"EOD"); *大見出し1 **小見出し1-1 -項目1 -項目2 -項目3 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落''強調''1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 **小見出し1-2 :用語1:いろいろ書いた解説文1と''強調単語'' 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 :用語2:いろいろ書いた解説文2 :用語3:いろいろ書いた解説文3 ---- *大見出し2 **小見出し2-1 <http://suika.fam.cx/> **小見出し2-2 [[結城浩]] 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落'''強調'''1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落'''''強い強調'''''1段落1段落1段落1段落1段落1段落1段落1段落1 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 レベル0レベル0レベル0レベル0レベル0レベル0 >レベル1 >レベル1 >レベル1 >>レベル2 >>レベル2 >>レベル2 >>>レベル3 -はろ1 --はろ2 ろろろろ2 ---はろ3 --はろ2 ---はろ3 --はろ2 ---はろ3 >>>レベル3 >>>レベル3 >>>レベル3 EOD print $txt; exit; } sub diff_check { traverse_sequences( $msgrefA, $msgrefB, { MATCH => \&df_match, DISCARD_A => \&df_delete, DISCARD_B => \&df_add, } ); &diff_flush; } sub diff_flush { $diff_text .= join('', map { "-$_\n" } splice(@diff_deleted)); $diff_text .= join('', map { "+$_\n" } splice(@diff_added)); } sub df_match { my ($a, $b) = @_; &diff_flush; $diff_text .= "=$msgrefA->[$a]\n"; } sub df_delete { my ($a, $b) = @_; push(@diff_deleted, $msgrefA->[$a]); } sub df_add { my ($a, $b) = @_; push(@diff_added, $msgrefB->[$b]); } sub calc_message_digest { # You have to use MD5... my $text = shift; my @text = split(//, $text); my $len = length($text); my $checksum = 0; foreach (@text) { $checksum += ord($_); $checksum = ($checksum * 2) % 65536 + (($checksum & 32768) ? 1 : 0); # 16bit rotate } return "$len:$checksum"; } # Definition of YukiWikiDB package YukiWikiDB; my $debug = 1; # Constructor sub new { return shift->TIEHASH(@_); } # tying sub TIEHASH { my ($class, $dbname) = @_; my $self = { dir => $dbname, keys => [], }; if (not -d $self->{dir}) { if (!mkdir($self->{dir}, 0777)) { print "mkdir(" . $self->{dir} . ") fail\n" if ($debug); return undef; } } return bless($self, $class); } # Store sub STORE { my ($self, $key, $val) = @_; my $file = &make_filename($self, $key); if (open(FILE,"> $file")) { binmode(FILE); print FILE $val; close(FILE); return $self->{$key} = $val; } else { print "$file create error."; } } # Fetch sub FETCH { my ($self, $key) = @_; my $file = &make_filename($self, $key); if (open(FILE, $file)) { local $/; $self->{$key} = <FILE>; close(FILE); } return $self->{$key}; } # Exists sub EXISTS { my ($self, $key) = @_; my $file = &make_filename($self, $key); return -e($file); } # Delete sub DELETE { my ($self, $key) = @_; my $file = &make_filename($self, $key); unlink $file; return delete $self->{$key}; } sub FIRSTKEY { my ($self) = @_; opendir(DIR, $self->{dir}) or die $self->{dir}; @{$self->{keys}} = grep /\.txt$/, readdir(DIR); foreach my $name (@{$self->{keys}}) { $name =~ s/\.txt$//; $name =~ s/[0-9A-F][0-9A-F]/pack("C", hex($&))/eg; } return shift @{$self->{keys}}; } sub NEXTKEY { my ($self) = @_; return shift @{$self->{keys}}; } sub make_filename { my ($self, $key) = @_; my $enkey = ''; foreach my $ch (split(//, $key)) { $enkey .= sprintf("%02X", ord($ch)); } return $self->{dir} . "/$enkey.txt"; } __END__ =head1 NAME YukiWiki - 自由にページを追加・削除・編集できるWebページ構築CGI Copyright (C) 2000,2001 by Hiroshi Yuki. 結城浩 <hyuki@hyuki.com> http://www.hyuki.com/ http://www.hyuki.com/yukiwiki/ =head1 SYNOPSIS http://www.hyuki.com/yukiwiki/yukiwiki.cgi =head1 DESCRIPTION YukiWiki(結城ウィキィ)は参加者が自由にページを追加・削除・編集できる 不思議なWebページ群を作るCGIです。 Webで動作する掲示板とちょっと似ていますが、 Web掲示板が単にメッセージを追加するだけなのに対して、 YukiWikiは、Webページ全体を自由に変更することができます。 YukiWikiは、Cunningham & CunninghamのWikiWikiWebの 仕様を参考にして独自に作られました。 YukiWikiはPerlで書かれたCGIスクリプトとして実現されていますので、 Perlが動作するWebサーバならば比較的容易に設置できます。 あとはdbmopenが使える環境ならば設置できます(Version 1.5.0以降ならdbmopenが使えなくても設置できます)。 YukiWikiはフリーソフトです。 ご自由にお使いください。 =head1 設置方法 =head2 入手 YukiWikiの最新版は、 http://www.hyuki.com/yukiwiki/ から入手できます。 =head2 ファイル一覧 readme.txt ドキュメント yukiwiki.cgi YukiWiki本体 yukiwiki.gif ロゴ(カラー版) yukimono.gif ロゴ(モノクロ版) jcode.pl 漢字コードライブラリ =head2 インストール =over 4 =item 1. アーカイブを解く。 =item 2. yukiwiki.cgiのはじめの方にある設定を確認します。 通常は何もしなくてよいが、 はじめは$touchfileを''にした方がよいでしょう。 =item 3. yukiwiki.cgiとjcode.plを同じところに設置します。 =item 4. サイズ0のyukiwiki.dbというファイルを設置します。 (Perlシステムによってはyukiwiki.pag, yukiwiki.dir) =item 5. yukiwiki.cgiにブラウザからアクセスします。 =back =head2 パーミッション ファイル パーミッション 転送モード yukiwiki.cgi 755 ASCII yukiwiki.gif 644 BINARY yukimono.gif 644 BINARY jcode.pl 644 ASCII $dbmopen = 1; にした場合: yukiwiki.db 666 BINARY (yukiwiki.pag, yukiwiki.dirの場合もあり) $dbmopen = 0; にした場合: (カレントディレクトリを777にしておく) . 777 (転送しない) =head1 データのバックアップ方法 $dbmopen = 1;の場合は、 データはすべてyukiwiki.db(.dir, .pag)に入る。 これをバックアップすればよい。 $dbmopen = 0;の場合は、 yukiwikiというディレクトリができるので、 これ以下をバックアップすればよい。 =head1 新しいページの作り方 =over 4 =item 1. まず、適当なページ(例えばFrontPage)を選び、 ページの下にある「編集」リンクをたどります。 =item 2. するとテキスト入力ができる状態になるので、 そこにNewPageのような単語 (大文字小文字混在している英文字列) を書いて「保存」します。 =item 3. 保存すると、FrontPageのページが書き換わり、 あなたが書いたNewPageという文字列の後ろに ? というリンクが表示されます。 この ? はそのページがまだ存在しないことを示す印です。 =item 4. その ? をクリックすると新しいページNewPageができますので、 あなたの好きな文章をその新しいページに書いて保存します。 =item 5. NewPageページができるとFrontPageの ? は消えて、リンクとなります。 =back =head1 テキスト整形のルール =over 4 =item * 連続した複数行はフィルされて表示されます。 =item * 空行は段落C<< <p> >>の区切りとなります。 =item * HTMLのタグは書けません。 =item * B<''ボールド''>のようにシングルクォート二つではさむと、 ボールドC<< <b> >>になります。 =item * B<'''イタリック'''>のようにシングルクォート三つではさむと、 イタリックC<< <i> >>になります。 =item * B<---->のようにマイナス4つがあると、 水平線C<< <hr> >>になります。 =item * 行をB<*>ではじめると、 大見出しC<< <h2> >>になります。 =item * 行をB<**>ではじめると、 小見出しC<< <h3> >>になります。 =item * 行をマイナス-ではじめると、 箇条書きC<< <ul> >>になります。 マイナスの数が増えるとレベルが下がります(3レベルまで) -項目1 --項目1-1 --項目1-2 -項目2 -項目3 --項目3-1 ---項目3-1-1 ---項目3-1-2 --項目3-2 =item * コロンを使うと、 用語と解説文のリストC<< <dl> >>が書けます。 :用語1:いろいろ書いた解説文1 :用語2:いろいろ書いた解説文2 :用語3:いろいろ書いた解説文3 =item * リンク =over 4 =item * LinkToSomePageやFrontPageのように、 英単語の最初の一文字を大文字にしたものが 二つ以上連続したものはYukiWikiのページ名となり、 それが文章中に含まれるとリンクになります。 =item * http://www.hyuki.com/ のようなURLは自動的にリンクになります。 =item * 二重の大かっこ[[ ]]でくくった文字列も、 YukiWikiのページ名になります。 大かっこの中にはスペースを含めてはいけません。 日本語も使えます。 =back =item * 行頭がスペースやタブで始まっていると、 それは整形済みの段落C<< <pre> >>として扱われます。 プログラムの表示などに使うと便利です。 =item * 行を > ではじめると、 引用文C<< <blockquote> >>が書けます。 >の数が多いとインデントが深くなります(3レベルまで)。 >文章 >文章 >>さらなる引用 >文章 =back =head1 更新履歴 =over 4 =item * 2001年10月20日、Version 1.6.6。 更新の衝突対策。 元ページの簡単なチェックサムを取っておき、 更新前にチェックサムを比較する。 修正個所はdigestという文字列を検索すれば分かる。 本来はMD5などでちゃんとやった方がいいのだけれど。 衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。 =item * 2001年10月17日、Version 1.6.5。 プレビュー画面で、更新ボタンを押したときに送信される メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。 代わりに、textarea要素を使う。 再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。 =item * 2001年8月30日、Version 1.6.4。 URLでダイレクトにページ名を指定しても、 $WikiNameと$BracketName以外のページを作れないようにした。 (is_valid_nameとis_editable参照)。 =item * 2001年8月30日、Version 1.6.3。 RecentChangesを編集・再編集不可とした。 編集不可ページは@uneditableにページ名を入れる。 =item * 2001年2月25日、Version 1.6.1, 1.6.2。 差分機能のバグ修正。 do_previewで'>'が扱えないバグを修正 (ユーザからの指摘)。 =item * 2001年2月22日、Version 1.6.0。 差分機能を実装した。 =item * 2001年2月19日、Version 1.5.4。 画像ファイルへのリンクは画像にしてみた。 =item * 2001年2月19日、Version 1.5.3。 RecentChangesの中に削除したページがあるのをやめた。 use strict;で引っかかる部分を少し整理(完全ではない)。 =item * 2001年2月16日、Version 1.5.2。 textareaに表示およびプレビューする前に < や > を < や > に変換した (do_preview, editpage, print_preview_buttons)。 =item * 2000年12月27日、Version 1.5.1。 プレビュー画面を整理した。 =item * 2000年12月22日、Version 1.5.0。 全体的にずいぶん書き直した。 一覧を別途作成するようにした(do_list)。 書き込む前に確認画面を出すようにした(do_preview)。 テキストの書き方を編集画面に入れた(do_edit, do_reedit)。 WhatsNew→RecentChanges、TopPage→FrontPageに変更した。 =item * 2000年12月20日、Version 1.1.0。 tieを利用して、dbmopenが使えない場合でも動作するように修正。 利用者の1人である「極悪」さんから 送っていただいたコードを元にしています。 =item * 2000年9月5日、Version 1.0.2。 <body color=...> → <body bgcolor=...> 利用者からの指摘による。感謝。 =item * 2000年8月6日、Version 1.0.1を公開。 C MAGAZINE(ソフトバンクパブリッシング) 2000年10月号連載記事向け公開版。 [[ ]] の最後が「望」のようにシフトJISで 0x5Dになる場合の回避を行なった。 =item * 2000年8月5日、Version 1.0.0を公開。 =item * 2000年7月23日、Version 0.82を公開。 編集時のリンクミス。 <textarea>の属性変更。 =item * 2000年7月22日、Version 0.81を公開。 ロゴを組み込む。 =item * 2000年7月21日、Version 0.80を公開。 PODをCGI中に書き込む。 =item * 2000年7月19日、Version 0.70を公開。 '''イタリック'''や、--、---、>>、>>>などを実装。 =item * 2000年7月18日、Version 0.60を公開。 *太字*を''太字''に変更 =item * 2000年7月17日、Version 0.50を公開。 =item * 2000年7月17日、さらにいろいろ追加する。 =item * 2000年7月16日、いろいろ追加。 =item * 2000年7月15日、公開。 =back =head1 TODO - テキスト表示モード - Charsetを明示。 - textarea中の閉じタグ対応 - メニューの英語表記付記 - プレビューのボタンで、mymsgをinputのvalueに入れているが、改行をそのままvalueにいれてはいけないのではないか。 - 「再編集」の機能はブラウザの back で充分ではないか。プレビューはもっとシンプルに。 - ページタイトル(Wikiname)が検索にかかるようにする。 - InterWiki風の機能「URLを隠しつつ、リンクを張る」 =head1 作者 Copyright (C) 2000 by Hiroshi Yuki. 結城浩 <hyuki@hyuki.com> http://www.hyuki.com/ http://www.hyuki.com/yukiwiki/ 質問、意見、バグ報告は hyuki@hyuki.com にメールしてください。 =head1 配布条件 YukiWikiは、 GNU General Public Licenseにて公開します。 YukiWikiはフリーソフトです。 ご自由にお使いください。 自分好みのYukiWikiが作れるようにシンプルにしてあります。 =head1 謝辞 本家のWikiWikiを作ったCunningham & Cunningham, Inc.に 感謝します。 YukiWikiを楽しんで使ってくださる ネット上の方々に感謝します。 YukiWikiのロゴをデザインしてくださった橋本礼奈さん http://city.hokkai.or.jp/~reina/ に感謝します。 tieを使った版の元になるコードを送ってくださった 「極悪」さんに感謝します。 =head1 参照リンク =over 4 =item * YukiWikiホームページ http://www.hyuki.com/yukiwiki/ =item * 本家のWikiWiki http://c2.com/cgi/wiki?WikiWikiWeb =item * 本家のWikiWikiの作者(Cunningham & Cunningham, Inc.) http://c2.com/ =item * YukiWikiのロゴデザインをしてくださった橋本礼奈さんのページ http://city.hokkai.or.jp/~reina/ =back =cut