--- suikawiki/script/wiki.cgi 2002/02/04 15:27:22 1.7 +++ suikawiki/script/wiki.cgi 2002/03/24 00:51:52 1.8 @@ -1,1484 +1,1491 @@ -#!/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. -# -# 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"; -#[YukiWiki] -#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|

$IconTag$page_name

\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|

$IconTag${page_name}の編集

\n|; - &print_toolbar($page_name); - $page_msg = &escape($page_msg); - print <<"EOD"; -
- - - -
- - -
-
-

テキスト整形のルール

- -

通常は入力した文字がそのまま出力されますが、 -以下のルールに従ってテキスト整形を行うことができます。

- - -EOD - &print_footer; -} - -# ページの検索 -sub do_search { - if ($form{myword}) { - &print_header('検索結果'); - print qq|

$IconTag$form{myword}の検索結果

\n|; - &print_toolbar(); - print qq|\n|; - if ($count > 0) { - print qq|

$form{myword}を含むページは、上に示す$count ページです。

\n|; - } else { - print qq|

$form{myword}を含むページは見つかりません。

\n|; - } - } else { - &print_header('単語検索'); - print qq|

$IconTag単語検索

\n|; - &print_toolbar(); - } - print <<"EOD"; -

-

- - - -
-

-EOD - &print_footer; -} - -# ページの一覧 -sub do_list { - &print_header('ページ一覧'); - print qq|

$IconTag ページ一覧

\n|; - &print_toolbar(); - print qq|\n|; - &print_footer; -} - -# プレビュー -sub do_preview { - my $page_name = $form{mypage}; - my $escapedmsg = &escape($form{mymsg}); - &print_header($page_name); - print qq|

$IconTag${page_name}のプレビュー

\n|; - &print_toolbar($page_name); - # local $percent_name = &encode_percent($page_name); - print qq|

以下のプレビューを確認して、よければページ下部のボタンで更新してください。

\n|; - if ($form{mymsg}) { - print qq|
\n|; - # print &convert_html($escapedmsg); - print &convert_html($form{mymsg}); - print qq|
\n|; - } else { - print qq|

(ページの内容は空です。更新するとこのページは削除されます。)

\n|; - } - &print_preview_buttons($page_name, $escapedmsg, $form{mydigest}); - &print_footer; -} - -sub print_preview_buttons { - my ($page_name, $escapedmsg, $digest) = @_; - print <<"EOD"; -
- -
- - - - -
-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|

$IconTag${page_name}で【更新の衝突】が起きました

\n|; - print <<"EOD"; -

あなたがこのページを編集している間に、 -他の人が同じページを更新してしまったようです。 -

-以下に、あなたの編集したテキストがありますので、 -あなたの編集内容が失われないように、 -いますぐ、メモ帳などにコピー&ペーストしてください。 -

-コピー&ペーストが済んでから、 -最新の内容を見て再度編集し直してください。 -最新の内容は -$form{mypage} -で見ることができます。 -

-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|

$IconTag${page_name}を更新しました

\n|; - &print_toolbar($page_name); - print &convert_html(&get_page($page_name)); - } else { - print qq|

$IconTag${page_name}を削除しました

\n|; - &print_toolbar($page_name); - print qq|

${page_name}を削除しました。

\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|

$IconTag $form{mypage}の変更点

\n|; - &print_toolbar(); - $_ = &escape($diffbase{$form{mypage}}); - print <<"EOD"; - -
-EOD - print qq|
\n|;
-    foreach (split(/\n/, $_)) {
-        if (/^\+(.*)/) {
-            print qq|$1\n|;
-        } elsif (/^\-(.*)/) {
-            print qq|$1\n|;
-        } elsif (/^\=(.*)/) {
-            print qq|$1\n|;
-        } else {
-            print qq|??? $_\n|;
-        }
-    }
-    print qq|
\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 "

$IconTag$msg

"; - 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|'''([^']+?)'''|$1|g; - $line =~ s|''([^']+?)''|$1|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 - - -$title - - - -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"; -編集 | -差分 | -EOD - } - print <<"EOD"; -

- [ -トップ | -一覧 | -$editlink -単語検索 | -最終更新 - ] -

-EOD -} - -# ページのフッタを出力 -sub print_footer { - print <<"EOD"; -
-YukiWiki 1.6.6 Copyright (C) 2000,2001 by Hiroshi Yuki. -+ $modifier ${version}. -[/ -地図 -検索] -
- -EOD -} - -# URLやページの名前からリンクを作る -sub make_link { - my $name = shift; - $name =~ s/^<(.*)>$/$1/; - if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) { - if ($2) { - return qq||; - } else { - return qq|<$name>|; - } - } elsif ($name =~ /^mailto:(.*)/) { - my $address = $1; - return qq|<$address>|; - } elsif ($name =~ /^urn:[0-9A-Za-z_:-]+/) { - return qq|<$name>|; - } elsif ($database{$name}) { - my $percent_name = &encode_percent($name); - return qq|$name|; - } else { - my $percent_name = &encode_percent($name); - return qq|$name?|; - } -} - -# %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), '

' . &inline($1) . '

'); - } elsif (/^\*(.*)/) { - push(@result, splice(@saved), '

' . &inline($1) . '

'); - } elsif (/^----/) { - push(@result, splice(@saved), '
'); - } elsif (/^(-{1,3})(.*)/) { - &back_push('ul', length($1)); - push(@result, '
  • ' . &inline($2) . '
  • '); - } elsif (/^:([^:]+):(.*)/) { - &back_push('dl', 1); - push(@result, '
    ' . &inline($1) . '
    ', '
    ' . &inline($2) . '
    '); - } elsif (/^(>{1,3})(.*)/) { - &back_push('blockquote', length($1)); - push(@result, &inline($2)); - } elsif (/^\s*$/) { - push(@result, splice(@saved)); - unshift(@saved, "

    "); - push(@result, "

    "); - } 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 "") { - push(@result, splice(@saved)); - } - while (@saved < $level) { - unshift(@saved, ""); - 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} = ; - 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. - 結城浩 - 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<<

    >>の区切りとなります。 - -=item * - -HTMLのタグは書けません。 - -=item * - -B<''ボールド''>のようにシングルクォート二つではさむと、 -ボールドC<< >>になります。 - -=item * - -B<'''イタリック'''>のようにシングルクォート三つではさむと、 -イタリックC<< >>になります。 - -=item * - -B<---->のようにマイナス4つがあると、 -水平線C<<


    >>になります。 - -=item * - -行をB<*>ではじめると、 -大見出しC<<

    >>になります。 - -=item * - -行をB<**>ではじめると、 -小見出しC<<

    >>になります。 - -=item * - -行をマイナス-ではじめると、 -箇条書きC<<
      >>になります。 -マイナスの数が増えるとレベルが下がります(3レベルまで) - - -項目1 - --項目1-1 - --項目1-2 - -項目2 - -項目3 - --項目3-1 - ---項目3-1-1 - ---項目3-1-2 - --項目3-2 - -=item * - -コロンを使うと、 -用語と解説文のリストC<<
      >>が書けます。 - - :用語1:いろいろ書いた解説文1 - :用語2:いろいろ書いた解説文2 - :用語3:いろいろ書いた解説文3 - -=item * - -リンク - -=over 4 - -=item * - -LinkToSomePageやFrontPageのように、 -英単語の最初の一文字を大文字にしたものが -二つ以上連続したものはYukiWikiのページ名となり、 -それが文章中に含まれるとリンクになります。 - -=item * - -http://www.hyuki.com/ のようなURLは自動的にリンクになります。 - -=item * - -二重の大かっこ[[ ]]でくくった文字列も、 -YukiWikiのページ名になります。 -大かっこの中にはスペースを含めてはいけません。 -日本語も使えます。 - -=back - -=item * - -行頭がスペースやタブで始まっていると、 -それは整形済みの段落C<<
       >>として扱われます。
      -プログラムの表示などに使うと便利です。
      -
      -
      -=item *
      -
      -行を > ではじめると、
      -引用文C<< 
      >>が書けます。 ->の数が多いとインデントが深くなります(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。 - → -利用者からの指摘による。感謝。 - -=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を公開。 -編集時のリンクミス。 -
      + + + +
      +

      テキスト整形のルール

      + +

      通常は入力した文字がそのまま出力されますが、 +以下のルールに従ってテキスト整形を行うことができます。

      + +
        +
      • +空行は段落の区切りとなります。 + +
      • +HTMLのタグは書けません。 + +
      • +''強調''のようにシングルクォート二つではさむと、強調になります。 + +
      • +'''更に強調'''のようにシングルクォート三つではさむと、更に強調になります。 + +
      • +----のようにマイナス4つがあると、水平線になります。 + +
      • +*を行頭に書くと大見出しになります。 + +
      • +**を行頭に書くと小見出しになります。 + +
      • +-を行頭に書くと箇条書きになります。- -- --- の3レベルがあります。 + +
      • +:を行頭に書くと用語と解説文が作れます。 + +
        +    :用語1:いろいろ書いた解説文1
        +    :用語2:いろいろ書いた解説文2
        +    :用語3:いろいろ書いた解説文3
        +
        + +
      • +http://www.hyuki.com/ のようなURLは自動的にリンクになります。 + +
      • +YukiWikiのように大文字小文字を混ぜた英文字列を書くと、 +YukiWikiのページ名になります。 + +
      • +[[結城浩]]のように二重の大かっこ[[ ]]でくくった文字列を書くと、 +YukiWikiのページ名になります。 +大かっこの中にはスペースを含めてはいけません。 +日本語も使えます。 + +
      • +行頭がスペースで始まっていると、 +その段落は整形済み扱われます。 +プログラムを書き込むときに使うと便利です。 + +
      • +> を行頭に書くと、 +引用文が書けます。 +> の数が多いとインデントが深くなります(3レベルまで)。 + +
      +EOD + &print_footer; +} + +# ページの検索 +sub do_search { + if ($form{myword}) { + &print_header('検索結果'); + print qq|

      $IconTag$form{myword}の検索結果

      \n|; + &print_toolbar(); + print qq|
        \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|
      • $page_name\n|; + $count++; + } + } + print qq|
      \n|; + if ($count > 0) { + print qq|

      $form{myword}を含むページは、上に示す$count ページです。

      \n|; + } else { + print qq|

      $form{myword}を含むページは見つかりません。

      \n|; + } + } else { + &print_header('単語検索'); + print qq|

      $IconTag単語検索

      \n|; + &print_toolbar(); + } + print <<"EOD"; +

      +

      + + + +
      +

      +EOD + &print_footer; +} + +# ページの一覧 +sub do_list { + &print_header('ページ一覧'); + print qq|

      $IconTag ページ一覧

      \n|; + &print_toolbar(); + print qq|
        \n|; + foreach my $page_name (sort keys %database) { # sortするのは無謀かな + my $encoded = &encode_percent($page_name); + print qq|
      • $page_name\n| + } + print qq|
      \n|; + &print_footer; +} + +# プレビュー +sub do_preview { + my $page_name = $form{mypage}; + my $escapedmsg = &escape($form{mymsg}); + &print_header($page_name); + print qq|

      $IconTag${page_name}のプレビュー

      \n|; + &print_toolbar($page_name); + # local $percent_name = &encode_percent($page_name); + print qq|

      以下のプレビューを確認して、よければページ下部のボタンで更新してください。

      \n|; + if ($form{mymsg}) { + print qq|
      \n|; + # print &convert_html($escapedmsg); + print &convert_html($form{mymsg}); + print qq|
      \n|; + } else { + print qq|

      (ページの内容は空です。更新するとこのページは削除されます。)

      \n|; + } + &print_preview_buttons($page_name, $escapedmsg, $form{mydigest}); + &print_footer; +} + +sub print_preview_buttons { + my ($page_name, $escapedmsg, $digest) = @_; + print <<"EOD"; +
      + +
      + + + + +
      +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|

      $IconTag${page_name}で【更新の衝突】が起きました

      \n|; + print <<"EOD"; +

      あなたがこのページを編集している間に、 +他の人が同じページを更新してしまったようです。 +

      +以下に、あなたの編集したテキストがありますので、 +あなたの編集内容が失われないように、 +いますぐ、メモ帳などにコピー&ペーストしてください。 +

      +コピー&ペーストが済んでから、 +最新の内容を見て再度編集し直してください。 +最新の内容は +$form{mypage} +で見ることができます。 +

      +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|

      $IconTag${page_name}を更新しました

      \n|; + &print_toolbar($page_name); + print &convert_html(&get_page($page_name)); + } else { + print qq|

      $IconTag${page_name}を削除しました

      \n|; + &print_toolbar($page_name); + print qq|

      ${page_name}を削除しました。

      \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|

      $IconTag $form{mypage}の変更点

      \n|; + &print_toolbar(); + $_ = &escape($diffbase{$form{mypage}}); + print <<"EOD"; +
        +
      • 追加された行は青色です。 +
      • 削除された行は赤色です。 +
      • $form{mypage}へ行く。 +
      +
      +EOD + print qq|
      \n|;
      +    foreach (split(/\n/, $_)) {
      +        if (/^\+(.*)/) {
      +            print qq|$1\n|;
      +        } elsif (/^\-(.*)/) {
      +            print qq|$1\n|;
      +        } elsif (/^\=(.*)/) {
      +            print qq|$1\n|;
      +        } else {
      +            print qq|??? $_\n|;
      +        }
      +    }
      +    print qq|
      \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 "

      $IconTag$msg

      "; + 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|'''([^']+?)'''|$1|g; + $line =~ s|''([^']+?)''|$1|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 + + +$title + + + +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"; +編集 | +差分 | +EOD + } + print <<"EOD"; +

      + [ +トップ | +一覧 | +$editlink +単語検索 | +最終更新 + ] +

      +EOD +} + +# ページのフッタを出力 +sub print_footer { + print <<"EOD"; +
      +YukiWiki 1.6.6 Copyright (C) 2000,2001 by Hiroshi Yuki. ++ $modifier ${version}. +[/ +地図 +検索] +
      + +EOD +} + +# URLやページの名前からリンクを作る +sub make_link { + my $name = shift; + $name =~ s/^<(.*)>$/$1/; + $name =~ s/^\[\[(.*)\]\]$/$1/; + if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) { + if ($2) { + return qq||; + } else { + return qq|<$name>|; + } + } elsif ($name =~ /^mailto:(.*)/) { + my $address = $1; + return qq|<$address>|; + } elsif ($name =~ /^urn:[0-9A-Za-z_:-]+/) { + return qq|<$name>|; + } else { + my $name2 = $name; $name2 =~ tr/\x20/-/; + if ($database{$name2}) { + my $percent_name = &encode_percent($name2); + return qq|$name|; + } elsif ($database{'[['.$name2.']]'}) { + my $percent_name = &encode_percent('[['.$name2.']]'); + return qq|$name|; + } else { + my $percent_name = &encode_percent($name2); + return qq|$name?|; + } + } +} + +# %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), '

      ' . &inline($1) . '

      '); + } elsif (/^\*(.*)/) { + push(@result, splice(@saved), '

      ' . &inline($1) . '

      '); + } elsif (/^----/) { + push(@result, splice(@saved), '
      '); + } elsif (/^(-{1,3})(.*)/) { + &back_push('ul', length($1)); + push(@result, '
    • ' . &inline($2) . '
    • '); + } elsif (/^:([^:]+):(.*)/) { + &back_push('dl', 1); + push(@result, '
      ' . &inline($1) . '
      ', '
      ' . &inline($2) . '
      '); + } elsif (/^(>{1,3})(.*)/) { + &back_push('blockquote', length($1)); + push(@result, &inline($2)); + } elsif (/^\s*$/) { + push(@result, splice(@saved)); + unshift(@saved, "

      "); + push(@result, "

      "); + } 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 "") { + push(@result, splice(@saved)); + } + while (@saved < $level) { + unshift(@saved, ""); + 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} = ; + 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. + 結城浩 + 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<<

      >>の区切りとなります。 + +=item * + +HTMLのタグは書けません。 + +=item * + +B<''ボールド''>のようにシングルクォート二つではさむと、 +ボールドC<< >>になります。 + +=item * + +B<'''イタリック'''>のようにシングルクォート三つではさむと、 +イタリックC<< >>になります。 + +=item * + +B<---->のようにマイナス4つがあると、 +水平線C<<


      >>になります。 + +=item * + +行をB<*>ではじめると、 +大見出しC<<

      >>になります。 + +=item * + +行をB<**>ではじめると、 +小見出しC<<

      >>になります。 + +=item * + +行をマイナス-ではじめると、 +箇条書きC<<
        >>になります。 +マイナスの数が増えるとレベルが下がります(3レベルまで) + + -項目1 + --項目1-1 + --項目1-2 + -項目2 + -項目3 + --項目3-1 + ---項目3-1-1 + ---項目3-1-2 + --項目3-2 + +=item * + +コロンを使うと、 +用語と解説文のリストC<<
        >>が書けます。 + + :用語1:いろいろ書いた解説文1 + :用語2:いろいろ書いた解説文2 + :用語3:いろいろ書いた解説文3 + +=item * + +リンク + +=over 4 + +=item * + +LinkToSomePageやFrontPageのように、 +英単語の最初の一文字を大文字にしたものが +二つ以上連続したものはYukiWikiのページ名となり、 +それが文章中に含まれるとリンクになります。 + +=item * + +http://www.hyuki.com/ のようなURLは自動的にリンクになります。 + +=item * + +二重の大かっこ[[ ]]でくくった文字列も、 +YukiWikiのページ名になります。 +大かっこの中にはスペースを含めてはいけません。 +日本語も使えます。 + +=back + +=item * + +行頭がスペースやタブで始まっていると、 +それは整形済みの段落C<<
         >>として扱われます。
        +プログラムの表示などに使うと便利です。
        +
        +
        +=item *
        +
        +行を > ではじめると、
        +引用文C<< 
        >>が書けます。 +>の数が多いとインデントが深くなります(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。 + → +利用者からの指摘による。感謝。 + +=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を公開。 +編集時のリンクミス。 +