OSDN Git Service

61ebef0e44c620194540e5b44a9bbac3e1be13ff
[feedblog/feedgenerator.git] / feedgenerator.rb
1 #!/usr/local/bin/ruby
2 # -*- coding: utf-8 -*-
3 #
4 #= Atom Feed 1.0を管理するWEBアプリケーション
5 #
6 #Autohr::    Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
7 #Version::   2.0.0.0
8 #Copyright:: Copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
9 #License::   GPLv3
10
11 require "cgi"
12 require "cgi/session"
13 require "erb"
14 require "rexml/document"
15 require "pstore"
16 require 'time'
17 require "date"
18 require "fileutils"
19
20 # ログインID
21 LOGINID = "login"
22 # ログインパスワード
23 PASSWORD = "password"
24 # インターフェースのテーブルの幅
25 TABLEWIDTH = 800
26 # XMLファイル格納先までの相対パス
27 XMLPATH = "./../lunardial/xml/"
28 # loglist.xmlファイルの定義
29 LISTXMLPATH = "#{XMLPATH}loglist.xml"
30 # FeedBlog上の表示ページからログ格納ディレクトリまでのパス
31 FEEDXMLDIR = "./xml/"
32 # デバッガモード
33 DEBUG = false
34 # ファイルマネージャー機能を使用するならtrue
35 USEFILEMANAGER = true
36 # ファイルマネージャー機能スクリプト(filemanager.rb)のパス
37 FILEMANAGER = "./filemanager.rb"
38 # XMLに書き込む際、改行部分を<br>のまま保持するか、改行記号に直すか
39 REPLACEBRTAG = false
40
41 # バージョン情報を示す文字列です
42 APPVERSION = "- FeedGenerator for Ruby version 2.0.0.0 -<br>Copyright(c) 2009 Kureha.H (<a href=\"http://lunardial.sakura.ne.jp/\" target=\"_blank\">http://lunardial.sakura.ne.jp/</a>) & Yui Naruse (<a href=\"http://airemix.com/\" target=\"_blank\">http://airemix.com/</a>)"
43 # タイトル領域に表示される文字列です
44 APPTITLE = "FeedGenerator for Ruby version 2.0.0.0"
45
46 # = Objectクラス
47 #
48 # 基本クラスのオーバーライドを行います
49 class Object
50   # myopenメソッド
51   #
52   # ruby-1.9.x以降ではファイルを開いた際、エンコードの指定を行わないとエラーの原因になります。
53   # ただしruby-1.8.6以前はエンコードの指定に対応していないため、独自メソッドを定義してファイルの入出力を行います。
54   #
55   # _arg[0]_ :: 入出力を行うファイルのパス
56   # _arg[1]_ :: モードの指定。例 : w:utf-8(書き込みモード・UTF-8エンコードでファイルを開く)
57   def myopen(*arg)
58     mode = arg[1]
59     arg[1] = mode[/[^:]+/] if mode && RUBY_VERSION < "1.8.7" && mode.include?(':')
60     open(*arg) do |f|
61       f.flock(/\A\w+r/ =~ mode ? File::LOCK_SH : File::LOCK_EX)
62       return yield(f)
63     end
64   end
65 end
66
67 class NilClass
68   def blank?
69     nil?
70   end
71 end
72
73 class Array
74   def blank?
75     empty?
76   end
77 end
78
79 class String
80   def blank?
81     empty?
82   end
83 end
84
85 # = Feed/Entryのスーパークラス
86
87 # 入出力用のメソッドなど、共通する機能を提供します
88 class AbstractEntry
89   # 初期化メソッドです
90   #
91   # _hash_ :: 値を格納したhash配列。superfeed.new(CGI::Session.new(new CGI).params)のようにして使う。
92   def initialize(hash)
93     # 内部データ保持用のハッシュ配列です
94     @attr = {}
95     
96     # 引数を格納します
97     @paramlist.each do |key|
98       val = hash[key.to_sym] || hash[key.to_s]
99       if val
100         # val.strip!
101         val.gsub!(/\r\n|\r/, "\n")
102         @attr[key.to_sym] = CGI.escapeHTML(val)
103       else
104         # 空の場合の対策
105         @attr[key.to_sym] = ""
106       end
107     end
108     
109     # 可視・不可視を示すハッシュキーを格納する配列です
110     @display = {}
111     
112     # ハッシュキーの日本語説明を格納する配列です
113     @name = []
114   end
115   
116   # Accessor
117   attr_reader :attr, :paramlist, :name, :display
118   
119   # 内部の@attrハッシュにアクセスする手段を提供するメソッドです
120   #
121   # AbstractEntry.attr[:title]にアクセスしたい場合はAbstractEntry.titleで可能になります。
122   # AbstractEntry.send("title")という方法でもアクセス可能です。
123   def method_missing(methname, *args)
124     methname = methname.to_s
125     
126     if methname[-1] == ?=
127       # setter
128       raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" unless args.length == 1
129       methname.chop!
130       methname = @paramlist.find{|par|par == methname}
131       return @attr[methname.to_sym] = args[0] if methname
132     else
133       # getter
134       raise ArgumentError, "wrong number of arguments (#{args.length} for 0)" unless args.empty?
135       return @attr[methname.to_sym] if @attr.key?(methname.to_sym)
136     end
137     # attr上にキーがない値を入れようとした場合はNoMethodError
138     raise NoMethodError
139   end
140   
141   def [](key)
142     @attr[key.to_sym]
143   end
144   
145   def []=(key, value)
146     @attr[key.to_sym] = value
147   end
148 end
149
150 # = Feedクラス
151 #
152 # Feedの基礎情報を保有するクラスです
153 class Feed < AbstractEntry
154   # 初期化メソッドです
155   #
156   # _hash_ :: 値を格納したhash配列。feed.new(CGI::Session.new(new CGI).params)のようにして使います。
157   def initialize(hash)
158     # 内部データ保持用のハッシュ配列です
159     @attr = {}
160     
161     # 内部データの項目名を格納する配列です
162     @paramlist = ["feedattr", "title", "subtitle", "self", "url", "updated", "feedid", "rights", "aname", "amail", "others"]
163     
164     # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
165     super(hash)
166     
167     # 可視・不可視を示すハッシュキーを格納する配列です
168     @display = {"feedattr" => "none", "title" => "", "subtitle" => "", 
169       "self" => "", "url" => "", 
170       "updated" => "none", "feedid" => "", 
171       "rights" => "", "aname" => "", 
172       "amail" => "", "others" => "none"}
173     
174     # デバッグモードの場合、全ての入力要素を表示します
175     if DEBUG == true
176       @display.each do |key, val|
177         @display[key] = ""
178       end
179     end
180     
181     # ハッシュキーの日本語説明を格納する配列です
182     @name = {"feedattr" => "feedの保持している属性", "title" => "ウェブページのタイトル", "subtitle" => "ウェブページの簡単な説明", 
183       "self" => "このXMLファイルのURL", "url" => "ウェブページのURL", 
184       "updated" => "XMLファイルの最終更新日", "feedid" => "あなたのページ独自のID", 
185       "rights" => "ページの著作権表記", "aname" => "ページの製作者", 
186       "amail" => "ページ製作者のメールアドレス", "others" => "その他の要素"}
187     
188   end
189   
190   # Atom XMLファイルを読み込んで解析し、等価なFeedオブジェクトを返却するメソッドです
191   #
192   # _path_ :: Atom XMLファイルのパス
193   def self.readxml(path)
194     
195     # ファイルを読み込みます
196     doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
197     xml = {}
198     others = []
199     
200     # Feedの属性値を取得します
201     xmlattr = []
202     doc.elements["feed"].attributes.each_attribute do |attr|
203       xmlattr.push("#{attr.to_string} ")
204     end
205     xml[:feedattr] = xmlattr.join("\n").gsub(/"/, "'")
206     
207     # XML解析部分です。各element毎に判定を行います
208     doc.elements.each("feed") do |elm|
209       elm.elements.each { |child|
210         begin
211           case child.name
212             when "id"
213             xml[:feedid] = child.text
214             when "title", "subtitle", "updated", "rights"
215             xml[child.name.to_sym] = child.text
216             when "author"
217             child.elements.each do |gchild|
218               case gchild.name
219                 when "name"
220                 xml[:aname] = gchild.text
221                 when "email"
222                 xml[:amail] = gchild.text
223               end
224             end
225             when "link"
226             child.attributes.each do |k, v|
227               if k == "rel"
228                 if v == "self"
229                   xml[:self] = child.attributes["href"]
230                 elsif v == "alternate"
231                   xml[:url] = child.attributes["href"]
232                 end
233               end
234             end
235             when "entry"
236             # Entry要素は無視します
237           else
238             # 上記判定以外の全要素は配列に格納します
239             others.push(child.to_s)
240           end
241         rescue NoMethodError
242         end
243         
244       }
245     end
246     
247     # Others要素を結合して代入します
248     xml[:others] = others.join("\n")
249     feed = Feed.new(xml)
250     return feed
251   end
252   
253   # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
254   def to_s
255     buf = []
256     
257     buf.push("<feed #{@attr[:feedattr]}>")
258     buf.push("<title type=\"text\">#{@attr[:title]}</title>")
259     buf.push("<subtitle type=\"text\">#{@attr[:subtitle]}</subtitle>")
260     buf.push("<link rel=\"self\" type=\"application/atom+xml\" href=\"#{@attr[:self]}\" />")
261     buf.push("<link rel=\"alternate\" type=\"text/html\" href=\"#{@attr[:url]}\" />")
262     buf.push("<updated>#{Time.now.iso8601}</updated>")
263     buf.push("<id>#{@attr[:feedid]}</id>")
264     buf.push("<rights type=\"text\">#{@attr[:rights]}</rights>")
265     buf.push("<author>")
266     buf.push("\t<name>#{@attr[:aname]}</name>")
267     buf.push("\t<email>#{@attr[:amail]}</email>")
268     buf.push("</author>")
269     buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
270     
271     return buf.join("\n")
272   end
273   
274   # Feed情報更新メソッド
275   def self.update(path, feed)
276     entrylist = Entry.readxml(path)
277     Feed.to_xml(path, feed, entrylist)
278     
279     true
280   end
281   
282   # XMLファイル出力用メソッドです
283   #
284   # _feed_ :: Feedオブジェクト
285   # _entry_ :: Entryオブジェクトの配列
286   def self.to_xml(path, feed, entrylist)
287     buf = []
288     buf.push("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
289     buf.push("#{feed.to_s}\n")
290     entrylist.each { |entry|
291       buf.push("#{entry.to_s}\n")
292     }
293     buf.push("</feed>")
294     
295     myopen(path, "w") do |f|
296       f.print buf.join("\n")
297     end
298   end
299   
300 end
301
302 # = Entryクラス
303 #
304 # Entryの基礎情報を保有するクラスです
305 class Entry < AbstractEntry
306   # 初期化メソッドです
307   #
308   # _hash_ :: 値を格納したhash配列。entry.new(CGI::Session.new(new CGI).params)のようにして使います。
309   def initialize(hash)
310     # 内部データ保持用のハッシュ配列です
311     @attr = {}
312     
313     # 内部データの項目名を格納する配列です
314     @paramlist = ["entryid", "title", "summary", "published", "updated", "url", "content", "others"]
315     
316     # AbstractEntryのinitializeメソッドを呼び出し、値を@attr配列に格納します
317     super(hash)
318     
319     # 可視・不可視を示すハッシュキーを格納する配列です
320     @display = {"entryid" => "none", "title" => "",
321       "summary" => "", "published" => "none",
322       "updated" => "none", "url" => "none",
323       "content" => "", "others"=>"none"}
324     
325     # デバッグモードの場合、全ての入力要素を表示します
326     if DEBUG == true
327       @display.each do |key, val|
328         @display[key] = ""
329       end
330     end
331     
332     # ハッシュキーの日本語説明を格納する配列です
333     @name = {"entryid" => "記事固有のID", "title" => "記事のタイトル",
334       "summary" => "記事の簡単な説明", "published" => "記事の出版時刻",
335       "updated" => "記事の更新時刻", "url" => "記事へのURLアドレス",
336       "content" => "記事の本文", "others"=>"その他の項目"}
337   end
338   
339   # Atom XMLファイルを読み込んで解析し、等価なEntryオブジェクト配列を返却するメソッドです
340   #
341   # _path_ :: Atom XMLファイルのパス
342   def self.readxml(path)
343     
344     # ファイルを読み込みます
345     doc = REXML::Document.new(myopen(path, "r:utf-8"){|f|f.read})
346     entrylist = []
347     xml = {}
348     
349     # XML解析部分です。各element毎に判定を行います
350     doc.elements.each("feed/entry") do |elm|
351       xml = {}
352       others = []
353       elm.elements.each do |child|
354         begin
355           case child.name
356             when "id"
357             xml[:entryid] = child.text
358             when "link"
359             xml[:url] = child.attributes["href"]
360             when "title", "summary", "summary", "published", "updated", "content"
361             xml[child.name.to_sym] = child.text
362           else
363             # 上記判定以外の全要素は配列に格納します
364             others.push(child.to_s)
365           end
366         rescue NoMethodError
367         end
368       end
369       # Others要素を結合して代入します
370       xml[:others] = others.join("\n")
371       entrylist.push(Entry.new(xml))
372     end
373     
374     return entrylist
375   end
376   
377   # Entry挿入メソッド
378   def self.insert(path, entry)
379     feed = Feed.readxml(path)
380     entrylist = Entry.readxml(path)
381     entrylist.unshift entry
382     
383     Feed.to_xml(path, feed, entrylist)
384     
385     true
386   end
387   
388   # Entry更新メソッド
389   def self.update(path, entry)
390     feed = Feed.readxml(path)
391     entrylist = Entry.readxml(path)
392     
393     successed = false
394     entrylist.each_with_index { |e, i|
395       if e.entryid == entry.entryid
396         entrylist[i] = entry 
397         successed = true
398       end
399     }
400     Feed.to_xml(path, feed, entrylist) if successed
401     
402     successed
403   end
404   
405   # Entryロードメソッド
406   def self.select(path, entryid)
407     feed = Feed.readxml(path)
408     entrylist = Entry.readxml(path)
409     
410     entry = nil
411     entrylist.each_with_index { |e, i|
412       entry = entrylist[i].dup if e.entryid == entryid
413     }
414     
415     entry
416   end
417   
418   # Entry消去メソッド
419   def self.delete(path, entryid)
420     feed = Feed.readxml(path)
421     entrylist = Entry.readxml(path)
422     
423     successed = false
424     delete_index = -1
425     entrylist.each_with_index { |e, i|
426       if e.entryid == entryid
427         delete_index = i
428         successed = true
429       end
430     }
431     
432     if successed
433       entrylist.delete_at delete_index
434       Feed.to_xml(path, feed, entrylist)
435     end
436     
437     successed
438   end
439   
440   # データソースから読み取ったHTMLを、エディタで編集可能な形式に変換するメソッドです
441   def content_for_generator
442     str = @attr[:content].dup
443     # str.strip!
444     str.gsub!(/(&lt;\/(?:p|h\d|div)(?:&gt;|>))\n/i, '\1')
445     str.gsub!(/\n/, '&lt;br&gt;') if REPLACEBRTAG
446     str.gsub!(/(&lt;(?:(?!&gt;).)*?)#{Regexp.escape(FEEDXMLDIR)}/) { "#$1#{XMLPATH}" }
447     str
448   end
449   
450   # エディタで編集されたCONTENT要素を、データソースに書き込める形式に変換するメソッドです
451   def content_for_blog
452     str = @attr[:content].dup
453     str = CGI.unescapeHTML(str)
454     # str.strip!
455     str.gsub!(/(\r\n|\n)/, "")
456     str.gsub!(/<br>/i, "\n") if REPLACEBRTAG
457     str.gsub!(/(<br>|<\/p>|<\/h\d>|<\/div>)(?=[^\n])/i) { "#$1\n" } unless REPLACEBRTAG
458     str.gsub!(/(<[^>]*?)#{Regexp.escape(XMLPATH)}/) { "#$1#{FEEDXMLDIR}" }
459     CGI.escapeHTML(str)
460   end
461   
462   # 確認画面で表示されるCONTENT要素を生成するメソッドです
463   def content_for_view
464     str = @attr[:content].dup
465     str = CGI.unescapeHTML(str)
466     # str.strip!
467     str.gsub!(/<br>/i, "\n") if REPLACEBRTAG
468     str.gsub!(/(<[^>]*?)#{Regexp.escape(FEEDXMLDIR)}/) { "#$1#{XMLPATH}" }
469     str
470   end
471   
472   # 内部に保持している情報を、Atom Feed1.0形式の文字列に出力するメソッドです
473   def to_s
474     buf = []
475     buf.push("<entry>")
476     buf.push("<id>#{@attr[:entryid]}</id>")
477     buf.push("<title>#{@attr[:title]}</title>")
478     buf.push("<summary>#{@attr[:summary]}</summary>")
479     buf.push("<published>#{@attr[:published]}</published>")
480     buf.push("<updated>#{@attr[:updated]}</updated>")
481     buf.push("<link href=\"#{@attr[:url]}\" />")
482     buf.push("<content type=\"html\">#{@attr[:content]}</content>")
483     buf.push("#{CGI.unescapeHTML(@attr[:others])}") if @attr[:others] != ""
484     buf.push("</entry>")
485     
486     return buf.join("\n")
487   end
488 end
489
490 # = HtmlWriterクラス
491
492 # テンプレートファイル(*.erb)を読み込み、管理するクラスです
493 class HtmlWriter
494   # 初期化メソッドです
495   #
496   # _template_ :: テンプレートファイル(*.erb)のパス
497   # _binding_ :: binding変数
498   def initialize(template, binding)
499     @erb = ERB.new(myopen(template, "r:utf-8") {|f| f.read}, nil, "-")
500     @binding = binding
501   end
502   
503   # テンプレートファイルの文字列を返却するメソッドです
504   def to_code
505     @erb.result(@binding)
506   end
507 end
508
509 # = LogListクラス
510 #
511 # loglist.xmlにかかわる入出力を行います
512 class LogList
513   # 初期化メソッドです
514   #
515   # _display_ :: 画面表示用文字の配列
516   # _path_ :: XMLファイルパスの配列
517   # _logpath_ :: loglist.xmlのパス
518   def initialize(display, path, logpath)
519     @display = display
520     @path = path
521     @logpath = logpath
522   end
523   
524   attr_accessor :display, :path, :logpath
525   
526   # loglist.xmlファイルを読み込んで解析し、LogListオブジェクトを返却するメソッドです
527   #
528   # _logpath_ :: loglist.xmlへのパス
529   def LogList.readxml(logpath)
530     
531     # ファイルを読み込みます
532     lines = []
533     myopen(logpath, "r:utf-8") { |f|
534       lines = f.readlines
535     }
536     
537     doc = REXML::Document.new(lines.join("\n"))
538     @display = []
539     @path = []
540     
541     doc.elements.each("list/file") { |elm|
542       elm.elements.each {|child|
543         case child.name
544           when "display"
545           @display.push(child.text)
546           when "path"
547           @path.push(child.text)
548         else 
549           # With no action
550         end
551       }
552     }
553     
554     return LogList.new(@display, @path, logpath)
555   end
556   
557   # loglist.xmlにオブジェクトの内容を反映するメソッドです
558   def to_xml
559     buf = []
560     buf.push("<list>")
561     path.each_with_index do |path, i|
562       buf.push("<file>")
563       buf.push("<display>#{@display[i]}</display>")
564       buf.push("<path>#{@path[i]}</path>")
565       buf.push("</file>")
566     end
567     buf.push("</list>")
568     
569     myopen(@logpath, "w") do |f|
570       f.print buf.join("\n")
571     end
572   end
573   
574 end
575
576 # = FileUploaderクラス
577 #
578 # 画像の管理を行うクラスです
579 class FileUploader
580   # 初期化メソッドです
581   def initialize
582   end
583   
584   # ファイルの一覧を取得し、ファイル名称を格納した配列を返却します
585   def filelist
586     arr = Dir::entries(XMLPATH).sort
587     arr.delete(".")
588     arr.delete("..")
589     
590     return arr
591   end
592   
593   # ファイルの消去を実行します
594   #
595   # _name_ :: 消去するファイル名
596   def delete(name)
597     File.delete(XMLPATH + name.match(/[^\/]*?$/).to_a[0])
598   end
599   
600   # ファイルのアップロードを実行します
601   #
602   # _name_ :: アップロードファイルの名称
603   # _img_ :: アップロードファイル
604   def upload(name, img)
605     open(XMLPATH + File.basename(name), "w") do |f|
606       f.binmode
607       f.write img
608     end
609   end
610 end
611
612 # = Controllerクラス
613 #
614 # コントローラ部分に相当する処理を受け持つクラスです
615 class Controller
616   def Controller.NormalForm(cgi, session, params, db)
617     db.transaction do
618       # modeとactionの相関図は以下のようになります。
619       # [mode] + [action]
620       #        + [action]
621       #        + [action]
622       #        + ... and more
623       case params["mode"]
624         # ログ選択画面
625         when "logselect"
626         session["filepath"] = params["logpath"]
627         db["loglist"] = LogList.readxml(LISTXMLPATH)
628         # 初期状態で選択されるログは「loglist.xml」の最上に位置するログになります
629         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
630         db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
631         db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
632         
633         # 新規記事追加部分
634         when "newentry"
635         case params["action"]
636           # 確認画面
637           when "confirm"
638           db["newentry"] = Entry.new(params)
639           db["newentry"].content = db["newentry"].content_for_blog
640           # 記事の追記を実際にファイルに反映
641           when "exec"
642           successed = Entry.insert(XMLPATH + File.basename(session["filepath"]), Entry.new(params))
643           unless successed
644             db["error"] = "日記の新規追加に失敗しました。" 
645             params["mode"] = "error"
646           end
647           # 画面を戻った際の処理
648           when "back"
649           db["newentry"] = Entry.new(params)
650         else 
651           # New Diary - Default
652           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
653           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
654         end
655         
656         # 記事編集部分
657         when "editentry"
658         case params["action"]
659           # 編集画面
660           when "edit"
661           session["editid"] = cgi["editid"].to_s
662           db["editentry"] = Entry.select(XMLPATH + File.basename(session["filepath"]), session["editid"])
663           # 確認画面
664           when "confirm"
665           session["editid"] = cgi["editid"].to_s
666           db["editentry"] = Entry.new(params)
667           db["editentry"].content = db["editentry"].content_for_blog
668           # 記事の変更を実際にファイルに反映
669           when "exec"
670           successed = Entry.update(XMLPATH + File.basename(session["filepath"]), Entry.new(params))
671           unless successed
672             db["error"] = "日記の編集処理に失敗しました。<br>該当の日記が既に存在しない可能性があります。" 
673             params["mode"] = "error"
674           end
675           when "back"
676           db["editentry"] = Entry.new(params)
677         else
678           # Edit Diary - Default
679           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
680           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
681         end
682         
683         # 記事削除部分
684         when "delentry"
685         case params["action"]
686           # 確認画面
687           when "confirm"
688           session["delid"] = cgi["delid"].to_s
689           # 記事の削除を実際にファイルに反映
690           when "exec"
691           successed = Entry.delete(XMLPATH + File.basename(session["filepath"]), cgi["delid"].to_s)
692           unless successed
693             db["error"] = "日記の編集処理に失敗しました。<br>該当の日記が既に存在しない可能性があります。" 
694             params["mode"] = "error"
695           end
696         else
697           # Delete Diary - Default
698           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
699           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
700         end
701         
702         # Feed情報変更部分
703         when "editfeed"
704         case params["action"]
705           # 確認画面
706           when "confirm"
707           db["feed"] = Feed.new(params)
708           # 実際にFeed情報の変更をファイルに反映
709           when "back"
710           db["feed"] = Feed.new(params)
711           when "exec"
712           Feed.update(XMLPATH + File.basename(session["filepath"]), Feed.new(params))
713         else
714           
715         end
716         
717         # ログ編集モード
718         when "log"
719         # 必ず内部データをリフレッシュする
720         db["loglist"] = LogList.readxml(LISTXMLPATH)
721         case params["action"]
722           # ログファイルの編集を実際にファイルに反映
723           when "addexec"
724           # エラーチェック。この段階のエラーは強度のエラーを発する。
725           db["loglist"].path.each do |val|
726             if val == cgi["logpath"]
727               # 重複していた場合エラーメッセージを表示し、処理を行わない
728               params["action"] = ""
729               params["mode"] = "error"
730               db["error"] = "ログファイルの追加中に重大なエラーが発生しました。<br>環境を見直してください。"
731               return
732             end
733           end
734           
735           # 入力された内容を保持する配列に追加
736           db["loglist"].path[1, 0] = cgi["logpath"]
737           db["loglist"].display[1, 0] = cgi["logdisplay"]
738           db["loglist"].to_xml
739           # 既存のdiary.xmlを指定された名称でコピーして保存
740           FileUtils.copy_file(XMLPATH + File.basename(db["loglist"].path[0]), XMLPATH + File.basename(db["logpath"]), true)
741           # 保持している情報を更新する
742           db["loglist"] = LogList.readxml(LISTXMLPATH)
743           db["entry"] = []
744           # 新たなdiary.xmlを生成。この際保持する情報は同一のFeedオブジェクトnew()
745           Feed.to_xml(XMLPATH + File.basename(db["loglist"].path[0]), db["feed"], [])
746           # 確認画面
747           when "addconfirm"
748           # 入力されたログが既に存在するかを確認
749           db["loglist"].path.each do |val|
750             if val == cgi["logpath"]
751               # 重複していた場合エラーメッセージを表示し、処理を行わない
752               params["action"] = ""
753               db["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
754               return
755             end
756           end
757           
758           db["logpath"] = cgi["logpath"]
759           db["logdisplay"] = cgi["logdisplay"]
760           
761           if db["logpath"].blank? || db["logdisplay"].blank?
762             params["action"] = ""
763           end
764           when "back"
765           
766           # 削除確認画面
767           when "delconfirm"
768           db["logdelindex"] = params["logdelindex"].to_i
769           
770           if db["logdelindex"] < 1
771             db["error"] = "<span style='color: #ff0000'>ログファイルの削除パラメタが不正です。</span><br>"
772             params["action"] = ""
773           end
774           # 削除処理
775           when "delexec"
776           if cgi["logdelindex"].to_i < 1
777             params["action"] = ""
778             params["mode"] = "error"
779             db["error"] = "ログファイルの削除中に重大なエラーが発生しました。<br>環境を見直してください。"
780             return
781           else
782             # 記事ファイルを削除します
783             File.delete(XMLPATH + File.basename(db["loglist"].path[db["logdelindex"]]))
784             # ログリストから削除します
785             db["loglist"].path.delete_at(cgi["logdelindex"].to_i)
786             db["loglist"].display.delete_at(cgi["logdelindex"].to_i)
787             db["loglist"].to_xml
788             # 保持している情報を更新する
789             db["loglist"] = LogList.readxml(LISTXMLPATH)
790             db["entry"] = []
791           end
792           
793           # 編集画面
794           when "edit"
795           db["logeditindex"] = params["logdelindex"].to_i
796           
797           db["logpath"] = db["loglist"].path[db["logeditindex"]]
798           db["logdisplay"] = db["loglist"].display[db["logeditindex"]]
799           
800           if db["logeditindex"] == 0
801             db["error"] = "<span style='color: #ff0000'>ログファイルの編集パラメタが不正です。</span><br>"
802             params["action"] = ""
803           end
804           # 編集確認画面
805           when "editconfirm"
806           checkflag = true
807           db["loglist"].path.each_with_index do |val, i|
808             if db["logeditindex"] != i
809               if params["logpath"].to_s == db["loglist"].path[i].to_s
810                 checkflag = false
811               end
812             end
813           end
814           
815           if checkflag == false
816             params["action"] = "edit"
817             db["error"] = "<span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
818           else
819             db["loginsertindex"] = params["loginsertindex"].to_i
820             
821             db["logpath"] = params["logpath"].to_s
822             db["logdisplay"] = params["logdisplay"].to_s
823           end
824           # 編集実行
825           when "editexec"
826           checkflag = true
827           db["loglist"].path.each_with_index do |val, i|
828             if db["logeditindex"] != i
829               if params["logpath"].to_s == db["loglist"].path[i].to_s
830                 checkflag = false
831               end
832             end
833           end
834           
835           if checkflag == false
836             params["action"] = ""
837             params["mode"] = "error"
838             db["error"] = "ログファイルの編集中に重大なエラーが発生しました。<br>環境を見直してください。"
839             return
840           else
841             db["loginsertindex"] = params["loginsertindex"].to_i
842             
843             db["logpath"] = params["logpath"].to_s
844             db["logdisplay"] = params["logdisplay"].to_s
845             
846             # ファイルを移動します
847             if XMLPATH + File.basename(db["loglist"].path[db["logeditindex"]]) != XMLPATH + File.basename(db["logpath"])
848               FileUtils.move(XMLPATH + File.basename(db["loglist"].path[db["logeditindex"]]), XMLPATH + File.basename(db["logpath"]))
849               # ログリストを更新します
850               db["loglist"].path.delete_at(db["logeditindex"])
851               db["loglist"].display.delete_at(db["logeditindex"])
852               db["loglist"].path.insert(db["loginsertindex"] + 1, db["logpath"])
853               db["loglist"].display.insert(db["loginsertindex"] + 1, db["logdisplay"])
854               db["loglist"].to_xml
855             end
856           end
857           
858           # 初期表示画面
859         else
860           # 現在編集中のデータを強制的に最上のファイルパスに変更
861           session["filepath"] = db["loglist"].path[0]
862           
863           # 前月の時刻を作成します
864           prevmonth = (DateTime.now << 1)
865           
866           # 前月の時刻を元に、ディフォルト書式を生成します
867           db["logpath"] = FEEDXMLDIR + prevmonth.strftime("%Y%m") + ".xml"
868           db["logdisplay"] = prevmonth.strftime("%Y年%m月").gsub("年0", "年")
869         end
870         
871         # インポートモードの場合の処理
872         when "import"
873         db["loglist"] = LogList.readxml(LISTXMLPATH)
874         
875         # リセットモードの場合の処理
876         when "reset"
877         case params["action"]
878           # リセット実行時の処理
879           when "exec"
880           file = FileUploader.new
881           file.filelist().each { |fname|
882             file.delete(fname) if File.ftype(XMLPATH + fname) == "file"
883           }
884           
885           # 全ファイルの初期化を実行する
886           # loglist.xmlの初期化
887           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
888           loglist.to_xml
889           
890           db["loglist"] = LogList.readxml(LISTXMLPATH)
891           
892           # diary.xmlの初期化
893           Feed.to_xml(XMLPATH + db["loglist"].path[0].match(/[^\/]*?$/).to_a[0], Feed.new({}), [])
894           
895           # 初期の編集ファイルはloglist.xml上の最も上のファイル
896           session["filepath"] = db["loglist"].path[0]
897           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
898           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
899           
900           # 画面の遷移先をトップページにする
901           params["mode"] = ""
902           params["action"] = ""
903           # パラメタ無しの処理
904         else
905           # ファイル一覧を取得する
906           filelist = FileUploader.new.filelist()
907           
908           # タイプがファイルのみリストとして取得する
909           db["filelist"] = []
910           filelist.each { |fname| db["filelist"] << fname if File.ftype(XMLPATH + fname) == "file"}
911         end
912         
913         # ログイン時の画面
914       else
915         # loglist.xmlが存在するかチェック
916         if File.exist?(LISTXMLPATH) == false
917           # なかった場合はloglist.xmlを自動生成
918           loglist = LogList.new(["最新の記事"], ["#{FEEDXMLDIR}diary.xml"], LISTXMLPATH)
919           loglist.to_xml
920           Feed.to_xml(XMLPATH + File.basename(loglist.path[0]), Feed.new({}), [])
921         end
922         
923         db["loglist"] = LogList.readxml(LISTXMLPATH)
924         
925         # diary.xmlが存在するかチェック
926         if File.exist?(XMLPATH + File.basename(db["loglist"].path[0])) == false
927           # なかった場合はdiary.xmlを自動生成
928           Feed.to_xml(XMLPATH + File.basename(db["loglist"].path[0]), Feed.new({}), [])
929         end
930         
931         # 初期の編集ファイルはloglist.xml上の最も上のファイル
932         session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
933         db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
934         db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
935       end
936     end
937   end
938   
939   # マルチパートフォームの場合の処理です
940   def Controller.MultiForm(cgi, session, params, db)
941     db.transaction do
942       
943       # loglist.xmlをロードします
944       db["loglist"] = LogList.readxml(LISTXMLPATH)
945       
946       case params["mode"]
947         # 特定位置に挿入する場合の処理
948         when "insert"
949         case params["action"]
950           when "exec"
951           # この段階のエラーに対しては強度のエラーを発する
952           checkflag = true
953           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
954           db["loglist"].path.each do |val|
955             if val == db["logpath"]
956               # 重複していた場合エラーメッセージを表示し、処理を行わない
957               db["logpath"] = ""
958               params["action"] = ""
959               params["mode"] = "error"
960               db["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
961               return
962             end
963           end
964           
965           db["loginsertindex"] = cgi["loginsertindex"].read.to_i
966           db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
967           
968           # 0位置への挿入防止
969           if db["loginsertindex"] == 0
970             params["action"] = ""
971             params["mode"] = "error"
972             db["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
973             return
974           end
975           
976           if File.basename(db["logpath"]).blank? || db["logdisplay"].blank?
977             params["action"] = ""
978             params["mode"] = "error"
979             db["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
980             return
981           end
982           
983           # ファイルを実際にアップロードします
984           file = FileUploader.new
985           file.upload(db["logpath"], db["importxml"])
986           
987           # loglist.xmlを更新します
988           db["loglist"].path[db["loginsertindex"], 0] = db["logpath"]
989           db["loglist"].display[db["loginsertindex"], 0] = db["logdisplay"]
990           db["loglist"].to_xml
991           
992           # 情報を更新します
993           db["loglist"] = LogList.readxml(LISTXMLPATH)
994           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
995           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
996           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
997           
998           when "confirm"
999           # 入力されたログファイルパスが既に存在するかを確認
1000           checkflag = true
1001           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
1002           db["loglist"].path.each do |val|
1003             if val == db["logpath"]
1004               # 重複していた場合エラーメッセージを表示し、処理を行わない
1005               db["logpath"] = ""
1006               params["action"] = ""
1007               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
1008               checkflag = false
1009               return
1010             end
1011           end
1012           
1013           if checkflag
1014             db["loginsertindex"] = cgi["loginsertindex"].read.to_i
1015             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
1016             db["importxml"] = cgi["updata"].read
1017             
1018             # XMLの整合性をチェックします
1019             begin
1020               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
1021             rescue => exception
1022               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
1023               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
1024               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
1025               db["error"] << "</div>"
1026               params["action"] = ""
1027               return
1028             end
1029           end
1030           
1031           # 0位置への挿入防止
1032           if db["loginsertindex"] == 0
1033             params["action"] = ""
1034             db["error"] = "<br><span style='color: #ff0000'>ラジオボックスでログの挿入位置を選択してください!</span><br>"
1035             return
1036           end
1037           
1038           if db["logpath"] == FEEDXMLDIR || db["logdisplay"].blank?
1039             params["action"] = ""
1040             db["error"] = "<br><span style='color: #ff0000'>インポートファイル、及び、ログの表示名は空欄にできません。</span><br>"
1041             return
1042           end
1043           
1044         else
1045         end
1046         
1047         # diary.xmlを入れ替える処理
1048         when "replace"
1049         case params["action"]
1050           when "exec"
1051           # この段階のエラーに対しては強度のエラーを発する
1052           checkflag = true
1053           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
1054           db["loglist"].path.each do |val|
1055             if val == db["logpath"]
1056               # 重複していた場合エラーメッセージを表示し、処理を行わない
1057               params["action"] = ""
1058               params["mode"] = "error"
1059               db["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
1060               return
1061             end
1062           end
1063           
1064           db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
1065           
1066           if File.basename(db["logpath"]).blank? || db["logdisplay"].blank? || db["importxml"].blank?
1067             params["action"] = ""
1068             params["mode"] = "error"
1069             db["error"] = "ログファイルの追加中に重大なエラーが発生しました。"
1070             return
1071           end
1072           
1073           # diary.xmlを移動します
1074           FileUtils.move(XMLPATH + "diary.xml", XMLPATH + File.basename(db["logpath"]))
1075           # ファイルをアップロードします
1076           file = FileUploader.new
1077           file.upload("diary.xml", db["importxml"])
1078           
1079           # loglist.xmlを更新します
1080           db["loglist"].path[1, 0] = db["logpath"]
1081           db["loglist"].display[1, 0] = db["logdisplay"]
1082           db["loglist"].to_xml
1083           
1084           # 情報を更新します
1085           db["loglist"] = LogList.readxml(LISTXMLPATH)
1086           session["filepath"] = db["loglist"].path[0] if session["filepath"] == nil
1087           db["feed"] = Feed.readxml(XMLPATH + File.basename(session["filepath"]))
1088           db["entry"] = Entry.readxml(XMLPATH + File.basename(session["filepath"]))
1089           
1090           when "confirm"
1091           # 入力されたログファイルパスが既に存在するかを確認
1092           checkflag = true
1093           db["logpath"] = Controller.get_mpart_value(cgi["logpath"])
1094           db["loglist"].path.each do |val|
1095             if val == db["logpath"]
1096               # 重複していた場合エラーメッセージを表示し、処理を行わない
1097               params["action"] = ""
1098               db["error"] = "<br><span style='color: #ff0000'>同一のファイルが存在します!別の名前を指定してください。</span><br>"
1099               checkflag = false
1100               return
1101             end
1102           end
1103           
1104           if checkflag == true
1105             db["logdisplay"] = Controller.get_mpart_value(cgi["logdisplay"])
1106             db["importxml"] = cgi["updata"].read
1107             
1108             # XMLの整合性をチェックします
1109             begin
1110               REXML::Document.new(Controller.fix_updata_enc(db["importxml"].to_s))
1111             rescue => exception
1112               db["error"] = "<br><span style='color: #ff0000'>不正な整形のXMLです!ファイルを見直してください。</span><br>"
1113               db["error"] << "<div class='divstyle' style='width: 560px; color: #ff0000; text-align: left;'>"
1114               db["error"] << CGI.escapeHTML(exception.to_s).gsub("\n", "<br>")
1115               db["error"] << "</div>"
1116               params["action"] = ""
1117               return
1118             end
1119           end
1120           
1121           if db["logpath"].blank? || db["logdisplay"].blank? || db["importxml"].blank?
1122             params["action"] = ""
1123             db["error"] = "<br><span style='color: #ff0000'>インポートファイル、及び、ログの表示名、ログのパスは空欄にできません。</span><br>"
1124             return
1125           end
1126           
1127         else
1128         end
1129         
1130       else
1131       end
1132     end
1133   end
1134   
1135   # Formのenctypeがmultypartだった場合、入力された値をrubyバージョンに左右されずに読み取るメソッドです。
1136   def Controller.get_mpart_value(cgi_param)
1137     if RUBY_VERSION >= "1.9.0"
1138       cgi_param[0..cgi_param.length].to_s
1139     else
1140       cgi_param.read.to_s
1141     end
1142   end
1143   
1144   # アップロードされたXMLをバージョンに左右されずに読み取るために、ruby-1.9.1以上ではエンコーディングを強制指定します
1145   def Controller.fix_updata_enc(file_to_s)
1146     if RUBY_VERSION >= "1.9.0"
1147       file_to_s.force_encoding("UTF-8")
1148     else
1149       file_to_s
1150     end
1151   end
1152   
1153 end
1154
1155 def main
1156   # SESSION変数、パラメータなどを取得します
1157   cgi = CGI.new
1158   session = CGI::Session.new(cgi)
1159   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
1160   
1161   # コントローラー部分
1162   # セッション管理
1163   if session["login"] != "true"
1164     if (cgi["loginid"] == LOGINID && cgi["password"] == PASSWORD)
1165       session["login"] = "true"
1166       
1167       # ワークフォルダの中をクリーンアップします
1168       filelist = Dir::entries("./work")
1169       # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合
1170       filelist.each do |file|
1171         File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"
1172       end
1173     end
1174   end
1175   
1176   # ログアウト処理
1177   if params["mode"] == "logout"
1178     session["login"] = nil
1179     session["logini"] = nil
1180     session["password"] = nil
1181     session.delete
1182   end
1183   
1184   begin
1185     # メインコントローラー
1186     # セッションが有効な場合のも実行します
1187     if session["login"] == "true"
1188       # PStore破損チェック!
1189       begin
1190         db = PStore.new("./work/#{session.session_id}.dat")
1191         db.transaction do
1192           db["error"] = ""
1193         end
1194       rescue
1195         # PStoreファイルを破棄する
1196         File.delete("./work/#{session.session_id}.dat")
1197         # PStoreが破損していた場合はセッション情報を破棄する
1198         session["login"] = nil
1199         session["logini"] = nil
1200         session["password"] = nil
1201         session.delete
1202       end
1203       
1204       # フォームによって挙動を変更します
1205       if cgi["mode"].respond_to?(:read) && cgi["action"].respond_to?(:read)
1206         params["mode"] = cgi["mode"].read
1207         params["action"] = cgi["action"].read
1208         Controller.MultiForm(cgi, session, params, db)
1209       else
1210         Controller.NormalForm(cgi, session, params, db)
1211       end
1212     end
1213     
1214     # メニューとして表示されるHTML文字列です
1215     if USEFILEMANAGER == true
1216       menu = "<div class=\"divstyle\" style=\"width: #{TABLEWIDTH}px;\"><div class=\"divstyle\" align=\"center\" style=\"margin-bottom: 5px;\">現在編集中のファイル : #{session["filepath"]} [ <a href=\"#{cgi.script_name}?mode=selectlog\">他のファイルを選択</a> ]</div>[ <a href=\"#{cgi.script_name}\">トップページ</a> | 記事管理 ( <a href=\"#{cgi.script_name}?mode=newentry\">作成</a> | <a href=\"#{cgi.script_name}?mode=editentry\">編集</a> | <a href=\"#{cgi.script_name}?mode=delentry\">消去</a> )  | <a href=\"#{cgi.script_name}?mode=editfeed\">XML情報編集</a> | <a href=\"#{cgi.script_name}?mode=log\">ログ管理</a> | <a href=\"#{cgi.script_name}?mode=import\">インポート</a> | <a href=\"#{FILEMANAGER}\" target=\"_blank\">ファイル管理</a> | <a href=\"#{cgi.script_name}?mode=reset\">初期化</a> | <a href=\"#{cgi.script_name}?mode=logout\">ログアウト</a> ]</div>"
1217     else
1218       menu = "<div class=\"divstyle\" style=\"width: #{TABLEWIDTH}px;\"><div class=\"divstyle\" align=\"center\" style=\"margin-bottom: 5px;\">現在編集中のファイル : #{session["filepath"]} [ <a href=\"#{cgi.script_name}?mode=selectlog\">他のファイルを選択</a> ]</div>[ <a href=\"#{cgi.script_name}\">トップページ</a> | 記事管理 ( <a href=\"#{cgi.script_name}?mode=newentry\">作成</a> | <a href=\"#{cgi.script_name}?mode=editentry\">編集</a> | <a href=\"#{cgi.script_name}?mode=delentry\">消去</a> )  | <a href=\"#{cgi.script_name}?mode=editfeed\">XML情報編集</a> | <a href=\"#{cgi.script_name}?mode=log\">ログ管理</a> | <a href=\"#{cgi.script_name}?mode=import\">インポート</a> | <a href=\"#{cgi.script_name}?mode=reset\">初期化</a> | <a href=\"#{cgi.script_name}?mode=logout\">ログアウト</a> ]</div>"
1219     end
1220     
1221     # ビュー部分
1222     # modeとactionの相関図は以下のようになります。
1223     # [mode] + [action]
1224     #        + [action]
1225     #        + [action]
1226     #        + ... and more
1227     if session["login"] != "true"
1228       # セッションが存在しない場合は強制的にトップページに遷移
1229       htmlwriter = HtmlWriter.new("./erbtemp/login.html.erb", binding)
1230     else
1231       case params["mode"]
1232         when "selectlog"
1233         htmlwriter = HtmlWriter.new("./erbtemp/select.html.erb", binding)
1234         when "newentry"
1235         htmlwriter = HtmlWriter.new("./erbtemp/newentry.html.erb", binding)
1236         when "editentry"
1237         htmlwriter = HtmlWriter.new("./erbtemp/editentry.html.erb", binding)
1238         when "delentry"
1239         htmlwriter = HtmlWriter.new("./erbtemp/delentry.html.erb", binding)
1240         when "editfeed"
1241         htmlwriter = HtmlWriter.new("./erbtemp/editfeed.html.erb", binding)
1242         when "log"
1243         htmlwriter = HtmlWriter.new("./erbtemp/log.html.erb", binding)
1244         when "insert"
1245         htmlwriter = HtmlWriter.new("./erbtemp/insertfeed.html.erb", binding)
1246         when "replace"
1247         htmlwriter = HtmlWriter.new("./erbtemp/replacefeed.html.erb", binding)
1248         when "import"
1249         htmlwriter = HtmlWriter.new("./erbtemp/indeximport.html.erb", binding)
1250         when "reset"
1251         htmlwriter = HtmlWriter.new("./erbtemp/reset.html.erb", binding)
1252         when "error"
1253         htmlwriter = HtmlWriter.new("./erbtemp/error.html.erb", binding)
1254       else
1255         htmlwriter = HtmlWriter.new("./erbtemp/index.html.erb", binding)
1256       end
1257     end
1258   rescue => exception
1259     # エラーが発生した場合、それを画面に表示します
1260     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1261   end
1262   
1263   # 実際にHTMLを出力します
1264   begin
1265     cgi.out{htmlwriter.to_code}
1266   rescue => exception
1267     # エラーが発生した場合、それを画面に表示します
1268     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
1269     cgi.out{htmlwriter.to_code}
1270   end
1271 end
1272
1273 begin
1274   main
1275 rescue => evar
1276   # エラーが発生した場合、それを画面に表示します
1277   detail = ("%s: %s (%s)\n" %
1278   [evar.backtrace[0], evar.message, evar.send('class')]) +
1279   evar.backtrace[1..-1].join("\n")
1280   puts "content-type: text/html\n\n<plaintext>\n" + detail
1281 end