Gauche:CGI:MiniWiKi
MiniWiKi
メモ
私が初めてWiKiなるものに触れたのがWiLiKiで、 「すげぇ!こんなん作れちゃうのか〜信じられん」 と興奮したのを覚えてます。 WiKi Way とか読むと原理は簡単とか書いてたけど信用してなかった。 機能はめちゃめちゃ低いけどこんなんが作れるようになると嬉しいですね。 ちなみに一番最初のバージョンは113行でした。 cut-sea:2004/09/03 07:34:02 PDT
TODO
書式関係はこの辺にして、もっと本質的な部分としてMUSTなもの。
- DBアクセス中のリクエストに対する処置(一時DBアクセス不可対応)
- 書きこみの衝突(Aさんedit,Bさんedit,Bさんcommit,Aさんcommit)時の処置
- ページ毎にバージョン(commitの度にincrement)番号に相当する数をもたせる。
- edit しようとしたらキーとしてバージョン番号を渡す
- commit 時にはバージョン番号を送らせる。
- 現在のバージョン番号と同じ番号ならDBに書きこんでバージョン番号をincrement
- 現在のバージョン番号より古いやつを送ってきたら衝突検出
- 簡単なのはとりあえずエラーメッセージのページ出力
- diff とってedit画面にしてやれば、なお親切なんだろう
ソース
#! /usr/local/bin/gosh (use dbm) (use dbm.gdbm) (use text.html-lite) (use gauche.parameter) (use gauche.charconv) (use rfc.uri) (use www.cgi) (use srfi-13) ;(use gauche.logger) ;;=============================== ;; user configuration ;;=============================== ;(define *log* (log-open "/home/cut-sea/data/wiki.log")) ;; for logging (define *db-file* "/home/cut-sea/data/wiki.db") ;; dbfile as storage (define *wiki* "MiniWiKi") ;; top page (define *wait-max* 10) (define *DB-BUSY-message* "resource temporarily unavailable") ;;=============================== ;; Style Sheet ;;=============================== (define *style* " body { font-family: verdana, arial, helvetica, sans-serif; color: black; background-color: #fffff8; } :link { text-decoration: none; color: #00bb00; } :visited { text-decoration: none; color: #557722; } h1 { text-align: center; font-size: 190%; color: #444400; margin-top: 20pt; margin-bottom: 20pt; border-bottom: solid thick #888844; } h2 { text-align: left; font-size: 150%; color: #448800; margin-top: 30pt; border-bottom: solid thin #448800; } h3, h4, h5, h6 { text-align: left; color: #448800; background: transparent; } h3 { font-size: 130% } h4 { font-size: 110% } h5 { font-size: 100% } h6 { font-size: 100%; font-style: italic } blockquote { background-color: #fffff0; border: dashed thin #ffbb22; } blockquote.preview { background-color: #eeffee; border: dashed thin #ff55cc; } pre { margin-left: 2em; margin-right: 2em; font-family: monospace; background-color: #bbccf8; border: solid thin #ff2222; } hr { width: 80%; } table { background-color: #eeeecc; border: solid thin #ffffee; } ") ;;=============================== ;; for database ;;=============================== (define db (make-parameter #f)) (define (db-try-open dbtype dbpath) (define (try retry) (with-error-handler (lambda (e) (cond ((>= retry *wait-max*) (raise e)) ((string-contains-ci (ref e 'message) *DB-BUSY-message*) (sys-sleep 1) (try (+ retry 1))) (else (raise (make <error> :message "Couldn't open database file."))))) (lambda () (dbm-open dbtype :path dbpath :rw-mode :write :key-convert #t :value-convert #t)))) (try 0)) (define-syntax with-db (syntax-rules () ((with-db (db path) . body) (parameterize ((db (db-try-open <gdbm> path))) (with-error-handler (lambda (e) (dbm-close (db)) (raise e)) (lambda () (begin0 (begin . body) (dbm-close (db))))))))) ;;=============================== ;; construct HTML from WiKi form ;;=============================== (define eof? eof-object?) (define (hr? line) (#/^----$/ line)) (define (p-sep? line) (#/^$/ line)) (define (pre? line) (#/^[ \t]+/ line)) (define (pre-in? line) (#/^\{\{\{$/ line)) (define (pre-out? line) (#/^\}\}\}$/ line)) (define (block-in? line) (#/^<<<$/ line)) (define (block-out? line) (#/^>>>$/ line)) (define (ul? line) (#/^-[ \t]+/ line)) (define (ol? line) (#/^#[ \t]+/ line)) (define (table? line) (#/^(\|\|.*)\|\|$/ line)) (define (tbl? line) (#/(.*)\|\|/ line)) (define (h*? line) (#/^\*{1,3}/ line)) (define (h2? line) (#/^\*[ \t]+/ line)) (define (h3? line) (#/^\*\*[ \t]+/ line)) (define (h4? line) (#/^\*\*\*[ \t]+/ line)) (define (br? line) (#/~%/ line)) (define (escape? line) (#/`([^`]+)`/ line)) (define (strong? line) (#/'''([^']+)'''/ line)) (define (em? line) (#/''([^']+)''/ line)) (define (wikiname? line) (#/\[\[([^\[]+)?\]\]/ line)) (define (wikiname->link ttl) (if (dbm-exists? (db) ttl) (html:a :href #`"?t=,(uri-encode-string ttl)" (html-escape-string ttl)) (list (html-escape-string ttl) (html:a :href #`"?t=,(uri-encode-string ttl)&c=e" "?")))) (define (wikiform->html text) (call-with-input-string text (lambda (in) (form->html in)))) (define (make-block->html terminate? return) (lambda (in) (define (loop tree line) (define (traverse-unit func) (loop (cons (func line) tree) (read-line in))) (define (traverse-block func) (loop (cons (func in) tree) (read-line in))) (define (traverse-lines func) (let* ((sub (func line in)) (next-line (car sub)) (subtree (cdr sub))) (loop (cons subtree tree) next-line))) (cond ((terminate? line) (return tree)) ((h*? line) (traverse-unit h-block)) ((hr? line) (traverse-unit hr-block)) ((p-sep? line) (traverse-lines p-block)) ((pre? line) (traverse-lines pre-lines)) ((pre-in? line) (traverse-block pre-block)) ((block-in? line) (traverse-block bq-block)) ((ul? line) (traverse-lines ul-lines)) ((ol? line) (traverse-lines ol-lines)) ((table? line) (traverse-lines tr-lines)) (else (traverse-lines p-block)))) (loop '() (read-line in)))) (define form->html (make-block->html eof? reverse)) (define bq-block (make-block->html (any-pred eof? block-out?) (lambda (tree) (html:blockquote (reverse tree))))) (define (h-block line) (define (return tag) (lambda (m) (tag (line->html (m 'after))))) (cond ((h2? line) => (return html:h2)) ((h3? line) => (return html:h3)) ((h4? line) => (return html:h4)))) (define (hr-block line) (html:hr)) (define (p-block line in) (define (return line tree) (cons line (html:p (reverse tree)))) (define terminate? (any-pred eof? p-sep? h*? hr? pre? pre-in? block-in? block-out? ul? ol? table?)) (define (loop tree line) (if (terminate? line) (return line tree) (loop (cons (line->html line) tree) (read-line in)))) (loop '() (if (string=? line "") (read-line in) line))) (define (pre-lines line in) (define (return line tree) (cons line (html:pre (reverse tree)))) (define (loop tree line) (cond ((pre? line) => (lambda (m) (loop (cons (html:br) (cons (m 'after) tree)) (read-line in)))) (else (return line tree)))) (loop '() line)) (define (pre-block in) (define (return tree) (html:pre (reverse tree))) (define terminate? (any-pred eof? pre-out?)) (define (loop tree line) (if (terminate? line) (return tree) (loop (cons (html:br) (cons line tree)) (read-line in)))) (loop '() (read-line in))) (define (make-ul&ol self-pred? alter-pred? tag) (lambda (line in) (define (return line tree) (cons line tree)) (define terminate? (any-pred eof? p-sep? h*? hr? pre? pre-in? block-in? block-out? table? alter-pred?)) (define (list->ulol lst) (tag (map (lambda (item) (html:li (line->html item))) lst))) (define (loop tree line) (cond ((terminate? line) (return line (list->ulol (reverse tree)))) ((self-pred? line) => (lambda (m) (loop (cons (m 'after) tree) (read-line in)))) (else (let ((last-line (car tree)) (prev-tree (cdr tree))) (loop (cons (string-append last-line line) prev-tree) (read-line in)))))) (loop '() line))) (define ul-lines (make-ul&ol ul? ol? html:ul)) (define ol-lines (make-ul&ol ol? ul? html:ol)) (define (tr-lines line in) (define (return line tree) (cons line (html:table :border 1 (reverse tree)))) (define terminate? (any-pred eof? p-sep? h*? hr? pre? pre-in? block-in? block-out? ul? ol?)) (define (line->tr line) (define (item->td item) (html:td (line->html item))) (let rec ((line line) (tree '())) (cond ((tbl? line) => (lambda (m) (rec (m 1) (cons (item->td (m 'after)) tree)))) (else (html:tr tree))))) (define (loop tree line) (cond ((terminate? line) (return line tree)) ((table? line) => (lambda (m) (loop (cons (line->tr (m 1)) tree) (read-line in)))) (else (return line tree)))) (loop '() line)) (define (line->html line) (define (trans tag fn n) (lambda (m) (list (line->html (m 'before)) (tag (fn (m n))) (line->html (m 'after))))) (cond ((escape? line) => (trans identity identity 1)) ((strong? line) => (trans html:strong line->html 1)) ((em? line) => (trans html:em line->html 1)) ((br? line) => (trans (lambda (_) (html:br)) identity 0)) ((wikiname? line) => (trans identity wikiname->link 1)) (else line))) ;;=============================== ;; command of generate page ;;=============================== (define (page . content) `(,(cgi-header :content-type #`"text/html; char-set=,(gauche-character-encoding)") ,(html:html (html:head (html:meta :http-equiv "Content-type" :content "text/html" :charset #`",(gauche-character-encoding)") (html:title *wiki*) (html:style :type "text/css" *style*)) (apply html:body content)))) (define (cmd-show-page ttl) ; (log-format *log* "*** cmd-show-page[~a] ***" ttl) (let* ((data (dbm-get (db) ttl (cons 0.0 ""))) (key (car data)) (article (cdr data))) (page (html:h1 (html-escape-string ttl)) (html:a :href #`"?t=,(uri-encode-string ttl)&c=e" "[編集]") (html:a :href #`"?t=,(uri-encode-string *wiki*)" "[トップ]") (wikiform->html article)))) (define (cmd-edit-page ttl) ; (log-format *log* "*** cmd-edit-page[~a] ***" ttl) (let* ((data (dbm-get (db) ttl (cons 0.0 ""))) (key (car data)) (article (cdr data))) (page (html:form :method "POST" (html:h1 (html-escape-string #`",|ttl|の編集")) (html:input :type "hidden" :name "c" :value "c") (html:input :type "hidden" :name "t" :value ttl) (html:input :type "hidden" :name "k" :value key) (html:p (html:input :type "submit" :name "commit" :value "変更") (html:input :type "submit" :name "preview" :value "プレビュー")) (html:p (html:textarea :rows 45 :cols 100 :name "a" (html-escape-string article))))))) (define (cmd-commit-page ttl art key) ; (log-format *log* "*** cmd-commit-page[~a] ***" ttl) (let* ((data (dbm-get (db) ttl (cons 0.0 ""))) (org-key (car data)) (org-art (cdr data))) (cond ((= key org-key) (begin (if (and art (not (string=? art ""))) (dbm-put! (db) ttl (cons (+ key 0.1) art)) (dbm-delete! (db) ttl)) (cgi-header :status "302 Moved" :location #`"?t=,(uri-encode-string ttl)"))) ((< key org-key) (cmd-collision-page ttl art key)) (else (cmd-illegal-error-page ttl))))) (define (cmd-preview-page ttl art key) ; (log-format *log* "*** cmd-preview-page[~a] ***" ttl) (page (html:h1 (html-escape-string ttl)) (html:blockquote :class "preview" (if art (wikiform->html art) (html:p))) (html:form :method "POST" (html:h2 (html-escape-string #`",|ttl|の編集")) (html:input :type "hidden" :name "c" :value "c") (html:input :type "hidden" :name "t" :value ttl) (html:input :type "hidden" :name "k" :value key) (html:p (html:input :type "submit" :name "commit" :value "変更") (html:input :type "submit" :name "preview" :value "プレビュー")) (html:p (html:textarea :rows 45 :cols 100 :name "a" (html-escape-string art)))))) (define (cmd-collision-page ttl art key) ; (log-format *log* "*** cmd-collision-page[~a] ***" ttl) (page (html:h1 (html-escape-string ttl)) (html:p "衝突を検出しました!"))) (define (cmd-illegal-error-page ttl) ; (log-format *log* "*** cmd-illegal-error-page[~a] ***" ttl) (page (html:h1 (html-escape-string ttl)) (html:p "本来ありえない異常なアクセスを検出しました!"))) ;;=============================== ;; main logic ;;=============================== (define (main args) (cgi-main (lambda (params) (let ((ttl (cgi-get-parameter "t" params :convert (cut ces-convert <> "*jp"))) (art (cgi-get-parameter "a" params :convert (cut ces-convert <> "*jp"))) (cmd (cgi-get-parameter "c" params)) (key (cgi-get-parameter "k" params :convert x->number)) (sbm (cgi-get-parameter "commit" params)) (prv (cgi-get-parameter "preview" params))) (with-db (db *db-file*) (if ttl (cond ((not cmd) (cmd-show-page ttl)) ((string=? cmd "e") (cmd-edit-page ttl)) ((string=? cmd "c") (cond (sbm (cmd-commit-page ttl art key)) (prv (cmd-preview-page ttl art key))))) (cmd-show-page *wiki*)))))))
db 周辺
最初 with-db じゃなくて、自前で実装した macro を使ってたんだけど、
それだと db を掴んだ状態で再帰的に db を open しようとするところが
あって(WiKiNameをみるところ)、ひたすらエラーと戦ってた。
で、ようやく parameter のありがたさが分かり、
Gauche:CGI:スケジュール予定表:Shiro版で解説された db まわりを
パクらせてもらって修正するところに落ち着いた。 とりあえず、WiLiKi同様の WiKiName のみをサポートした貧弱 WiKi ですけど。 Gauche:CGI:スケジュール予定表:Shiro版の構成を勉強したおかげで、 コード自体Gauche:CGI:スケジュール予定表を書いたときより ずっと綺麗に書けたと思う。
- db-try-open を導入。 wiliki/src/db.scm にあるものをほぼそのまま引っ張って来て使わせてもらっちゃう。cut-sea:2004/09/17 07:34:37 PDT
エスケープのポイント
Shiro (2004/09/03 19:14:47 PDT)現在のコードだと、ページタイトルに特殊文字が 入った場合がやばいですね。原則は次の通り。
- urlに任意の文字列が入る可能性のある箇所はuri-encodeする:wikiname->link等、 ページタイトルを埋め込んでいるところ。
- html:xxx の属性値ではなく内容に任意の文字列が入る可能性のある箇所は html-escapeする:cmd-edit-pageのhtml:h1の箇所
- ありがとうございます。
どうにも uri-encode を忘れてしまいます。 書いているとユーザ側から見てどうなるとか受け取ったときにどうなっているとか、 まだまだ頭がついていけないっす。 ってのは言い訳で完全に忘れてました。 (use rfc.uri) すらしてなかったです。 ちなみに cmd-edit-page の html:h1 の内容のところを html-escape すると エンコードされて表示されるので、これは違うかなと思ったんで、やってません。 例えば & とかの名称を含む WiKiName を入れたときに & とかに 置き換わって表示されちゃいます。これは & としたいかなと。cut-sea:2004/09/04 01:07:15 PDT
- Shiro あれ、なんでだろ。ttlにはescapeしてない文字列が渡って来てる んですよね。html:h1はcontentsの部分はescapeしないはずですし、 他にやってるところも見当たらないのに。"<tt>abc</tt>"みたいなタイトルの ページを作るとどうなりますか?
- <tt>こんなページ</tt> ってのを角括弧で囲ってWiKiNameにして使ってみると、
そのまま<tt>こんなページ</tt>っていうページタイトルで作成されますね。
<tt>こんなページ</tt>の編集とかもそのまま表示されてて、
中身を書き込んでやると<tt>こんなページ</tt>って部分がリンクになります。
他に = とか +%# なども使ってみましたが、そのまま表示されてますね。
確かに言われてみるとどの部分でエスケープ出来ているのか不思議ですね。
念のため最新コード張り付け直してみます。cut-sea:2004/09/04 04:08:04 PDT
今の TOP ページをブラウザの view source で見るとこんな感じです。</style ></head ><body><h1>MiniWiKi</h1 ><a href="?t=MiniWiKi&c=e">[編集]</a ><a href="?t=MiniWiKi">[トップ]</a
><p><a href="?t=MiniWiKi">MiniWiKi</a >へようこそ!<br />WiKi<a href="?t=WiKi&c=e">?</a >ですので気軽に書いてください。<br />このWiKi<a href="?t=WiKi&c=e">?</a >サイトはGauche<a href="?t=Gauche&c=e">?</a >という川合史朗氏の作成された<a href="?t=Scheme">Scheme</a >インタプリタにて実装しております。<br /><pre>このサイトの使い方<br />[編集]ボタンを押してください。<br />[トップ]を押してください。<br /><a href="?t=MiniWiKi">MiniWiKi</a >は<a href="?t=cut-sea">cut-sea</a >が作りました。</pre
><hr />個人のページ<br /><a href="?t=cut-sea">cut-sea</a ><br /><a href="?t=%26lt%3b%b0%d5%c3%cf%b0%ad%26gt%3b"><意地悪></a ><br /><a href="?t=%26lt%3bp%26gt%3b%26amp%3b%25%3f%2b%3d%23!%40%26lt%3b%2fp%26gt%3b"><p>&%?+=#!@</p></a ><br /><a href="?t=%26lt%3btt%26gt%3b%a4%b3%a4%f3%a4%ca%a5%da%a1%bc%a5%b8%26lt%3b%2ftt%26gt%3b"><tt>こんなページ</tt></a ><br /><br /><blockquote>この中がブロックになります。<br />わかるかな?Shiro<a href="?t=Shiro&c=e">?</a >さん<blockquote>入れ子です。しってますよね?<blockquote>入れ子は困る?そんなん言わないでよ。</blockquote >入れ子できまっせ。<a href="?t=cut-sea">cut-sea</a ><br /></blockquote > こんな感じでできますね。すばらしい!</blockquote ></p ></body ></html >
- Shiro: ってことは、内部でもHTMLエスケープした状態で扱ってる ってことですかね。blockquote構文もエスケープした状態でマッチかけてる みたいですし。完全にそれで統一するのならそれはそれで動くのですが、 内部ではHTMLエスケープは行わずに処理し、 外に出す直前にエスケープする、という方が間違いが起こりにくいと思います。 なお、ブラウザのview sourceは余分な処理が入ってる場合もあるので、 生データをチェックするには直接wgetやGaucheのhttp-get等でデータをフェッチ した方が良いです。また、ブラウザからcgiに送られているデータは、gauche.logger 等を使ってログファイルにダンプしてみると良くわかります。
- 実は自分でもポリシー決めてからエスケープしてるんじゃなくて、
書いている内にコード読みながら、ここんところでエスケープか?とかしながらでした。
確かに > の三連発で照合してて変な感じがしました。正直やりたくないです
もしかして別のところに html-escape が入ってしまっているためなんでしょうかね。
- えーと、gauche.logger を使ってみました。(初体験)
最初の方で (use gauche.logger)して、(define *log* (log-open "/home...."))
としておいて、さてどこで捕捉すれば良いのだろうと思ったのですが、
(define (wikiform->html text) (with-input-from-string text (lambda () (let loop ((line (read-line))) (log-format *log* "~a" line) ;;<=ここね (if (eof-object? line) "" (cons (wikiline->html line) (loop (read-line))))))))
これがCGIが最初に受け取っているところなので、これでアクセスしてみるとSep 4 20:59:19 wiki.scm[26466]: [[MiniWiKi]]へようこそ! Sep 4 20:59:19 wiki.scm[26466]: [[WiKi]]ですので気軽に書いてください。 Sep 4 20:59:19 wiki.scm[26466]: この[[WiKi]]サイトは[[Gauche]]という川合史朗氏の作成された[[Scheme]]インタプリタにて実装しております。 Sep 4 20:59:19 wiki.scm[26466]: このサイトの使い方 Sep 4 20:59:19 wiki.scm[26466]: ---- Sep 4 20:59:19 wiki.scm[26466]: 個人のページ Sep 4 20:59:19 wiki.scm[26466]: [[cut-sea]] Sep 4 20:59:19 wiki.scm[26466]: [[<意地悪>]] Sep 4 20:59:19 wiki.scm[26466]: [[<p>&%?+=#!@</p>]] Sep 4 20:59:19 wiki.scm[26466]: [[<tt>こんなページ</tt>]] Sep 4 20:59:19 wiki.scm[26466]: <<< Sep 4 20:59:19 wiki.scm[26466]: #<eof>
こうなります。と言うことでエスケープされた状態で(ブラウザからhttpd経由で) 受け取っているってことですよね? とすると、これをデコードしてやらにゃならんと思うのですが、html-lite には無いですよね?
- データベースにもこれで登録されちゃってますね。
ご推察の通り内部ではエスケープされた形でハンドリングしてますね。
gosh> (dbm-map db (lambda (k v) (print k " : " v))) <意地悪> : 意地悪なページです。 (snip) <p>&%?+=#!@</p> : へんてこな名前のページです。 [[<p>&%?+=#!@</p>]]って名前ですよ。 <tt>こんなページ</tt> : おっけーな感じか? (snip)
WiLiKiで試すと<tt>こんなページ</tt>ってそのままdbに格納されてたので やっぱりおかしい。
- なんとか修正完了。ようやく元の動作まで確認できました。cut-sea:2004/09/04 06:47:38 PDT
teranishi:line->htmlのcondは、=>を使えば正規表現のマッチングを一回で済ますことができます。
- リファレンスマニュアル参照…ふむふむ。おお、ありがとうございます。 なるほど、cond の => って使ったことなかったんで同じだと思ってました。 cond 節の辺がインデントがメチャメチャ汚いし、正規表現を二回もマッチさせるのも 能力の無駄使いなんで anaphoric cond とか検討しはじめてたとこだったりして。 (またもや車輪の再発明)cut-sea:2004/09/09 21:50:52 PDT
あと、'* ||- 1||# 2||'のように、本来混在できないものが処理されてしまうようなのですが、問題ないのでしょうか。
- すみません。この辺は、あまり真剣にチェックしてないです。(^^;
一応表の中のWiKiName位は解釈してほしいと思ってたんで recursive に line->html を呼んで、とりあえずなんでも突っ込めるようにしちゃってますねぇ。
HTML の構成として入れて良いかどうかよく知らずにいるので、 出来るものはなんでも取りこみたい方針で、まずは作り込んでます。 許されないものなら ctx あたりでもって除外する必要ありです。 他にも似たようなのはいっぱいありそうですが、 一応ダメなのは気付いたところで随時 ctx で切り分けようと思ってます。cut-sea:2004/09/09 21:50:52 PDT - やっぱり、WiKiName とかだけ変換する関数line->hogeを作った方がいいのかなぁ。cut-sea:2004/09/10 04:44:50 PDT
- 私は逆に、WiKiNameとかだけ変換するようにline->htmlを書き換えるべきだと思っています。
ul,ol,tableはそれぞれul-lines,ol-lines,table-lines内で処理すべきだし、 (そうしないと<table>がないのに<tr>が現れたりします)
hr,h2,h3,h4はブロックレベル要素なのでform->html内で処理すべきです。 (ブロックレベル要素についてはHTMLの仕様を参照)teranishi:2004/09/13 20:18:56 PDT - どうも。ブロック要素とインライン要素ってやつですね。 実はhtml:pの扱いに詰まって、この辺もっかい作り直しかなぁと思い始めてました。 結構解説しているサイトもあるようなので、この辺整理してから出直して来ます。cut-sea:2004/09/14 04:59:13 PDT
- 表の中にはほぼ何でも入れられるみたいだけど、ここではインラインのみにした。 一応ブロック/インライン要素を考えて構成し直したつもり。cut-sea:2004/09/17 07:34:37 PDT
teranishi: 個人的な趣味ですが、orsよりGaucheRefj:any-predを使いたいし、 line->htmlでは関数を2つ渡さずにGaucheRefj:composeを使いたいですね。 遅くなるかもしれませんが。
- ありがとうございます。
趣味の問題というより私が無知なだけかと。(^,^;
とりあえず、ors => any-pred を使う様にしてみました。 おかげでカッコも少し減りました。 compose はいずれにせよ二つ渡さざるを得ないかなぁと思ったんでそのままですが。cut-sea:2004/09/21 04:40:22 PDT