#!/usr/bin/ruby ## <URL:http://www.t-doi.org/linux/autopic.html>を勝手に拝借させていただき ## 自分用に改造。 ## == Usage ## ./mailparse.rb < aMail ## == たぶん必要な環境 (deb package) ## * Ruby (ruby 1.6.7-3) ## * Uconv (libuconv-ruby 0.4.9-1) ## * REXML (librexml-ruby 1.2.5-1) ## * TMail (libtmail-ruby 0.10.0-1) $SAVE_DIR = `echo $HOME`.chomp + '/public_html/mp' require 'uconv' require 'rexml/document' require 'time' require 'ftools' require 'kconv' require 'tmail' require 'base64' logfile = $SAVE_DIR + "/photos.xml" doc = nil if File.exist? logfile File.open(logfile) {|f| doc = REXML::Document.new(Uconv::euctou8(f.read)) } else doc = REXML::Document.new "<photolist></photolist>" doc << REXML::XMLDecl.new doc.xml_decl.encoding = 'euc-jp' end class PhotoMail attr_accessor :text, :filename, :origname attr_reader :pdate def initialize(date, subject) @mdate = date if(%r|\[pic\:(\d\d\-\d\d\-\d\d)\_(\d\d)\-(\d\d)\]|.match(subject)) @pdate = Time.parse("#$1 #$2:#$3") end end def savefile(data, basedir=".") File.mkpath(File.dirname("#{basedir}#@filename")) file = File.open("#{basedir}#@filename",'w+') file.chmod(0644) file.write(data) file.close() end def to_xml xml = REXML::Element.new("photo") xml.attributes["maildate"] = @mdate.xmlschema if @pdate then xml.attributes["photodate"] = @pdate.xmlschema end xml.attributes["origname"] = @origname if @filename xml.attributes["filename"] = ".#@filename" xml.attributes["id"] = "mp#{@filename.gsub(%r|/|, '')}" end xml.add_text (Uconv::euctou8(Kconv.toeuc(@text))) xml end end mailstring = '' while (line = gets()) mailstring += line end mail = TMail::Mail.parse(mailstring) if mail.multipart? then pm = PhotoMail.new(mail.date, mail.subject) # ここにメールを処理する部分が入るが省略。 doc.root.add_element(pm.to_xml) # 実行する度にphoto要素内の文字列の頭と末尾に改行が増えていってしまうので、 # 美しくないけれどその処理をしておく……。 doc.elements.each("photolist/photo") {|e| e.text.strip!} # 実行する度にphoto要素間に2つくらいずつ改行が増えていってしまうので、 # 美しくないけれどその処理をしておく……。 doc.elements.each("photolist") do |child| child.delete_if {|grandchild| grandchild.class==REXML::Text} end File.open(logfile,'w+') {|f| f.print "#{Uconv::u8toeuc(doc.to_s)}\n"} endこのプログラムを見てとりあえず疑問に思ってほしいことは、、、、一杯ある。
プログラムの中身がどうという以前に、プログラム開発とデバッグの方法が基 本的に理解されていないように見えるのがまずは問題である。つまり、
スクリプトの中ではいじられていないデータ (すなわち読み込まれて何もされず に書き出されるデータ) にまで被害が及ぶので、 バグの原因はUconvまたは REXMLの一方もしくは両方 (つまり複合的な要因) にあると 思われる。とかいって人のせいにする前に、どちらのせいかくらいは調べてほしい。これ を調べるのは別になにも難しいことではない。単に、 Uconv を使わないよう にプログラムを変更して、それでも同じ問題が起きるなら REXML (またはその 使いかた)のせいであり、そうでなければ Uconv に少なくとも原因の一部はあ るということになる。
さらに、「どこでおかしくなったか」も重要な情報である。つまり、このプロ グラムの場合なら、とりあえず読み込んで要素を追加する前にすぐに書いてみ て、それでも結果がおかしいなら REXML 自体の読み込みあるいは書き出しの メソッドに問題がある可能性があり、そうでなければ自分のプログラムが結果 を破壊しているわけである。 実際、 ruby 1.8.1 と適当なバージョンの rexml 等で上のスクリプトを適当 に補間したもの、具体的には
# ここにメールを処理する部分が入るが省略。の後に
pm.text = "" mail.parts.each do |m| if m.main_type == 'text' pm.text += m.body else pm.filename = File.basename(m.disposition_param('filename').gsub(/\\/, '/')) pm.savefile(decode64(m.body), $SAVE_DIR); end endだけを追加したものを動かしてみると、(最後の妙な処理を消しても))何もおかしいことは起きない。このプログラムの作者の環境ではそれでも破壊されているという可能性はあるが、若干疑問な気がしてくるであろう。
もっとも、妙なことは起きない代わりに、
<?xml version='1.0'?><photolist><photo id='mpfoo.jpg' filename='.foo.jpg' maildate='2003-01-11T20:08:02+09:00'>sample text </photo></photolist>という具合に text 部分以外に全く改行がない XML ファイルが生成される。 これは DOM ライクな XML 処理エンジンの動作としては全く正当なものである が、人が読みやすいものではない。従って、元のプログラムではなんらかの形 で読みやすくするための処理をしていて、そのために書き出すたびに余計に改 行が増えているのではないかと想像される。
この辺りの、デバッグの方法の極めて基本的な部分については、「達人プログ ラマー」3章18節に簡潔にまとめられている。これは良い本なので読まないと 損である。
まあ、そんなことはともかく、問題はこのプログラムの構成原理である。目的 が、
<?xml version='1.0'?> <photolist> <photo id='mpfoo.jpg' filename='.foo.jpg' maildate='2003-01-11T20:08:02+09:00'> sample text </photo> </photolist>というファイルにエントリーを追加したいということであれば、「そういうプ ログラム」を書くことが自然であろう。しかも、このファイル自体が自分自身 の出力なので、XML をパースして DOM ライクなツリー構造を得る必要は全く ない。要するに、ファイルの最後にエントリーを追加すればいいのだから、
f = open(filename, "a") f.print "<photo id=mp# "+@filename.gsub(%r|/|, '') + ...というようなことをすればいいだけである。
もっとも、これでは </photolist> の後に要素が追加されてしまって、正し い XML ファイルにならない。これを回避するには、
#!/usr/bin/ruby $SAVE_DIR = ENV["HOME"] + '/public_html/mp' require 'time' require 'tmail' require 'base64' logfile = $SAVE_DIR + "/photos.xml" outfilename = $SAVE_DIR + "/photos" + Process.pid.to_s outfile = open(outfilename, "w+") if File.exist? logfile infile=File.open(logfile) while s=infile.gets outfile.print s unless s =~ /<\/photolist>/ end else outfile.print "<?xml version='1.0' encoding='euc-jp'?>\n<photolist>" end mail = TMail::Mail.parse(gets(nil)) if mail.multipart? then text = "" mail.parts.each do |m| if m.main_type == 'text' text += m.body else mdate = mail.date if(%r|\[pic\:(\d\d\-\d\d\-\d\d)\_(\d\d)\-(\d\d)\]|.match(mail.subject)) pdate = Time.parse("#$1 #$2:#$3") else pdate=nil end filename = File.basename(m.disposition_param('filename').gsub(/\\/, '/')) file = File.open($SAVE_DIR + "/"+filename,'w+') file.chmod(0644) file.write(m.body) file.close() outfile.print "<photo " # ここで色々書く outfile.print "maildate='",mail.date.xmlschema,"'>\n" outfile.print text, "\n</photo>\n" end end end outfile.print "</photolist>\n" File.rename(logfile, logfile + ".bak"); File.rename(outfilename, logfile);細かいことだが、環境変数 HOME の値は ENV["HOME"] で得られることや、改行ごと入 力全部を一気に読むには単に gets(nil) でいいこと等は知っていてもばちは当たら ないと思う。
ここでの主張は、
まあ、元のプログラムの作者の意図は REXML の勉強であって、そのためにわ ざわざややこしいことをしているということなのかもしれないが、クラスの使 い方を見る限りそれは疑わしい。(意図はそうかもしれないが勉強になっているかどうかが疑わしい) 例えばこのプログラムで定義されている PhotoMail なるクラスは、「何のためにそのクラスを使うのか」ということを 全く考えることなく作られているように見える。例えば、 @mdate や @pdate といった変数は、(ここで見る限り) XML 属性に書くのに一度だけつかわれるだけであ る。ならば、
xml.attributes["maildate"] = m.date.xmlschemaといった具合に、 Mail オブジェクトから Element オブジェクトに直接渡す べきであろう。
極めて善意に解釈するなら、中間クラスを使うことで XML 以外の形式にも対 応するというような意図があるのかもしれない。しかし、それなら、 PhotoMail に Mail オブジェクトを受け取って処理するというメソッドがある べきであろう。つまり、トップレベルでは
mail = TMail::Mail.parse(gets(nil)) pm = PhotoMail.new(mail) if xml = pm.to_xml then doc.root.add_element(xml)というような形になっていて欲しいところである。 text、 filename、 origname 等の、外から読み書きする必要が全くなさそうなイン スタンス変数が読み書きできるようになっている辺りも、クラスによって処理 の独立性を高めるという機能をクラスが果たしていないことを表している。
つまり、いいたいことは、
といっても、自分では一生懸命練習しているつもりなのにそれが能力を伸ばす ことになっていないというケースが往々にしてあるわけで、何故そうなるかが 真の問題である。独学ではできないかといえば、できている人はいくらでもい るわけではあるが、しかし実際に独学でできていない人も結構いる。その違い はどこからくるのか?というのはなんだかとても難しい問題ではある。
2003/1/5 追記 ここに本人のコメントがある。 保存版
なかなか反応は素直ですが、問題は実践につながるかどうかですね。