/[pub]/suikawiki/script/wiki.cgi
Suika

Contents of /suikawiki/script/wiki.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (show annotations) (download)
Mon Feb 4 13:31:47 2002 UTC (22 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.1: +1482 -1482 lines
2002-02-02  wakaba <wakaba@suika.fam.cx>

	* wiki.cgi: New file.
	* ChangeLog: Likewise.

1 #!/usr/bin/perl
2 use lib "../lib";
3 use CGI::Carp 'fatalsToBrowser';
4 use Algorithm::Diff qw(traverse_sequences);
5 # use strict;
6 #
7 # yukiwiki.cgi - Yet another WikiWikiWeb clone.
8 #
9 # Copyright (C) 2000,2001 by Hiroshi Yuki.
10 # <hyuki@hyuki.com>
11 # http://www.hyuki.com/yukiwiki/
12 #
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # $Id: wiki.cgi,v 1.1 2002/02/04 13:19:08 wakaba Exp $
24 ##############################
25 my $version = "1.6.6";
26 ##############################
27 # 単独テストのときには 1 にする。
28 my $testing = 0;
29 ##############################
30 # 漢字ライブラリ
31 my $jcodelib = 'jcode.pl';
32 ##############################
33 # 保存・表示の漢字コード
34 my $kanjicode = 'euc'; # 'sjis' 'euc'
35 my $charset = 'euc-jisx0213'; # 'Shift_JIS' 'EUC-JP'
36 ##############################
37 # dbmopenが使えるなら1、使えないなら0
38 my $dbmopen = 0;
39 ##############################
40 # データベース名(.pag, .dir, .dbなどは不要)
41 # $dbmopen = 1のときはデータベース名、
42 # $dbmopen = 0のときはディレクトリ名になる。
43 my $dbname = './wiki';
44 my $diffdbname = './wikidiff';
45 ##############################
46 # 修正者の氏名(自由に変更してください)
47 my $modifier = 'suika';
48 ##############################
49 # 修正者のWebページ(自由に変更してください)
50 my $modifierlink = 'http://suika.fam.cx/';
51 ##############################
52 # このページのURL
53 my $thisurl = 'wiki';
54 ##############################
55 # 開始ページ名
56 my $toppage = 'HomePage';
57 ##############################
58 # 最終更新ページ名
59 my $whatsnew = 'RecentChanges';
60 ##############################
61 # 最終更新に掲載するページ数
62 my $maxnew = 50;
63 ##############################
64 # アイコンファイル名(カラー版)
65 my $iconfile = '';
66 ##############################
67 # アイコンファイル名(モノクロ版)
68 # my $iconfile = '';
69 ##############################
70 # ページを変更したときにtouchするファイル(''なら何もしない)
71 my $touchfile = 'touch.txt';
72 ##############################
73 # プレビュー用の背景色
74 my $preview_color = '#FFCCCC';
75 ##############################
76 # 全ページのスタイル
77 my $style = <<'EOD';
78 pre, dl, ul, ol, p, blockquote { line-height:120%; }
79 a { text-decoration: none; }
80 a:link { color: #0000FF; background-color: #FFFFFF; }
81 a:visited { color: #9900CC; background-color: #FFFFFF; }
82 a:hover { text-decoration: underline; }
83 EOD
84 ##############################
85 # テキスト入力部分の大きさ
86 my $cols = 80;
87 my $rows = 20;
88 ##############################
89 my %form = ();
90 my %database = ();
91 my %diffbase = ();
92 my $diff_text = '';
93 my @diff_added = ();
94 my @diff_deleted = ();
95 my $msgrefA;
96 my $msgrefB;
97 ##############################
98 # 編集不可ページ名一覧
99 my @uneditable = ( $whatsnew );
100 ##############################
101 # リンク用の正規表現
102 # YukiWikiのリンクは2種類ある。
103 #
104 # (1) WikiName (RecentChangesとかFrontPageのようなもの)
105 # (2) BracketName ([[結城浩]]とか[[トラブルシュート]]のようなもの)
106 #
107 # ※シフトJISの2バイト目には ']' が来うるので、
108 # 文字']'を1つ多くとるようにしている。
109 #
110 my $WikiName = '([A-Z][a-z]+([A-Z][a-z]+)+)';
111 my $BracketName = '\[\[([^>\s]+?\]?)\]\]';
112
113 # アイコン部分のタグ
114 my $IconTag = ''; #<<"EOD";
115 #<a href="http://www.hyuki.com/yukiwiki/"><img src="$iconfile"
116 # border="0" width="80" height="80" alt="[YukiWiki]" /></a>
117 #EOD
118
119 require "$jcodelib";
120
121 &init_form($kanjicode);
122
123 if ($testing) {
124 %form = (
125 # 'mycmd' => 'write',
126 'mycmd' => 'read',
127 #'mycmd' => 'search',
128 #'mycmd' => 'edit',
129 'mymsg' => <<"EOD",
130 はじめまして。
131 これからいろいろ書き込みますね。
132 LinkPageも見てください。
133 TestPageはどうでしょうか。
134 どうぞよろしく。
135 http://www.hyuki.com/
136 [[結城浩]]
137 EOD
138 'mypage' => '<結城浩>',
139 'myword' => '結',
140 # '3C8C8B8FE98D5F3E' => '',
141 # 'TestPage' => '',
142 );
143 }
144 &main;
145 exit(0);
146
147 # メイン
148 sub main {
149 &normalize_form;
150 if ($dbmopen) {
151 if (!dbmopen(%database, $dbname, 0666)) {
152 &print_error("(dbmopen) $dbname が作れません。");
153 }
154 } else {
155 if (!tie(%database, "YukiWikiDB", $dbname)) {
156 &print_error("(tie error)");
157 }
158 }
159
160 # myspecial対応
161 foreach (keys %form) {
162 if (/^myspecial_(.*)/) {
163 $form{mycmd} = $1;
164 last;
165 }
166 }
167
168 if ($form{mycmd} eq 'read') {
169 &do_read;
170 } elsif ($form{mycmd} eq 'preview') {
171 &do_preview;
172 } elsif ($form{mycmd} eq 'write') {
173 &do_write;
174 } elsif ($form{mycmd} eq 'edit') {
175 &do_edit;
176 } elsif ($form{mycmd} eq 'reedit') {
177 &do_reedit;
178 } elsif ($form{mycmd} eq 'search') {
179 &do_search;
180 } elsif ($form{mycmd} eq 'list') {
181 &do_list;
182 } elsif ($form{mycmd} eq 'diff') {
183 &do_diff;
184 } else {
185 $form{mypage} = $toppage;
186 &do_read;
187 }
188 if ($dbmopen) {
189 dbmclose(%database);
190 } else {
191 untie(%database);
192 }
193 }
194
195 # ページの表示
196 sub do_read {
197 my $page_name = $form{mypage};
198 my $percent_name = &encode_percent($page_name);
199 &print_header($page_name);
200 print qq|<h1>$IconTag<a href="$thisurl?mycmd=search;myword=$percent_name">$page_name</a></h1>\n|;
201 &print_toolbar($page_name);
202 print &convert_html(&get_page($page_name));
203 &print_footer;
204 }
205
206 # ページの編集
207 sub do_edit {
208 if (not &is_editable($form{mypage})) {
209 # 編集不可ページは表示のみ
210 &do_read;
211 return;
212 }
213 &editpage(&get_page($form{mypage}));
214 }
215
216 # ページの再編集
217 sub do_reedit {
218 if (not &is_editable($form{mypage})) {
219 # 編集不可ページは表示のみ
220 &do_read;
221 } else {
222 &editpage($form{mymsg});
223 }
224 }
225
226 sub editpage {
227 my $page_msg = shift;
228 my $page_name = $form{mypage};
229 my $digest = &calc_message_digest($page_msg);
230 &print_header($page_name);
231 print qq|<h1>$IconTag${page_name}の編集</h1>\n|;
232 &print_toolbar($page_name);
233 $page_msg = &escape($page_msg);
234 print <<"EOD";
235 <form action="$thisurl" method="post">
236 <input type="hidden" name="mycmd" value="preview">
237 <input type="hidden" name="mypage" value="$page_name">
238 <input type="hidden" name="mydigest" value="$digest">
239 <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$page_msg</textarea><br>
240 <input type="submit" value="プレビュー">
241 </form>
242 <hr>
243 <h3>テキスト整形のルール</h3>
244
245 <p>通常は入力した文字がそのまま出力されますが、
246 以下のルールに従ってテキスト整形を行うことができます。</p>
247
248 <ul>
249 <li>
250 空行は段落の区切りとなります。
251
252 <li>
253 HTMLのタグは書けません。
254
255 <li>
256 ''強調''のようにシングルクォート二つではさむと、強調になります。
257
258 <li>
259 '''更に強調'''のようにシングルクォート三つではさむと、更に強調になります。
260
261 <li>
262 ----のようにマイナス4つがあると、水平線になります。
263
264 <li>
265 *を行頭に書くと大見出しになります。
266
267 <li>
268 **を行頭に書くと小見出しになります。
269
270 <li>
271 -を行頭に書くと箇条書きになります。- -- --- の3レベルがあります。
272
273 <li>
274 :を行頭に書くと用語と解説文が作れます。
275
276 <pre>
277 :用語1:いろいろ書いた解説文1
278 :用語2:いろいろ書いた解説文2
279 :用語3:いろいろ書いた解説文3
280 </pre>
281
282 <li>
283 http://www.hyuki.com/ のようなURLは自動的にリンクになります。
284
285 <li>
286 YukiWikiのように大文字小文字を混ぜた英文字列を書くと、
287 YukiWikiのページ名になります。
288
289 <li>
290 [[結城浩]]のように二重の大かっこ[[ ]]でくくった文字列を書くと、
291 YukiWikiのページ名になります。
292 大かっこの中にはスペースを含めてはいけません。
293 日本語も使えます。
294
295 <li>
296 行頭がスペースで始まっていると、
297 その段落は整形済み扱われます。
298 プログラムを書き込むときに使うと便利です。
299
300 <li>
301 &gt; を行頭に書くと、
302 引用文が書けます。
303 &gt; の数が多いとインデントが深くなります(3レベルまで)。
304
305 </ul>
306 EOD
307 &print_footer;
308 }
309
310 # ページの検索
311 sub do_search {
312 if ($form{myword}) {
313 &print_header('検索結果');
314 print qq|<h1>$IconTag$form{myword}の検索結果</h1>\n|;
315 &print_toolbar();
316 print qq|<ul>\n|;
317 my $count = 0;
318 foreach my $page_name (sort keys %database) { # sortするのは無謀かな
319 if ($database{$page_name} =~ /\Q$form{'myword'}\E/) {
320 my $encoded = &encode_percent($page_name);
321 print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n|;
322 $count++;
323 }
324 }
325 print qq|</ul>\n|;
326 if ($count > 0) {
327 print qq|<p><strong>$form{myword}</strong>を含むページは、上に示す<strong>$count</strong> ページです。</p>\n|;
328 } else {
329 print qq|<p><strong>$form{myword}</strong>を含むページは見つかりません。</p>\n|;
330 }
331 } else {
332 &print_header('単語検索');
333 print qq|<h1>$IconTag単語検索</h1>\n|;
334 &print_toolbar();
335 }
336 print <<"EOD";
337 <p>
338 <form action="$thisurl" method="post">
339 <input type="hidden" name="mycmd" value="search">
340 <input type="text" name="myword" size="20" value="$form{myword}">
341 <input type="submit" value="単語検索">
342 </form>
343 </p>
344 EOD
345 &print_footer;
346 }
347
348 # ページの一覧
349 sub do_list {
350 &print_header('ページ一覧');
351 print qq|<h1>$IconTag ページ一覧</h1>\n|;
352 &print_toolbar();
353 print qq|<ul>\n|;
354 foreach my $page_name (sort keys %database) { # sortするのは無謀かな
355 my $encoded = &encode_percent($page_name);
356 print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n|
357 }
358 print qq|</ul>\n|;
359 &print_footer;
360 }
361
362 # プレビュー
363 sub do_preview {
364 my $page_name = $form{mypage};
365 my $escapedmsg = &escape($form{mymsg});
366 &print_header($page_name);
367 print qq|<h1>$IconTag${page_name}のプレビュー</h1>\n|;
368 &print_toolbar($page_name);
369 # local $percent_name = &encode_percent($page_name);
370 print qq|<p>以下のプレビューを確認して、よければページ下部のボタンで更新してください。</p>\n|;
371 if ($form{mymsg}) {
372 print qq|<table width="100%" bgcolor="$preview_color" ><tr><td>\n|;
373 # print &convert_html($escapedmsg);
374 print &convert_html($form{mymsg});
375 print qq|</td></tr></table>\n|;
376 } else {
377 print qq|<p>(ページの内容は空です。更新するとこのページは<strong>削除</strong>されます。)</p>\n|;
378 }
379 &print_preview_buttons($page_name, $escapedmsg, $form{mydigest});
380 &print_footer;
381 }
382
383 sub print_preview_buttons {
384 my ($page_name, $escapedmsg, $digest) = @_;
385 print <<"EOD";
386 <form action="$thisurl" method="post">
387 <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$escapedmsg</textarea>
388 <br />
389 <input type="hidden" name="mypage" value="$page_name">
390 <input type="hidden" name="mydigest" value="$digest">
391 <input type="submit" name="myspecial_preview" value="再度プレビュー">
392 <input type="submit" name="myspecial_write" value="ページの更新">
393 </form>
394 EOD
395 }
396
397 # 書き込む
398 sub do_write {
399 if (not &is_editable($form{mypage})) {
400 # 編集不可ページは表示のみ
401 &do_read;
402 return;
403 }
404
405 my $page_name = $form{mypage};
406
407 # digestを使って、更新の衝突チェック
408 my $original_digest = &calc_message_digest(&get_page($page_name));
409 if ($form{mydigest} ne $original_digest) {
410 &print_header($page_name);
411 print qq|<h1>$IconTag${page_name}で【更新の衝突】が起きました</h1>\n|;
412 print <<"EOD";
413 <p>あなたがこのページを編集している間に、
414 他の人が同じページを更新してしまったようです。
415 </p><p>
416 以下に、あなたの編集したテキストがありますので、
417 あなたの編集内容が失われないように、
418 いますぐ、メモ帳などにコピー&ペーストしてください。
419 </p><p>
420 コピー&ペーストが済んでから、
421 最新の内容を見て再度編集し直してください。
422 最新の内容は
423 <a target="_blank" href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a>
424 で見ることができます。
425 </p>
426 EOD
427 # &print_toolbar($page_name);
428 &print_preview_buttons($page_name, &escape($form{mymsg}), $form{mydigest});
429 &print_footer;
430 return;
431 }
432
433 # diff生成
434 {
435 &opendiff;
436 my @msg1 = split(/\n/, &get_page($page_name));
437 my @msg2 = split(/\n/, $form{mymsg});
438 $msgrefA = \@msg1;
439 $msgrefB = \@msg2;
440 &diff_check;
441 $diffbase{$form{mypage}} = $diff_text;
442 $diff_text = '';
443 &closediff;
444 }
445
446 &print_header($page_name);
447 &set_page($page_name, $form{mymsg});
448 if ($form{mymsg}) {
449 print qq|<h1>$IconTag${page_name}を更新しました</h1>\n|;
450 &print_toolbar($page_name);
451 print &convert_html(&get_page($page_name));
452 } else {
453 print qq|<h1>$IconTag${page_name}を削除しました</h1>\n|;
454 &print_toolbar($page_name);
455 print qq|<p>${page_name}を削除しました。</p>\n|;
456 }
457 &print_footer;
458 # 更新されたのでタッチしておく。
459 if ($touchfile) {
460 open(FILE, "> $touchfile");
461 print FILE "\n";
462 close(FILE);
463 }
464 }
465
466 # ページの変更点
467 sub do_diff {
468 if (not &is_editable($form{mypage})) {
469 # 編集不可ページは表示のみ
470 &do_read;
471 return;
472 }
473 &opendiff;
474 &print_header($form{mypage} . 'の変更点');
475 print qq|<h1>$IconTag <a href="$thisurl?mycmd=read&mypage=$form{mypage}">$form{mypage}</a>の変更点</h1>\n|;
476 &print_toolbar();
477 $_ = &escape($diffbase{$form{mypage}});
478 print <<"EOD";
479 <ul>
480 <li>追加された行は<ins>青色</ins>です。
481 <li>削除された行は<del>赤色</del>です。
482 <li><a href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a>へ行く。
483 </ul>
484 <hr />
485 EOD
486 print qq|<pre>\n|;
487 foreach (split(/\n/, $_)) {
488 if (/^\+(.*)/) {
489 print qq|<ins>$1</ins>\n|;
490 } elsif (/^\-(.*)/) {
491 print qq|<del>$1</del>\n|;
492 } elsif (/^\=(.*)/) {
493 print qq|$1\n|;
494 } else {
495 print qq|??? $_\n|;
496 }
497 }
498 print qq|</pre>\n|;
499 &print_footer;
500 &closediff;
501 }
502
503 sub opendiff {
504 if ($dbmopen) {
505 if (!dbmopen(%diffbase, $diffdbname, 0666)) {
506 &print_error("(dbmopen) $diffdbname が作れません。");
507 }
508 } else {
509 if (!tie(%diffbase, "YukiWikiDB", $diffdbname)) {
510 &print_error("(tie error)");
511 }
512 }
513 }
514
515 sub closediff {
516 if ($dbmopen) {
517 dbmclose(%diffbase);
518 } else {
519 untie(%diffbase);
520 }
521 }
522
523 # フォームからの情報を連想配列 %form に入れる
524 # &init_form('euc');
525 sub init_form {
526 my ($charcode) = @_;
527 my $query;
528 if ($ENV{REQUEST_METHOD} =~ /^post$/i) {
529 read(STDIN, $query, $ENV{CONTENT_LENGTH});
530 } else {
531 $query = $ENV{QUERY_STRING};
532 }
533 my @assocarray = split(/[&;]/, $query);
534 foreach my $assoc (@assocarray) {
535 my ($property, $value) = split(/=/, $assoc);
536 $value =~ tr/+/ /;
537 $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;
538 &jcode::convert(\$value, $charcode);
539 $form{$property} = $value;
540 }
541 }
542
543 # エラーページを出力する
544 sub print_error {
545 my ($msg) = @_;
546 &print_header('Error');
547 print "<h1>$IconTag$msg</h1></body></html>";
548 exit(0);
549 }
550
551 sub escape {
552 my ($line) = shift;
553 $line =~ s|<|&lt;|g;
554 $line =~ s|>|&gt;|g;
555 $line =~ s|"|&quot;|g;
556 # $line =~ s|\&|&amp;|g;
557 return $line;
558 }
559
560 sub inline {
561 my ($line) = shift;
562 $line = &escape($line);
563 $line =~ s|'''([^']+?)'''|<strong>$1</strong>|g;
564 $line =~ s|''([^']+?)''|<em>$1</em>|g;
565 $line =~ s!
566 (
567 ((mailto|http|https|ftp):[\x21-\x7E]*) # Direct http://...
568 |
569 ($WikiName) # LocalLinkLikeThis
570 |
571 ($BracketName) # [[日本語リンク]]
572 )
573 !
574 &make_link($1)
575 !gex;
576 return $line;
577 }
578
579 # ページのタイトルからページの内容を得る
580 sub get_page {
581 my $page_name = shift;
582 return $database{$page_name};
583 }
584
585 # ページの内容を与える
586 # &set_page($title, $txt)
587 sub set_page {
588 # ページを更新する
589 my $title = $_[0];
590 $database{$title} = $_[1];
591 # 空ページなら削除する
592 unless ($database{$title}) {
593 delete $database{$title};
594 }
595 # RecentChangesを更新する
596 my $delim = ' - ';
597 my @pages = split(/\n/, $database{$whatsnew});
598 my $datestr = &get_current_datestr;
599 unshift(@pages, qq|-$datestr$delim$title|);
600 # 同一ページの更新は最新のもののみにし、
601 # 存在しないページはスキップする。
602 my %count;
603 my @newpages;
604 foreach my $line (@pages) {
605 my ($prefix, $title) = split(/$delim/, $line);
606 $count{$title}++;
607 if ($count{$title} == 1 and exists($database{$title})) {
608 push(@newpages, qq|$prefix - $title|);
609 }
610 }
611 # ここで本当に更新
612 $database{$whatsnew} = join("\n", splice(@newpages, 0, $maxnew));
613 }
614
615 # ページのヘッダを出力
616 sub print_header {
617 my $title = shift;
618 print <<"EOD";
619 Content-type: text/html
620
621 <html><head>
622 <title>$title</title>
623 <style type="text/css">
624 $style
625 </style>
626 </head>
627 <body>
628 EOD
629 }
630
631 # ツールバーを出力
632 sub print_toolbar {
633 my $page_name = shift;
634 my $percent_name = &encode_percent($page_name);
635 my $editlink = '';
636 if ($page_name ne '' and &is_editable($page_name)) {
637 $editlink = <<"EOD";
638 <a href="$thisurl?mycmd=edit;mypage=$percent_name">編集</a> |
639 <a href="$thisurl?mycmd=diff;mypage=$percent_name">差分</a> |
640 EOD
641 }
642 print <<"EOD";
643 <p>
644 [
645 <a href="$thisurl?mycmd=read;mypage=$toppage">トップ</a> |
646 <a href="$thisurl?mycmd=list">一覧</a> |
647 $editlink
648 <a href="$thisurl?mycmd=search">単語検索</a> |
649 <a href="$thisurl?mycmd=read;mypage=$whatsnew">最終更新</a>
650 ]
651 </p>
652 EOD
653 }
654
655 # ページのフッタを出力
656 sub print_footer {
657 print <<"EOD";
658 <address>
659 <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>
660 + <a href="$modifierlink">$modifier</a> ${version}.
661 [<a href="/" title="このサーバーの首頁">/</a>
662 <a href="/map" title="このサーバーの案内">地図</a>
663 <a href="/search/" title="このサーバーの検索">検索</a>]
664 </address>
665 </body></html>
666 EOD
667 }
668
669 # URLやページの名前からリンクを作る
670 sub make_link {
671 my $name = shift;
672 if ($name =~ /^(http|https|ftp).*?(\.gif|\.png|\.jpeg|\.jpg)?$/) {
673 if ($2) {
674 return qq|<a href="$name"><img border="0" src="$name" /></a>|;
675 } else {
676 return qq|<a href="$name">$name</a>|;
677 }
678 } elsif ($name =~ /^mailto:(.*)/) {
679 my $address = $1;
680 return qq|<a href="$name">$address</a>|;
681 } elsif ($database{$name}) {
682 my $percent_name = &encode_percent($name);
683 return qq|<a href="$thisurl?mycmd=read;mypage=$percent_name">$name</a>|;
684 } else {
685 my $percent_name = &encode_percent($name);
686 return qq|$name<a href="$thisurl?mycmd=edit;mypage=$percent_name">?</a>|;
687 }
688 }
689
690 # %xx の形式にエンコードする
691 # これは、
692 # http://www.hyuki.com/yukiwiki/yukiwiki.cgi?mycmd=read&mypage=%3C%8C%8B%8F%E9%8D_%3E
693 # という形式のために使われる。
694 # '<結城浩>' → '%3C%8C%8B%8F%E9%8D_%3E'
695 sub encode_percent {
696 my $name = shift;
697 my $encoded = '';
698 foreach my $ch (split(//, $name)) {
699 if ($ch =~ /[A-Za-z0-9_]/) {
700 $encoded .= $ch;
701 } else {
702 $encoded .= '%' . sprintf("%02X", ord($ch));
703 }
704 }
705 return $encoded;
706 }
707
708 # テキスト本体をHTMLに変換する
709 sub convert_html {
710 my ($txt) = shift;
711 my (@txt) = split(/\n/, $txt);
712 foreach (@txt) {
713 chomp;
714 if (/^\*\*(.*)/) {
715 push(@result, splice(@saved), '<h3>' . &inline($1) . '</h3>');
716 } elsif (/^\*(.*)/) {
717 push(@result, splice(@saved), '<h2>' . &inline($1) . '</h2>');
718 } elsif (/^----/) {
719 push(@result, splice(@saved), '<hr>');
720 } elsif (/^(-{1,3})(.*)/) {
721 &back_push('ul', length($1));
722 push(@result, '<li>' . &inline($2) . '</li>');
723 } elsif (/^:([^:]+):(.*)/) {
724 &back_push('dl', 1);
725 push(@result, '<dt>' . &inline($1) . '</dt>', '<dd>' . &inline($2) . '</dd>');
726 } elsif (/^(>{1,3})(.*)/) {
727 &back_push('blockquote', length($1));
728 push(@result, &inline($2));
729 } elsif (/^\s*$/) {
730 push(@result, splice(@saved));
731 unshift(@saved, "</p>");
732 push(@result, "<p>");
733 } elsif (/^(\s+.*)$/) {
734 &back_push('pre', 1);
735 push(@result, &escape($1)); # Not &inline, but &escape
736 } else {
737 push(@result, &inline($_));
738 }
739 }
740 push(@result, splice(@saved));
741 return join("\n", @result);
742 }
743
744 # &back_push($tag, $count)
745 # $tagのタグを$levelレベルまで詰める。
746 sub back_push {
747 my ($tag, $level) = @_;
748 while (@saved > $level) {
749 push(@result, shift(@saved));
750 }
751 if ($saved[0] ne "</$tag>") {
752 push(@result, splice(@saved));
753 }
754 while (@saved < $level) {
755 unshift(@saved, "</$tag>");
756 push(@result, "<$tag>");
757 }
758 }
759
760 # 編集可能ページか?
761 sub is_editable {
762 my ($pagename) = @_;
763 foreach (@uneditable) {
764 if ($pagename eq $_) {
765 return 0;
766 }
767 }
768 if (&is_valid_name($pagename)) {
769 return 1;
770 }
771 return 0;
772 }
773
774 # Validな名前か?
775 sub is_valid_name {
776 my ($pagename) = @_;
777 if ($pagename =~ /^$WikiName$/) {
778 return 1;
779 } elsif ($pagename =~ /^$BracketName$/) {
780 return 1;
781 } else {
782 return 0;
783 }
784 }
785
786 # 現在時刻の文字列を得る
787 sub get_current_datestr {
788 my (@wdays) = ( "日", "月", "火", "水", "木", "金", "土" );
789 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time);
790 return sprintf("%4d-%02d-%02d (%s) %02d:%02d:%02d",
791 $year + 1900, $mon + 1, $mday, $wdays[$wday], $hour, $min, $sec);
792 }
793
794 # URL?SomePageや、
795 # URL?[[結城浩]]の形式だった場合、(not yet)
796 # 強制的にmycmdをreadにして$formの内容を設定する。
797 sub normalize_form {
798 foreach my $key (keys %form) {
799 if ($key =~ /^$WikiName$/) {
800 $form{mycmd} = 'read';
801 $form{mypage} = $1;
802 last;
803 } elsif ($key =~ /^$BracketName$/) {
804 $form{mycmd} = 'read';
805 $form{mypage} = $1;
806 last;
807 }
808 }
809 }
810
811 # 変換テストを行なうときのサンプル
812 sub print_sample {
813 my $txt = &convert_html(<<"EOD");
814 *大見出し1
815 **小見出し1-1
816 -項目1
817 -項目2
818 -項目3
819 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
820 段落1段落1段落1段落1段落1段落''強調''1段落1段落1段落1段落1段落1
821 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
822
823 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
824 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
825 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
826 **小見出し1-2
827 :用語1:いろいろ書いた解説文1と''強調単語''
828 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
829 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
830 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
831 :用語2:いろいろ書いた解説文2
832 :用語3:いろいろ書いた解説文3
833 ----
834 *大見出し2
835 **小見出し2-1
836 http://www.hyuki.com/
837 **小見出し2-2
838
839 [[結城浩]]
840
841 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
842 段落1段落1段落1段落'''強調'''1段落1段落1段落1段落1段落1段落1段落1
843 段落1段落1段落1段落'''''強い強調'''''1段落1段落1段落1段落1段落1段落1段落1段落1
844 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
845 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
846 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
847
848 レベル0レベル0レベル0レベル0レベル0レベル0
849
850 >レベル1
851 >レベル1
852 >レベル1
853 >>レベル2
854 >>レベル2
855 >>レベル2
856 >>>レベル3
857 -はろ1
858 --はろ2
859 ろろろろ2
860 ---はろ3
861 --はろ2
862 ---はろ3
863 --はろ2
864 ---はろ3
865 >>>レベル3
866 >>>レベル3
867 >>>レベル3
868 EOD
869 print $txt;
870 exit;
871 }
872
873 sub diff_check {
874 traverse_sequences(
875 $msgrefA, $msgrefB,
876 {
877 MATCH => \&df_match,
878 DISCARD_A => \&df_delete,
879 DISCARD_B => \&df_add,
880 }
881 );
882 &diff_flush;
883 }
884
885 sub diff_flush {
886 $diff_text .= join('', map { "-$_\n" } splice(@diff_deleted));
887 $diff_text .= join('', map { "+$_\n" } splice(@diff_added));
888 }
889
890 sub df_match {
891 my ($a, $b) = @_;
892 &diff_flush;
893 $diff_text .= "=$msgrefA->[$a]\n";
894 }
895
896 sub df_delete {
897 my ($a, $b) = @_;
898 push(@diff_deleted, $msgrefA->[$a]);
899 }
900
901 sub df_add {
902 my ($a, $b) = @_;
903 push(@diff_added, $msgrefB->[$b]);
904 }
905
906 sub calc_message_digest { # You have to use MD5...
907 my $text = shift;
908 my @text = split(//, $text);
909 my $len = length($text);
910 my $checksum = 0;
911 foreach (@text) {
912 $checksum += ord($_);
913 $checksum = ($checksum * 2) % 65536 + (($checksum & 32768) ? 1 : 0); # 16bit rotate
914 }
915 return "$len:$checksum";
916 }
917
918 # Definition of YukiWikiDB
919 package YukiWikiDB;
920
921 my $debug = 1;
922
923 # Constructor
924 sub new {
925 return shift->TIEHASH(@_);
926 }
927
928 # tying
929 sub TIEHASH {
930 my ($class, $dbname) = @_;
931 my $self = {
932 dir => $dbname,
933 keys => [],
934 };
935 if (not -d $self->{dir}) {
936 if (!mkdir($self->{dir}, 0777)) {
937 print "mkdir(" . $self->{dir} . ") fail\n" if ($debug);
938 return undef;
939 }
940 }
941 return bless($self, $class);
942 }
943
944 # Store
945 sub STORE {
946 my ($self, $key, $val) = @_;
947 my $file = &make_filename($self, $key);
948 if (open(FILE,"> $file")) {
949 binmode(FILE);
950 print FILE $val;
951 close(FILE);
952 return $self->{$key} = $val;
953 } else {
954 print "$file create error.";
955 }
956 }
957
958 # Fetch
959 sub FETCH {
960 my ($self, $key) = @_;
961 my $file = &make_filename($self, $key);
962 if (open(FILE, $file)) {
963 local $/;
964 $self->{$key} = <FILE>;
965 close(FILE);
966 }
967 return $self->{$key};
968 }
969
970 # Exists
971 sub EXISTS {
972 my ($self, $key) = @_;
973 my $file = &make_filename($self, $key);
974 return -e($file);
975 }
976
977 # Delete
978 sub DELETE {
979 my ($self, $key) = @_;
980 my $file = &make_filename($self, $key);
981 unlink $file;
982 return delete $self->{$key};
983 }
984
985 sub FIRSTKEY {
986 my ($self) = @_;
987 opendir(DIR, $self->{dir}) or die $self->{dir};
988 @{$self->{keys}} = grep /\.txt$/, readdir(DIR);
989 foreach my $name (@{$self->{keys}}) {
990 $name =~ s/\.txt$//;
991 $name =~ s/[0-9A-F][0-9A-F]/pack("C", hex($&))/eg;
992 }
993 return shift @{$self->{keys}};
994 }
995
996 sub NEXTKEY {
997 my ($self) = @_;
998 return shift @{$self->{keys}};
999 }
1000
1001 sub make_filename {
1002 my ($self, $key) = @_;
1003 my $enkey = '';
1004 foreach my $ch (split(//, $key)) {
1005 $enkey .= sprintf("%02X", ord($ch));
1006 }
1007 return $self->{dir} . "/$enkey.txt";
1008 }
1009 __END__
1010
1011 =head1 NAME
1012
1013 YukiWiki - 自由にページを追加・削除・編集できるWebページ構築CGI
1014
1015 Copyright (C) 2000,2001 by Hiroshi Yuki.
1016 結城浩 <hyuki@hyuki.com>
1017 http://www.hyuki.com/
1018 http://www.hyuki.com/yukiwiki/
1019
1020 =head1 SYNOPSIS
1021
1022 http://www.hyuki.com/yukiwiki/yukiwiki.cgi
1023
1024 =head1 DESCRIPTION
1025
1026 YukiWiki(結城ウィキィ)は参加者が自由にページを追加・削除・編集できる
1027 不思議なWebページ群を作るCGIです。
1028 Webで動作する掲示板とちょっと似ていますが、
1029 Web掲示板が単にメッセージを追加するだけなのに対して、
1030 YukiWikiは、Webページ全体を自由に変更することができます。
1031
1032 YukiWikiは、Cunningham & CunninghamのWikiWikiWebの
1033 仕様を参考にして独自に作られました。
1034
1035 YukiWikiはPerlで書かれたCGIスクリプトとして実現されていますので、
1036 Perlが動作するWebサーバならば比較的容易に設置できます。
1037
1038 あとはdbmopenが使える環境ならば設置できます(Version 1.5.0以降ならdbmopenが使えなくても設置できます)。
1039
1040
1041 YukiWikiはフリーソフトです。
1042 ご自由にお使いください。
1043
1044 =head1 設置方法
1045
1046 =head2 入手
1047
1048 YukiWikiの最新版は、
1049 http://www.hyuki.com/yukiwiki/
1050 から入手できます。
1051
1052 =head2 ファイル一覧
1053
1054 readme.txt ドキュメント
1055 yukiwiki.cgi YukiWiki本体
1056 yukiwiki.gif ロゴ(カラー版)
1057 yukimono.gif ロゴ(モノクロ版)
1058 jcode.pl 漢字コードライブラリ
1059
1060 =head2 インストール
1061
1062 =over 4
1063
1064 =item 1.
1065
1066 アーカイブを解く。
1067
1068 =item 2.
1069
1070 yukiwiki.cgiのはじめの方にある設定を確認します。
1071 通常は何もしなくてよいが、
1072 はじめは$touchfileを''にした方がよいでしょう。
1073
1074 =item 3.
1075
1076 yukiwiki.cgiとjcode.plを同じところに設置します。
1077
1078 =item 4.
1079
1080 サイズ0のyukiwiki.dbというファイルを設置します。
1081 (Perlシステムによってはyukiwiki.pag, yukiwiki.dir)
1082
1083 =item 5.
1084
1085 yukiwiki.cgiにブラウザからアクセスします。
1086
1087 =back
1088
1089 =head2 パーミッション
1090
1091 ファイル パーミッション 転送モード
1092 yukiwiki.cgi 755 ASCII
1093 yukiwiki.gif 644 BINARY
1094 yukimono.gif 644 BINARY
1095 jcode.pl 644 ASCII
1096
1097 $dbmopen = 1; にした場合:
1098 yukiwiki.db 666 BINARY
1099 (yukiwiki.pag, yukiwiki.dirの場合もあり)
1100
1101 $dbmopen = 0; にした場合: (カレントディレクトリを777にしておく)
1102 . 777 (転送しない)
1103
1104 =head1 データのバックアップ方法
1105
1106 $dbmopen = 1;の場合は、
1107 データはすべてyukiwiki.db(.dir, .pag)に入る。
1108 これをバックアップすればよい。
1109
1110 $dbmopen = 0;の場合は、
1111 yukiwikiというディレクトリができるので、
1112 これ以下をバックアップすればよい。
1113
1114 =head1 新しいページの作り方
1115
1116 =over 4
1117
1118 =item 1.
1119
1120 まず、適当なページ(例えばFrontPage)を選び、
1121 ページの下にある「編集」リンクをたどります。
1122
1123 =item 2.
1124
1125 するとテキスト入力ができる状態になるので、
1126 そこにNewPageのような単語
1127 (大文字小文字混在している英文字列)
1128 を書いて「保存」します。
1129
1130 =item 3.
1131
1132 保存すると、FrontPageのページが書き換わり、
1133 あなたが書いたNewPageという文字列の後ろに ? というリンクが表示されます。
1134 この ? はそのページがまだ存在しないことを示す印です。
1135
1136 =item 4.
1137
1138 その ? をクリックすると新しいページNewPageができますので、
1139 あなたの好きな文章をその新しいページに書いて保存します。
1140
1141 =item 5.
1142
1143 NewPageページができるとFrontPageの ? は消えて、リンクとなります。
1144
1145 =back
1146
1147 =head1 テキスト整形のルール
1148
1149 =over 4
1150
1151 =item *
1152
1153 連続した複数行はフィルされて表示されます。
1154
1155 =item *
1156
1157 空行は段落C<< <p> >>の区切りとなります。
1158
1159 =item *
1160
1161 HTMLのタグは書けません。
1162
1163 =item *
1164
1165 B<''ボールド''>のようにシングルクォート二つではさむと、
1166 ボールドC<< <b> >>になります。
1167
1168 =item *
1169
1170 B<'''イタリック'''>のようにシングルクォート三つではさむと、
1171 イタリックC<< <i> >>になります。
1172
1173 =item *
1174
1175 B<---->のようにマイナス4つがあると、
1176 水平線C<< <hr> >>になります。
1177
1178 =item *
1179
1180 行をB<*>ではじめると、
1181 大見出しC<< <h2> >>になります。
1182
1183 =item *
1184
1185 行をB<**>ではじめると、
1186 小見出しC<< <h3> >>になります。
1187
1188 =item *
1189
1190 行をマイナス-ではじめると、
1191 箇条書きC<< <ul> >>になります。
1192 マイナスの数が増えるとレベルが下がります(3レベルまで)
1193
1194 -項目1
1195 --項目1-1
1196 --項目1-2
1197 -項目2
1198 -項目3
1199 --項目3-1
1200 ---項目3-1-1
1201 ---項目3-1-2
1202 --項目3-2
1203
1204 =item *
1205
1206 コロンを使うと、
1207 用語と解説文のリストC<< <dl> >>が書けます。
1208
1209 :用語1:いろいろ書いた解説文1
1210 :用語2:いろいろ書いた解説文2
1211 :用語3:いろいろ書いた解説文3
1212
1213 =item *
1214
1215 リンク
1216
1217 =over 4
1218
1219 =item *
1220
1221 LinkToSomePageやFrontPageのように、
1222 英単語の最初の一文字を大文字にしたものが
1223 二つ以上連続したものはYukiWikiのページ名となり、
1224 それが文章中に含まれるとリンクになります。
1225
1226 =item *
1227
1228 http://www.hyuki.com/ のようなURLは自動的にリンクになります。
1229
1230 =item *
1231
1232 二重の大かっこ[[ ]]でくくった文字列も、
1233 YukiWikiのページ名になります。
1234 大かっこの中にはスペースを含めてはいけません。
1235 日本語も使えます。
1236
1237 =back
1238
1239 =item *
1240
1241 行頭がスペースやタブで始まっていると、
1242 それは整形済みの段落C<< <pre> >>として扱われます。
1243 プログラムの表示などに使うと便利です。
1244
1245
1246 =item *
1247
1248 行を > ではじめると、
1249 引用文C<< <blockquote> >>が書けます。
1250 >の数が多いとインデントが深くなります(3レベルまで)。
1251
1252 >文章
1253 >文章
1254 >>さらなる引用
1255 >文章
1256
1257 =back
1258
1259 =head1 更新履歴
1260
1261 =over 4
1262
1263 =item *
1264
1265 2001年10月20日、Version 1.6.6。
1266
1267 更新の衝突対策。
1268 元ページの簡単なチェックサムを取っておき、
1269 更新前にチェックサムを比較する。
1270 修正個所はdigestという文字列を検索すれば分かる。
1271 本来はMD5などでちゃんとやった方がいいのだけれど。
1272
1273 衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。
1274
1275 =item *
1276
1277 2001年10月17日、Version 1.6.5。
1278
1279 プレビュー画面で、更新ボタンを押したときに送信される
1280 メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。
1281 代わりに、textarea要素を使う。
1282 再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。
1283
1284 =item *
1285
1286 2001年8月30日、Version 1.6.4。
1287
1288 URLでダイレクトにページ名を指定しても、
1289 $WikiNameと$BracketName以外のページを作れないようにした。
1290 (is_valid_nameとis_editable参照)。
1291
1292 =item *
1293
1294 2001年8月30日、Version 1.6.3。
1295
1296 RecentChangesを編集・再編集不可とした。
1297 編集不可ページは@uneditableにページ名を入れる。
1298
1299 =item *
1300
1301 2001年2月25日、Version 1.6.1, 1.6.2。
1302
1303 差分機能のバグ修正。
1304 do_previewで'>'が扱えないバグを修正
1305 (ユーザからの指摘)。
1306
1307 =item *
1308
1309 2001年2月22日、Version 1.6.0。
1310 差分機能を実装した。
1311
1312 =item *
1313
1314 2001年2月19日、Version 1.5.4。
1315 画像ファイルへのリンクは画像にしてみた。
1316
1317 =item *
1318
1319 2001年2月19日、Version 1.5.3。
1320 RecentChangesの中に削除したページがあるのをやめた。
1321 use strict;で引っかかる部分を少し整理(完全ではない)。
1322
1323 =item *
1324
1325 2001年2月16日、Version 1.5.2。
1326 textareaに表示およびプレビューする前に < や > を &lt; や &gt; に変換した
1327 (do_preview, editpage, print_preview_buttons)。
1328
1329 =item *
1330
1331 2000年12月27日、Version 1.5.1。
1332 プレビュー画面を整理した。
1333
1334 =item *
1335
1336 2000年12月22日、Version 1.5.0。
1337 全体的にずいぶん書き直した。
1338 一覧を別途作成するようにした(do_list)。
1339 書き込む前に確認画面を出すようにした(do_preview)。
1340 テキストの書き方を編集画面に入れた(do_edit, do_reedit)。
1341 WhatsNew→RecentChanges、TopPage→FrontPageに変更した。
1342
1343 =item *
1344
1345 2000年12月20日、Version 1.1.0。
1346 tieを利用して、dbmopenが使えない場合でも動作するように修正。
1347 利用者の1人である「極悪」さんから
1348 送っていただいたコードを元にしています。
1349
1350 =item *
1351
1352 2000年9月5日、Version 1.0.2。
1353 <body color=...> → <body bgcolor=...>
1354 利用者からの指摘による。感謝。
1355
1356 =item *
1357
1358 2000年8月6日、Version 1.0.1を公開。
1359 C MAGAZINE(ソフトバンクパブリッシング)
1360 2000年10月号連載記事向け公開版。
1361 [[ ]] の最後が「望」のようにシフトJISで
1362 0x5Dになる場合の回避を行なった。
1363
1364 =item *
1365
1366 2000年8月5日、Version 1.0.0を公開。
1367
1368 =item *
1369
1370 2000年7月23日、Version 0.82を公開。
1371 編集時のリンクミス。
1372 <textarea>の属性変更。
1373
1374 =item *
1375
1376 2000年7月22日、Version 0.81を公開。
1377 ロゴを組み込む。
1378
1379 =item *
1380
1381 2000年7月21日、Version 0.80を公開。
1382 PODをCGI中に書き込む。
1383
1384 =item *
1385
1386 2000年7月19日、Version 0.70を公開。
1387 '''イタリック'''や、--、---、>>、>>>などを実装。
1388
1389 =item *
1390
1391 2000年7月18日、Version 0.60を公開。
1392 *太字*を''太字''に変更
1393
1394 =item *
1395
1396 2000年7月17日、Version 0.50を公開。
1397
1398 =item *
1399
1400 2000年7月17日、さらにいろいろ追加する。
1401
1402 =item *
1403
1404 2000年7月16日、いろいろ追加。
1405
1406 =item *
1407
1408 2000年7月15日、公開。
1409
1410 =back
1411
1412 =head1 TODO
1413
1414 - テキスト表示モード
1415 - Charsetを明示。
1416 - textarea中の閉じタグ対応
1417 - メニューの英語表記付記
1418 - プレビューのボタンで、mymsgをinputのvalueに入れているが、改行をそのままvalueにいれてはいけないのではないか。
1419 - 「再編集」の機能はブラウザの back で充分ではないか。プレビューはもっとシンプルに。
1420 - ページタイトル(Wikiname)が検索にかかるようにする。
1421 - InterWiki風の機能「URLを隠しつつ、リンクを張る」
1422
1423 =head1 作者
1424
1425 Copyright (C) 2000 by Hiroshi Yuki.
1426 結城浩 <hyuki@hyuki.com>
1427 http://www.hyuki.com/
1428 http://www.hyuki.com/yukiwiki/
1429
1430 質問、意見、バグ報告は hyuki@hyuki.com にメールしてください。
1431
1432 =head1 配布条件
1433
1434 YukiWikiは、
1435 GNU General Public Licenseにて公開します。
1436
1437 YukiWikiはフリーソフトです。
1438 ご自由にお使いください。
1439 自分好みのYukiWikiが作れるようにシンプルにしてあります。
1440
1441 =head1 謝辞
1442
1443 本家のWikiWikiを作ったCunningham & Cunningham, Inc.に
1444 感謝します。
1445
1446 YukiWikiを楽しんで使ってくださる
1447 ネット上の方々に感謝します。
1448
1449 YukiWikiのロゴをデザインしてくださった橋本礼奈さん
1450 http://city.hokkai.or.jp/~reina/
1451 に感謝します。
1452
1453 tieを使った版の元になるコードを送ってくださった
1454 「極悪」さんに感謝します。
1455
1456 =head1 参照リンク
1457
1458 =over 4
1459
1460 =item *
1461
1462 YukiWikiホームページ
1463 http://www.hyuki.com/yukiwiki/
1464
1465 =item *
1466
1467 本家のWikiWiki
1468 http://c2.com/cgi/wiki?WikiWikiWeb
1469
1470 =item *
1471
1472 本家のWikiWikiの作者(Cunningham & Cunningham, Inc.)
1473 http://c2.com/
1474
1475 =item *
1476
1477 YukiWikiのロゴデザインをしてくださった橋本礼奈さんのページ
1478 http://city.hokkai.or.jp/~reina/
1479
1480 =back
1481
1482 =cut

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24